1
0
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:
Quentin Gliech
2021-09-23 20:53:51 +02:00
parent 2cfaff737e
commit 3bf86c4b21
10 changed files with 62 additions and 12 deletions

View File

@@ -42,24 +42,31 @@ use crate::{
/// This is recoverable with [`recover_unauthorized`] /// This is recoverable with [`recover_unauthorized`]
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum AuthenticationError { pub enum AuthenticationError {
/// The bearer token has an invalid format
#[error("invalid token format")] #[error("invalid token format")]
TokenFormat(#[from] TokenFormatError), TokenFormat(#[from] TokenFormatError),
/// The bearer token is not an access token
#[error("invalid token type {0:?}, expected an access token")] #[error("invalid token type {0:?}, expected an access token")]
WrongTokenType(TokenType), WrongTokenType(TokenType),
/// The access token was not found in the database
#[error("unknown token")] #[error("unknown token")]
TokenNotFound(#[source] AccessTokenLookupError), TokenNotFound(#[source] AccessTokenLookupError),
/// The access token is no longer active
#[error("token is not active")] #[error("token is not active")]
TokenInactive, TokenInactive,
/// The access token expired
#[error("token expired")] #[error("token expired")]
TokenExpired, TokenExpired,
/// The `Authorization` header is missing
#[error("missing authorization header")] #[error("missing authorization header")]
MissingAuthorizationHeader, MissingAuthorizationHeader,
/// The `Authorization` header is invalid
#[error("invalid authorization header")] #[error("invalid authorization header")]
InvalidAuthorizationHeader, InvalidAuthorizationHeader,
} }

View File

@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Handle client authentication
use headers::{authorization::Basic, Authorization}; use headers::{authorization::Basic, Authorization};
use serde::{de::DeserializeOwned, Deserialize}; use serde::{de::DeserializeOwned, Deserialize};
use thiserror::Error; use thiserror::Error;
@@ -20,22 +22,34 @@ use warp::{reject::Reject, Filter, Rejection};
use super::headers::typed_header; use super::headers::typed_header;
use crate::config::{OAuth2ClientConfig, OAuth2Config}; use crate::config::{OAuth2ClientConfig, OAuth2Config};
/// Type of client authentication that succeeded
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum ClientAuthentication { pub enum ClientAuthentication {
/// `client_secret_basic` authentication, where the `client_id` and
/// `client_secret` are sent through the `Authorization` header with
/// `Basic` authentication
ClientSecretBasic, ClientSecretBasic,
/// `client_secret_post` authentication, where the `client_id` and
/// `client_secret` are sent in the request body
ClientSecretPost, ClientSecretPost,
/// `none` authentication for public clients, where only the `client_id` is
/// sent in the request body
None, None,
} }
impl ClientAuthentication { impl ClientAuthentication {
#[must_use] #[must_use]
/// Check if the authenticated client is public or not
pub fn public(&self) -> bool { pub fn public(&self) -> bool {
matches!(self, &Self::None) matches!(self, &Self::None)
} }
} }
/// Protect an enpoint with client authentication
#[must_use] #[must_use]
pub fn with_client_auth<T: DeserializeOwned + Send + 'static>( pub fn client_authentication<T: DeserializeOwned + Send + 'static>(
oauth2_config: &OAuth2Config, oauth2_config: &OAuth2Config,
) -> impl Filter<Extract = (ClientAuthentication, OAuth2ClientConfig, T), Error = Rejection> ) -> impl Filter<Extract = (ClientAuthentication, OAuth2ClientConfig, T), Error = Rejection>
+ Clone + Clone
@@ -161,7 +175,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn client_secret_post() { 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() let (auth, client, body) = warp::test::request()
.method("POST") .method("POST")
@@ -178,7 +192,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn client_secret_basic() { 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() let (auth, client, body) = warp::test::request()
.method("POST") .method("POST")
@@ -196,7 +210,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn none() { 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() let (auth, client, body) = warp::test::request()
.method("POST") .method("POST")

View File

@@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Deal with encrypted cookies
use std::{convert::Infallible, marker::PhantomData}; use std::{convert::Infallible, marker::PhantomData};
use chacha20poly1305::{ use chacha20poly1305::{
aead::{generic_array::GenericArray, Aead, NewAead}, aead::{generic_array::GenericArray, Aead, NewAead},
ChaCha20Poly1305, ChaCha20Poly1305,
}; };
use cookie::Cookie; use cookie::{Cookie, SameSite};
use data_encoding::BASE64URL_NOPAD; use data_encoding::BASE64URL_NOPAD;
use headers::{Header, HeaderValue, SetCookie}; use headers::{Header, HeaderValue, SetCookie};
use serde::{de::DeserializeOwned, Deserialize, Serialize}; 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 /// A cookie that can be encrypted with a well-known cookie key
pub trait EncryptableCookieValue: Serialize + Send + Sync + std::fmt::Debug { pub trait EncryptableCookieValue: Serialize + Send + Sync + std::fmt::Debug {
/// What key should be used for this cookie
fn cookie_key() -> &'static str; fn cookie_key() -> &'static str;
} }
@@ -167,6 +170,7 @@ pub struct EncryptedCookieSaver {
} }
impl EncryptedCookieSaver { impl EncryptedCookieSaver {
/// Save an [`EncryptableCookieValue`]
pub fn save_encrypted<T: EncryptableCookieValue, R: Reply>( pub fn save_encrypted<T: EncryptableCookieValue, R: Reply>(
&self, &self,
cookie: &T, cookie: &T,
@@ -176,9 +180,14 @@ impl EncryptedCookieSaver {
.wrap_error()? .wrap_error()?
.to_cookie_value() .to_cookie_value()
.wrap_error()?; .wrap_error()?;
// TODO: make those options customizable
let value = Cookie::build(T::cookie_key(), encrypted) let value = Cookie::build(T::cookie_key(), encrypted)
.http_only(true)
.same_site(SameSite::Strict)
.finish() .finish()
.to_string(); .to_string();
let header = SetCookie::decode(&mut [HeaderValue::from_str(&value).wrap_error()?].iter()) let header = SetCookie::decode(&mut [HeaderValue::from_str(&value).wrap_error()?].iter())
.wrap_error()?; .wrap_error()?;
Ok(with_typed_header(header, reply)) Ok(with_typed_header(header, reply))

View File

@@ -25,20 +25,25 @@ use warp::{reject::Reject, Filter, Rejection};
use super::cookies::EncryptableCookieValue; use super::cookies::EncryptableCookieValue;
use crate::config::{CookiesConfig, CsrfConfig}; use crate::config::{CookiesConfig, CsrfConfig};
/// Failed to validate CSRF token
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum CsrfError { pub enum CsrfError {
/// The token in the form did not match the token in the cookie
#[error("CSRF token mismatch")] #[error("CSRF token mismatch")]
Mismatch, Mismatch,
/// The token expired
#[error("CSRF token expired")] #[error("CSRF token expired")]
Expired, Expired,
/// Failed to decode the token
#[error("could not decode CSRF token")] #[error("could not decode CSRF token")]
Decode(#[from] DecodeError), Decode(#[from] DecodeError),
} }
impl Reject for CsrfError {} impl Reject for CsrfError {}
/// A CSRF token
#[serde_as] #[serde_as]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct CsrfToken { pub struct CsrfToken {
@@ -125,8 +130,8 @@ fn csrf_token(
/// Extract an up-to-date CSRF token to include in forms /// Extract an up-to-date CSRF token to include in forms
/// ///
/// Routes using this should not forget to reply the updated CSRF cookie using /// Routes using this should not forget to reply the updated CSRF cookie using
/// an [`super::cookies::EncryptedCookieSaver`] obtained with /// an [`EncryptedCookieSaver`][`super::cookies::EncryptedCookieSaver`] obtained
/// [`super::cookies::encrypted_cookie_saver`] /// with [`encrypted_cookie_saver`][`super::cookies::encrypted_cookie_saver`]
#[must_use] #[must_use]
pub fn updated_csrf_token( pub fn updated_csrf_token(
cookies_config: &CookiesConfig, cookies_config: &CookiesConfig,

View File

@@ -11,7 +11,9 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//
//! Deal with typed headers from the [`headers`] crate
use headers::{Header, HeaderValue}; use headers::{Header, HeaderValue};
use thiserror::Error; use thiserror::Error;
use warp::{reject::Reject, Filter, Rejection}; use warp::{reject::Reject, Filter, Rejection};

View File

@@ -15,6 +15,7 @@
//! Set of [`warp`] filters //! Set of [`warp`] filters
#![allow(clippy::unused_async)] // Some warp filters need that #![allow(clippy::unused_async)] // Some warp filters need that
#![deny(missing_docs)]
pub mod csrf; pub mod csrf;
// mod errors; // mod errors;
@@ -35,6 +36,7 @@ use crate::{
templates::Templates, templates::Templates,
}; };
/// Get the [`Templates`]
#[must_use] #[must_use]
pub fn with_templates( pub fn with_templates(
templates: &Templates, templates: &Templates,
@@ -43,6 +45,7 @@ pub fn with_templates(
warp::any().map(move || templates.clone()) warp::any().map(move || templates.clone())
} }
/// Extract the [`KeySet`] from the [`OAuth2Config`]
#[must_use] #[must_use]
pub fn with_keys( pub fn with_keys(
oauth2_config: &OAuth2Config, oauth2_config: &OAuth2Config,

View File

@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Load user sessions from the database
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{pool::PoolConnection, Executor, PgPool, Postgres}; use sqlx::{pool::PoolConnection, Executor, PgPool, Postgres};
use thiserror::Error; use thiserror::Error;
@@ -31,26 +33,32 @@ use crate::{
storage::{lookup_active_session, user::ActiveSessionLookupError, SessionInfo}, storage::{lookup_active_session, user::ActiveSessionLookupError, SessionInfo},
}; };
/// The session is missing or failed to load
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum SessionLoadError { pub enum SessionLoadError {
/// No session cookie was found
#[error("missing session cookie")] #[error("missing session cookie")]
MissingCookie, MissingCookie,
/// The session cookie is invalid
#[error("unable to parse or decrypt session cookie")] #[error("unable to parse or decrypt session cookie")]
InvalidCookie, InvalidCookie,
/// The session is unknown or inactive
#[error("unknown or inactive session")] #[error("unknown or inactive session")]
UnknownSession, UnknownSession,
} }
impl Reject for SessionLoadError {} impl Reject for SessionLoadError {}
/// An encrypted cookie to save the session ID
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct SessionCookie { pub struct SessionCookie {
current: i64, current: i64,
} }
impl SessionCookie { impl SessionCookie {
/// Forge the cookie from a [`SessionInfo`]
#[must_use] #[must_use]
pub fn from_session_info(info: &SessionInfo) -> Self { pub fn from_session_info(info: &SessionInfo) -> Self {
Self { Self {
@@ -58,6 +66,7 @@ impl SessionCookie {
} }
} }
/// Load the [`SessionInfo`] from database
pub async fn load_session_info( pub async fn load_session_info(
&self, &self,
executor: impl Executor<'_, Database = Postgres>, executor: impl Executor<'_, Database = Postgres>,

View File

@@ -22,7 +22,7 @@ use crate::{
config::{OAuth2ClientConfig, OAuth2Config}, config::{OAuth2ClientConfig, OAuth2Config},
errors::WrapError, errors::WrapError,
filters::{ filters::{
client::{with_client_auth, ClientAuthentication}, client::{client_authentication, ClientAuthentication},
database::connection, database::connection,
}, },
storage::oauth2::{access_token::lookup_access_token, refresh_token::lookup_refresh_token}, storage::oauth2::{access_token::lookup_access_token, refresh_token::lookup_refresh_token},
@@ -36,7 +36,7 @@ pub fn filter(
warp::path!("oauth2" / "introspect") warp::path!("oauth2" / "introspect")
.and(warp::post()) .and(warp::post())
.and(connection(pool)) .and(connection(pool))
.and(with_client_auth(oauth2_config)) .and(client_authentication(oauth2_config))
.and_then(introspect) .and_then(introspect)
.recover(recover) .recover(recover)
} }

View File

@@ -40,7 +40,7 @@ use crate::{
config::{KeySet, OAuth2ClientConfig, OAuth2Config}, config::{KeySet, OAuth2ClientConfig, OAuth2Config},
errors::WrapError, errors::WrapError,
filters::{ filters::{
client::{with_client_auth, ClientAuthentication}, client::{client_authentication, ClientAuthentication},
database::connection, database::connection,
with_keys, with_keys,
}, },
@@ -91,7 +91,7 @@ pub fn filter(
let issuer = oauth2_config.issuer.clone(); let issuer = oauth2_config.issuer.clone();
warp::path!("oauth2" / "token") warp::path!("oauth2" / "token")
.and(warp::post()) .and(warp::post())
.and(with_client_auth(oauth2_config)) .and(client_authentication(oauth2_config))
.and(with_keys(oauth2_config)) .and(with_keys(oauth2_config))
.and(warp::any().map(move || issuer.clone())) .and(warp::any().map(move || issuer.clone()))
.and(connection(pool)) .and(connection(pool))

View File

@@ -14,6 +14,7 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![deny(clippy::all)] #![deny(clippy::all)]
#![deny(rustdoc::broken_intra_doc_links)]
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
#![allow(clippy::missing_panics_doc)] #![allow(clippy::missing_panics_doc)]