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`]
|
/// 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,
|
||||||
}
|
}
|
||||||
|
@@ -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")
|
||||||
|
@@ -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))
|
||||||
|
@@ -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,
|
||||||
|
@@ -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};
|
||||||
|
@@ -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,
|
||||||
|
@@ -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>,
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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))
|
||||||
|
@@ -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)]
|
||||||
|
Reference in New Issue
Block a user