You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-09 04:22:45 +03:00
Fully document the mas_core::filters module
This commit is contained in:
@@ -42,24 +42,31 @@ use crate::{
|
||||
/// This is recoverable with [`recover_unauthorized`]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AuthenticationError {
|
||||
/// The bearer token has an invalid format
|
||||
#[error("invalid token format")]
|
||||
TokenFormat(#[from] TokenFormatError),
|
||||
|
||||
/// The bearer token is not an access token
|
||||
#[error("invalid token type {0:?}, expected an access token")]
|
||||
WrongTokenType(TokenType),
|
||||
|
||||
/// The access token was not found in the database
|
||||
#[error("unknown token")]
|
||||
TokenNotFound(#[source] AccessTokenLookupError),
|
||||
|
||||
/// The access token is no longer active
|
||||
#[error("token is not active")]
|
||||
TokenInactive,
|
||||
|
||||
/// The access token expired
|
||||
#[error("token expired")]
|
||||
TokenExpired,
|
||||
|
||||
/// The `Authorization` header is missing
|
||||
#[error("missing authorization header")]
|
||||
MissingAuthorizationHeader,
|
||||
|
||||
/// The `Authorization` header is invalid
|
||||
#[error("invalid authorization header")]
|
||||
InvalidAuthorizationHeader,
|
||||
}
|
||||
|
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Handle client authentication
|
||||
|
||||
use headers::{authorization::Basic, Authorization};
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use thiserror::Error;
|
||||
@@ -20,22 +22,34 @@ use warp::{reject::Reject, Filter, Rejection};
|
||||
use super::headers::typed_header;
|
||||
use crate::config::{OAuth2ClientConfig, OAuth2Config};
|
||||
|
||||
/// Type of client authentication that succeeded
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ClientAuthentication {
|
||||
/// `client_secret_basic` authentication, where the `client_id` and
|
||||
/// `client_secret` are sent through the `Authorization` header with
|
||||
/// `Basic` authentication
|
||||
ClientSecretBasic,
|
||||
|
||||
/// `client_secret_post` authentication, where the `client_id` and
|
||||
/// `client_secret` are sent in the request body
|
||||
ClientSecretPost,
|
||||
|
||||
/// `none` authentication for public clients, where only the `client_id` is
|
||||
/// sent in the request body
|
||||
None,
|
||||
}
|
||||
|
||||
impl ClientAuthentication {
|
||||
#[must_use]
|
||||
/// Check if the authenticated client is public or not
|
||||
pub fn public(&self) -> bool {
|
||||
matches!(self, &Self::None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Protect an enpoint with client authentication
|
||||
#[must_use]
|
||||
pub fn with_client_auth<T: DeserializeOwned + Send + 'static>(
|
||||
pub fn client_authentication<T: DeserializeOwned + Send + 'static>(
|
||||
oauth2_config: &OAuth2Config,
|
||||
) -> impl Filter<Extract = (ClientAuthentication, OAuth2ClientConfig, T), Error = Rejection>
|
||||
+ Clone
|
||||
@@ -161,7 +175,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn client_secret_post() {
|
||||
let filter = with_client_auth::<Form>(&oauth2_config());
|
||||
let filter = client_authentication::<Form>(&oauth2_config());
|
||||
|
||||
let (auth, client, body) = warp::test::request()
|
||||
.method("POST")
|
||||
@@ -178,7 +192,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn client_secret_basic() {
|
||||
let filter = with_client_auth::<Form>(&oauth2_config());
|
||||
let filter = client_authentication::<Form>(&oauth2_config());
|
||||
|
||||
let (auth, client, body) = warp::test::request()
|
||||
.method("POST")
|
||||
@@ -196,7 +210,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn none() {
|
||||
let filter = with_client_auth::<Form>(&oauth2_config());
|
||||
let filter = client_authentication::<Form>(&oauth2_config());
|
||||
|
||||
let (auth, client, body) = warp::test::request()
|
||||
.method("POST")
|
||||
|
@@ -12,13 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Deal with encrypted cookies
|
||||
|
||||
use std::{convert::Infallible, marker::PhantomData};
|
||||
|
||||
use chacha20poly1305::{
|
||||
aead::{generic_array::GenericArray, Aead, NewAead},
|
||||
ChaCha20Poly1305,
|
||||
};
|
||||
use cookie::Cookie;
|
||||
use cookie::{Cookie, SameSite};
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
use headers::{Header, HeaderValue, SetCookie};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
@@ -158,6 +160,7 @@ pub fn encrypted_cookie_saver(
|
||||
|
||||
/// A cookie that can be encrypted with a well-known cookie key
|
||||
pub trait EncryptableCookieValue: Serialize + Send + Sync + std::fmt::Debug {
|
||||
/// What key should be used for this cookie
|
||||
fn cookie_key() -> &'static str;
|
||||
}
|
||||
|
||||
@@ -167,6 +170,7 @@ pub struct EncryptedCookieSaver {
|
||||
}
|
||||
|
||||
impl EncryptedCookieSaver {
|
||||
/// Save an [`EncryptableCookieValue`]
|
||||
pub fn save_encrypted<T: EncryptableCookieValue, R: Reply>(
|
||||
&self,
|
||||
cookie: &T,
|
||||
@@ -176,9 +180,14 @@ impl EncryptedCookieSaver {
|
||||
.wrap_error()?
|
||||
.to_cookie_value()
|
||||
.wrap_error()?;
|
||||
|
||||
// TODO: make those options customizable
|
||||
let value = Cookie::build(T::cookie_key(), encrypted)
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Strict)
|
||||
.finish()
|
||||
.to_string();
|
||||
|
||||
let header = SetCookie::decode(&mut [HeaderValue::from_str(&value).wrap_error()?].iter())
|
||||
.wrap_error()?;
|
||||
Ok(with_typed_header(header, reply))
|
||||
|
@@ -25,20 +25,25 @@ use warp::{reject::Reject, Filter, Rejection};
|
||||
use super::cookies::EncryptableCookieValue;
|
||||
use crate::config::{CookiesConfig, CsrfConfig};
|
||||
|
||||
/// Failed to validate CSRF token
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CsrfError {
|
||||
/// The token in the form did not match the token in the cookie
|
||||
#[error("CSRF token mismatch")]
|
||||
Mismatch,
|
||||
|
||||
/// The token expired
|
||||
#[error("CSRF token expired")]
|
||||
Expired,
|
||||
|
||||
/// Failed to decode the token
|
||||
#[error("could not decode CSRF token")]
|
||||
Decode(#[from] DecodeError),
|
||||
}
|
||||
|
||||
impl Reject for CsrfError {}
|
||||
|
||||
/// A CSRF token
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CsrfToken {
|
||||
@@ -125,8 +130,8 @@ fn csrf_token(
|
||||
/// Extract an up-to-date CSRF token to include in forms
|
||||
///
|
||||
/// Routes using this should not forget to reply the updated CSRF cookie using
|
||||
/// an [`super::cookies::EncryptedCookieSaver`] obtained with
|
||||
/// [`super::cookies::encrypted_cookie_saver`]
|
||||
/// an [`EncryptedCookieSaver`][`super::cookies::EncryptedCookieSaver`] obtained
|
||||
/// with [`encrypted_cookie_saver`][`super::cookies::encrypted_cookie_saver`]
|
||||
#[must_use]
|
||||
pub fn updated_csrf_token(
|
||||
cookies_config: &CookiesConfig,
|
||||
|
@@ -11,7 +11,9 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
//! Deal with typed headers from the [`headers`] crate
|
||||
|
||||
use headers::{Header, HeaderValue};
|
||||
use thiserror::Error;
|
||||
use warp::{reject::Reject, Filter, Rejection};
|
||||
|
@@ -15,6 +15,7 @@
|
||||
//! Set of [`warp`] filters
|
||||
|
||||
#![allow(clippy::unused_async)] // Some warp filters need that
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub mod csrf;
|
||||
// mod errors;
|
||||
@@ -35,6 +36,7 @@ use crate::{
|
||||
templates::Templates,
|
||||
};
|
||||
|
||||
/// Get the [`Templates`]
|
||||
#[must_use]
|
||||
pub fn with_templates(
|
||||
templates: &Templates,
|
||||
@@ -43,6 +45,7 @@ pub fn with_templates(
|
||||
warp::any().map(move || templates.clone())
|
||||
}
|
||||
|
||||
/// Extract the [`KeySet`] from the [`OAuth2Config`]
|
||||
#[must_use]
|
||||
pub fn with_keys(
|
||||
oauth2_config: &OAuth2Config,
|
||||
|
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Load user sessions from the database
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{pool::PoolConnection, Executor, PgPool, Postgres};
|
||||
use thiserror::Error;
|
||||
@@ -31,26 +33,32 @@ use crate::{
|
||||
storage::{lookup_active_session, user::ActiveSessionLookupError, SessionInfo},
|
||||
};
|
||||
|
||||
/// The session is missing or failed to load
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SessionLoadError {
|
||||
/// No session cookie was found
|
||||
#[error("missing session cookie")]
|
||||
MissingCookie,
|
||||
|
||||
/// The session cookie is invalid
|
||||
#[error("unable to parse or decrypt session cookie")]
|
||||
InvalidCookie,
|
||||
|
||||
/// The session is unknown or inactive
|
||||
#[error("unknown or inactive session")]
|
||||
UnknownSession,
|
||||
}
|
||||
|
||||
impl Reject for SessionLoadError {}
|
||||
|
||||
/// An encrypted cookie to save the session ID
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SessionCookie {
|
||||
current: i64,
|
||||
}
|
||||
|
||||
impl SessionCookie {
|
||||
/// Forge the cookie from a [`SessionInfo`]
|
||||
#[must_use]
|
||||
pub fn from_session_info(info: &SessionInfo) -> Self {
|
||||
Self {
|
||||
@@ -58,6 +66,7 @@ impl SessionCookie {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the [`SessionInfo`] from database
|
||||
pub async fn load_session_info(
|
||||
&self,
|
||||
executor: impl Executor<'_, Database = Postgres>,
|
||||
|
@@ -22,7 +22,7 @@ use crate::{
|
||||
config::{OAuth2ClientConfig, OAuth2Config},
|
||||
errors::WrapError,
|
||||
filters::{
|
||||
client::{with_client_auth, ClientAuthentication},
|
||||
client::{client_authentication, ClientAuthentication},
|
||||
database::connection,
|
||||
},
|
||||
storage::oauth2::{access_token::lookup_access_token, refresh_token::lookup_refresh_token},
|
||||
@@ -36,7 +36,7 @@ pub fn filter(
|
||||
warp::path!("oauth2" / "introspect")
|
||||
.and(warp::post())
|
||||
.and(connection(pool))
|
||||
.and(with_client_auth(oauth2_config))
|
||||
.and(client_authentication(oauth2_config))
|
||||
.and_then(introspect)
|
||||
.recover(recover)
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ use crate::{
|
||||
config::{KeySet, OAuth2ClientConfig, OAuth2Config},
|
||||
errors::WrapError,
|
||||
filters::{
|
||||
client::{with_client_auth, ClientAuthentication},
|
||||
client::{client_authentication, ClientAuthentication},
|
||||
database::connection,
|
||||
with_keys,
|
||||
},
|
||||
@@ -91,7 +91,7 @@ pub fn filter(
|
||||
let issuer = oauth2_config.issuer.clone();
|
||||
warp::path!("oauth2" / "token")
|
||||
.and(warp::post())
|
||||
.and(with_client_auth(oauth2_config))
|
||||
.and(client_authentication(oauth2_config))
|
||||
.and(with_keys(oauth2_config))
|
||||
.and(warp::any().map(move || issuer.clone()))
|
||||
.and(connection(pool))
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(clippy::all)]
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
#![allow(clippy::missing_panics_doc)]
|
||||
|
Reference in New Issue
Block a user