You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-29 22:01:14 +03:00
storage: document all the repository traits and methods
This commit is contained in:
@ -23,11 +23,15 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{tracing::ExecuteExt, DatabaseError, LookupResultExt};
|
||||
|
||||
/// An implementation of [`CompatAccessTokenRepository`] for a PostgreSQL
|
||||
/// connection
|
||||
pub struct PgCompatAccessTokenRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgCompatAccessTokenRepository<'c> {
|
||||
/// Create a new [`PgCompatAccessTokenRepository`] from an active PostgreSQL
|
||||
/// connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -12,6 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! A module containing PostgreSQL implementation of repositories for the
|
||||
//! compatibility layer
|
||||
|
||||
mod access_token;
|
||||
mod refresh_token;
|
||||
mod session;
|
||||
|
@ -25,11 +25,15 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{tracing::ExecuteExt, DatabaseError, LookupResultExt};
|
||||
|
||||
/// An implementation of [`CompatRefreshTokenRepository`] for a PostgreSQL
|
||||
/// connection
|
||||
pub struct PgCompatRefreshTokenRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgCompatRefreshTokenRepository<'c> {
|
||||
/// Create a new [`PgCompatRefreshTokenRepository`] from an active
|
||||
/// PostgreSQL connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -23,11 +23,14 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{tracing::ExecuteExt, DatabaseError, DatabaseInconsistencyError, LookupResultExt};
|
||||
|
||||
/// An implementation of [`CompatSessionRepository`] for a PostgreSQL connection
|
||||
pub struct PgCompatSessionRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgCompatSessionRepository<'c> {
|
||||
/// Create a new [`PgCompatSessionRepository`] from an active PostgreSQL
|
||||
/// connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -27,11 +27,15 @@ use crate::{
|
||||
LookupResultExt,
|
||||
};
|
||||
|
||||
/// An implementation of [`CompatSsoLoginRepository`] for a PostgreSQL
|
||||
/// connection
|
||||
pub struct PgCompatSsoLoginRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgCompatSsoLoginRepository<'c> {
|
||||
/// Create a new [`PgCompatSsoLoginRepository`] from an active PostgreSQL
|
||||
/// connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -13,25 +13,29 @@
|
||||
// limitations under the License.
|
||||
|
||||
//! An implementation of the storage traits for a PostgreSQL database
|
||||
//!
|
||||
//! This backend uses [`sqlx`] to interact with the database. Most queries are
|
||||
//! type-checked, using introspection data recorded in the `sqlx-data.json`
|
||||
//! file. This file is generated by the `sqlx` CLI tool, and should be updated
|
||||
//! whenever the database schema changes, or new queries are added.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(
|
||||
clippy::all,
|
||||
clippy::str_to_string,
|
||||
clippy::future_not_send,
|
||||
rustdoc::broken_intra_doc_links
|
||||
rustdoc::broken_intra_doc_links,
|
||||
missing_docs
|
||||
)]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions
|
||||
)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use sqlx::{migrate::Migrator, postgres::PgQueryResult};
|
||||
use thiserror::Error;
|
||||
use ulid::Ulid;
|
||||
|
||||
/// An extension trait for [`Result`] which adds a [`to_option`] method, useful
|
||||
/// for handling "not found" errors from [`sqlx`]
|
||||
trait LookupResultExt {
|
||||
type Output;
|
||||
|
||||
@ -57,7 +61,11 @@ impl<T> LookupResultExt for Result<T, sqlx::Error> {
|
||||
#[error(transparent)]
|
||||
pub enum DatabaseError {
|
||||
/// An error which came from the database itself
|
||||
Driver(#[from] sqlx::Error),
|
||||
Driver {
|
||||
/// The underlying error from the database driver
|
||||
#[from]
|
||||
source: sqlx::Error,
|
||||
},
|
||||
|
||||
/// An error which occured while converting the data from the database
|
||||
Inconsistency(#[from] DatabaseInconsistencyError),
|
||||
@ -66,6 +74,7 @@ pub enum DatabaseError {
|
||||
/// invalid
|
||||
#[error("Invalid database operation")]
|
||||
InvalidOperation {
|
||||
/// The source of the error, if any
|
||||
#[source]
|
||||
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
||||
},
|
||||
@ -73,7 +82,13 @@ pub enum DatabaseError {
|
||||
/// An error which happens when an operation affects not enough or too many
|
||||
/// rows
|
||||
#[error("Expected {expected} rows to be affected, but {actual} rows were affected")]
|
||||
RowsAffected { expected: u64, actual: u64 },
|
||||
RowsAffected {
|
||||
/// How many rows were expected to be affected
|
||||
expected: u64,
|
||||
|
||||
/// How many rows were actually affected
|
||||
actual: u64,
|
||||
},
|
||||
}
|
||||
|
||||
impl DatabaseError {
|
||||
@ -100,12 +115,19 @@ impl DatabaseError {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error which occured while converting the data from the database
|
||||
#[derive(Debug, Error)]
|
||||
pub struct DatabaseInconsistencyError {
|
||||
/// The table which was being queried
|
||||
table: &'static str,
|
||||
|
||||
/// The column which was being queried
|
||||
column: Option<&'static str>,
|
||||
|
||||
/// The row which was being queried
|
||||
row: Option<Ulid>,
|
||||
|
||||
/// The source of the error
|
||||
#[source]
|
||||
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
||||
}
|
||||
@ -125,6 +147,7 @@ impl std::fmt::Display for DatabaseInconsistencyError {
|
||||
}
|
||||
|
||||
impl DatabaseInconsistencyError {
|
||||
/// Create a new [`DatabaseInconsistencyError`] for the given table
|
||||
#[must_use]
|
||||
pub(crate) const fn on(table: &'static str) -> Self {
|
||||
Self {
|
||||
@ -135,18 +158,22 @@ impl DatabaseInconsistencyError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the column which was being queried
|
||||
#[must_use]
|
||||
pub(crate) const fn column(mut self, column: &'static str) -> Self {
|
||||
self.column = Some(column);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the row which was being queried
|
||||
#[must_use]
|
||||
pub(crate) const fn row(mut self, row: Ulid) -> Self {
|
||||
self.row = Some(row);
|
||||
self
|
||||
}
|
||||
|
||||
/// Give the source of the error
|
||||
#[must_use]
|
||||
pub(crate) fn source<E: std::error::Error + Send + Sync + 'static>(
|
||||
mut self,
|
||||
source: E,
|
||||
@ -158,11 +185,12 @@ impl DatabaseInconsistencyError {
|
||||
|
||||
pub mod compat;
|
||||
pub mod oauth2;
|
||||
pub mod upstream_oauth2;
|
||||
pub mod user;
|
||||
|
||||
pub(crate) mod pagination;
|
||||
pub(crate) mod repository;
|
||||
pub(crate) mod tracing;
|
||||
pub mod upstream_oauth2;
|
||||
pub mod user;
|
||||
|
||||
pub use self::repository::PgRepository;
|
||||
|
||||
|
@ -23,11 +23,15 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{tracing::ExecuteExt, DatabaseError, LookupResultExt};
|
||||
|
||||
/// An implementation of [`OAuth2AccessTokenRepository`] for a PostgreSQL
|
||||
/// connection
|
||||
pub struct PgOAuth2AccessTokenRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgOAuth2AccessTokenRepository<'c> {
|
||||
/// Create a new [`PgOAuth2AccessTokenRepository`] from an active PostgreSQL
|
||||
/// connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -30,11 +30,15 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{tracing::ExecuteExt, DatabaseError, DatabaseInconsistencyError, LookupResultExt};
|
||||
|
||||
/// An implementation of [`OAuth2AuthorizationGrantRepository`] for a PostgreSQL
|
||||
/// connection
|
||||
pub struct PgOAuth2AuthorizationGrantRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgOAuth2AuthorizationGrantRepository<'c> {
|
||||
/// Create a new [`PgOAuth2AuthorizationGrantRepository`] from an active
|
||||
/// PostgreSQL connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -39,11 +39,14 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{tracing::ExecuteExt, DatabaseError, DatabaseInconsistencyError, LookupResultExt};
|
||||
|
||||
/// An implementation of [`OAuth2ClientRepository`] for a PostgreSQL connection
|
||||
pub struct PgOAuth2ClientRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgOAuth2ClientRepository<'c> {
|
||||
/// Create a new [`PgOAuth2ClientRepository`] from an active PostgreSQL
|
||||
/// connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -12,8 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! A module containing the PostgreSQL implementations of the OAuth2-related
|
||||
//! repositories
|
||||
|
||||
mod access_token;
|
||||
pub mod authorization_grant;
|
||||
mod authorization_grant;
|
||||
mod client;
|
||||
mod refresh_token;
|
||||
mod session;
|
||||
|
@ -23,11 +23,15 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{tracing::ExecuteExt, DatabaseError, LookupResultExt};
|
||||
|
||||
/// An implementation of [`OAuth2RefreshTokenRepository`] for a PostgreSQL
|
||||
/// connection
|
||||
pub struct PgOAuth2RefreshTokenRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgOAuth2RefreshTokenRepository<'c> {
|
||||
/// Create a new [`PgOAuth2RefreshTokenRepository`] from an active
|
||||
/// PostgreSQL connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -26,11 +26,14 @@ use crate::{
|
||||
LookupResultExt,
|
||||
};
|
||||
|
||||
/// An implementation of [`OAuth2SessionRepository`] for a PostgreSQL connection
|
||||
pub struct PgOAuth2SessionRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgOAuth2SessionRepository<'c> {
|
||||
/// Create a new [`PgOAuth2SessionRepository`] from an active PostgreSQL
|
||||
/// connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -51,11 +51,19 @@ use crate::{
|
||||
DatabaseError,
|
||||
};
|
||||
|
||||
/// An implementation of the [`Repository`] trait backed by a PostgreSQL
|
||||
/// transaction.
|
||||
pub struct PgRepository {
|
||||
txn: Transaction<'static, Postgres>,
|
||||
}
|
||||
|
||||
impl PgRepository {
|
||||
/// Create a new [`PgRepository`] from a PostgreSQL connection pool,
|
||||
/// starting a transaction.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a [`DatabaseError`] if the transaction could not be started.
|
||||
pub async fn from_pool(pool: &PgPool) -> Result<Self, DatabaseError> {
|
||||
let txn = pool.begin().await?;
|
||||
Ok(PgRepository { txn })
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
use tracing::Span;
|
||||
|
||||
/// An extension trait for [`sqlx::Execute`] that records the SQL statement as
|
||||
/// `db.statement` in a tracing span
|
||||
pub trait ExecuteExt<'q, DB>: Sized {
|
||||
/// Records the statement as `db.statement` in the current span
|
||||
fn traced(self) -> Self {
|
||||
|
@ -23,11 +23,15 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{pagination::QueryBuilderExt, tracing::ExecuteExt, DatabaseError, LookupResultExt};
|
||||
|
||||
/// An implementation of [`UpstreamOAuthLinkRepository`] for a PostgreSQL
|
||||
/// connection
|
||||
pub struct PgUpstreamOAuthLinkRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgUpstreamOAuthLinkRepository<'c> {
|
||||
/// Create a new [`PgUpstreamOAuthLinkRepository`] from an active PostgreSQL
|
||||
/// connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -12,6 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! A module containing the PostgreSQL implementation of the repositories
|
||||
//! related to the upstream OAuth 2.0 providers
|
||||
|
||||
mod link;
|
||||
mod provider;
|
||||
mod session;
|
||||
@ -178,6 +181,8 @@ mod tests {
|
||||
assert_eq!(links.edges[0].user_id, Some(user.id));
|
||||
}
|
||||
|
||||
/// Test that the pagination works as expected in the upstream OAuth
|
||||
/// provider repository
|
||||
#[sqlx::test(migrator = "crate::MIGRATOR")]
|
||||
async fn test_provider_repository_pagination(pool: PgPool) {
|
||||
const ISSUER: &str = "https://example.com/";
|
||||
|
@ -28,11 +28,15 @@ use crate::{
|
||||
LookupResultExt,
|
||||
};
|
||||
|
||||
/// An implementation of [`UpstreamOAuthProviderRepository`] for a PostgreSQL
|
||||
/// connection
|
||||
pub struct PgUpstreamOAuthProviderRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgUpstreamOAuthProviderRepository<'c> {
|
||||
/// Create a new [`PgUpstreamOAuthProviderRepository`] from an active
|
||||
/// PostgreSQL connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -26,11 +26,15 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{tracing::ExecuteExt, DatabaseError, DatabaseInconsistencyError, LookupResultExt};
|
||||
|
||||
/// An implementation of [`UpstreamOAuthSessionRepository`] for a PostgreSQL
|
||||
/// connection
|
||||
pub struct PgUpstreamOAuthSessionRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgUpstreamOAuthSessionRepository<'c> {
|
||||
/// Create a new [`PgUpstreamOAuthSessionRepository`] from an active
|
||||
/// PostgreSQL connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -27,11 +27,14 @@ use crate::{
|
||||
LookupResultExt,
|
||||
};
|
||||
|
||||
/// An implementation of [`UserEmailRepository`] for a PostgreSQL connection
|
||||
pub struct PgUserEmailRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgUserEmailRepository<'c> {
|
||||
/// Create a new [`PgUserEmailRepository`] from an active PostgreSQL
|
||||
/// connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -12,6 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! A module containing the PostgreSQL implementation of the user-related
|
||||
//! repositories
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_data_model::User;
|
||||
@ -35,11 +38,13 @@ pub use self::{
|
||||
session::PgBrowserSessionRepository,
|
||||
};
|
||||
|
||||
/// An implementation of [`UserRepository`] for a PostgreSQL connection
|
||||
pub struct PgUserRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgUserRepository<'c> {
|
||||
/// Create a new [`PgUserRepository`] from an active PostgreSQL connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -23,11 +23,14 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{tracing::ExecuteExt, DatabaseError, DatabaseInconsistencyError, LookupResultExt};
|
||||
|
||||
/// An implementation of [`UserPasswordRepository`] for a PostgreSQL connection
|
||||
pub struct PgUserPasswordRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgUserPasswordRepository<'c> {
|
||||
/// Create a new [`PgUserPasswordRepository`] from an active PostgreSQL
|
||||
/// connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -26,11 +26,15 @@ use crate::{
|
||||
LookupResultExt,
|
||||
};
|
||||
|
||||
/// An implementation of [`BrowserSessionRepository`] for a PostgreSQL
|
||||
/// connection
|
||||
pub struct PgBrowserSessionRepository<'c> {
|
||||
conn: &'c mut PgConnection,
|
||||
}
|
||||
|
||||
impl<'c> PgBrowserSessionRepository<'c> {
|
||||
/// Create a new [`PgBrowserSessionRepository`] from an active PostgreSQL
|
||||
/// connection
|
||||
pub fn new(conn: &'c mut PgConnection) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
@ -253,6 +253,7 @@ async fn test_user_email_repo(pool: PgPool) {
|
||||
repo.save().await.unwrap();
|
||||
}
|
||||
|
||||
/// Test the user password repository implementation.
|
||||
#[sqlx::test(migrator = "crate::MIGRATOR")]
|
||||
async fn test_user_password_repo(pool: PgPool) {
|
||||
const USERNAME: &str = "john";
|
||||
|
@ -20,20 +20,58 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{repository_impl, Clock};
|
||||
|
||||
/// A [`CompatAccessTokenRepository`] helps interacting with
|
||||
/// [`CompatAccessToken`] saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait CompatAccessTokenRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup a compat access token by its ID
|
||||
///
|
||||
/// Returns the compat access token if it exists, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the compat access token to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatAccessToken>, Self::Error>;
|
||||
|
||||
/// Find a compat access token by its token
|
||||
///
|
||||
/// Returns the compat access token if found, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `access_token`: The token of the compat access token to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find_by_token(
|
||||
&mut self,
|
||||
access_token: &str,
|
||||
) -> Result<Option<CompatAccessToken>, Self::Error>;
|
||||
|
||||
/// Add a new compat access token to the database
|
||||
///
|
||||
/// Returns the newly created compat access token
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `compat_session`: The compat session associated with the access token
|
||||
/// * `token`: The token of the access token
|
||||
/// * `expires_after`: The duration after which the access token expires, if
|
||||
/// specified
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -44,6 +82,13 @@ pub trait CompatAccessTokenRepository: Send + Sync {
|
||||
) -> Result<CompatAccessToken, Self::Error>;
|
||||
|
||||
/// Set the expiration time of the compat access token to now
|
||||
///
|
||||
/// Returns the expired compat access token
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `compat_access_token`: The compat access token to expire
|
||||
async fn expire(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
|
@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Repositories to interact with entities of the compatibility layer
|
||||
|
||||
mod access_token;
|
||||
mod refresh_token;
|
||||
mod session;
|
||||
|
@ -19,20 +19,55 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{repository_impl, Clock};
|
||||
|
||||
/// A [`CompatRefreshTokenRepository`] helps interacting with
|
||||
/// [`CompatRefreshToken`] saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait CompatRefreshTokenRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup a compat refresh token by its ID
|
||||
///
|
||||
/// Returns the compat refresh token if it exists, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the compat refresh token to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatRefreshToken>, Self::Error>;
|
||||
|
||||
/// Find a compat refresh token by its token
|
||||
///
|
||||
/// Returns the compat refresh token if found, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `refresh_token`: The token of the compat refresh token to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find_by_token(
|
||||
&mut self,
|
||||
refresh_token: &str,
|
||||
) -> Result<Option<CompatRefreshToken>, Self::Error>;
|
||||
|
||||
/// Add a new compat refresh token to the database
|
||||
///
|
||||
/// Returns the newly created compat refresh token
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `compat_session`: The compat session associated with this refresh
|
||||
/// token
|
||||
/// * `compat_access_token`: The compat access token created alongside this
|
||||
/// refresh token
|
||||
/// * `token`: The token of the refresh token
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -43,6 +78,17 @@ pub trait CompatRefreshTokenRepository: Send + Sync {
|
||||
) -> Result<CompatRefreshToken, Self::Error>;
|
||||
|
||||
/// Consume a compat refresh token
|
||||
///
|
||||
/// Returns the consumed compat refresh token
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `compat_refresh_token`: The compat refresh token to consume
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn consume(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
|
@ -19,14 +19,40 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{repository_impl, Clock};
|
||||
|
||||
/// A [`CompatSessionRepository`] helps interacting with
|
||||
/// [`CompatSessionRepository`] saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait CompatSessionRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup a compat session by its ID
|
||||
///
|
||||
/// Returns the compat session if it exists, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the compat session to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatSession>, Self::Error>;
|
||||
|
||||
/// Start a new compat session
|
||||
///
|
||||
/// Returns the newly created compat session
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `user`: The user to create the compat session for
|
||||
/// * `device`: The device ID of this session
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -36,6 +62,17 @@ pub trait CompatSessionRepository: Send + Sync {
|
||||
) -> Result<CompatSession, Self::Error>;
|
||||
|
||||
/// End a compat session
|
||||
///
|
||||
/// Returns the ended compat session
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `compat_session`: The compat session to end
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn finish(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
|
@ -20,20 +20,56 @@ use url::Url;
|
||||
|
||||
use crate::{pagination::Page, repository_impl, Clock, Pagination};
|
||||
|
||||
/// A [`CompatSsoLoginRepository`] helps interacting with
|
||||
/// [`CompatSsoLoginRepository`] saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait CompatSsoLoginRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup a compat SSO login by its ID
|
||||
///
|
||||
/// Returns the compat SSO login if it exists, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the compat SSO login to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatSsoLogin>, Self::Error>;
|
||||
|
||||
/// Find a compat SSO login by its login token
|
||||
///
|
||||
/// Returns the compat SSO login if found, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `login_token`: The login token of the compat SSO login to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find_by_token(
|
||||
&mut self,
|
||||
login_token: &str,
|
||||
) -> Result<Option<CompatSsoLogin>, Self::Error>;
|
||||
|
||||
/// Start a new compat SSO login token
|
||||
///
|
||||
/// Returns the newly created compat SSO login
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate the timestamps
|
||||
/// * `login_token`: The login token given to the client
|
||||
/// * `redirect_uri`: The redirect URI given by the client
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -43,6 +79,19 @@ pub trait CompatSsoLoginRepository: Send + Sync {
|
||||
) -> Result<CompatSsoLogin, Self::Error>;
|
||||
|
||||
/// Fulfill a compat SSO login by providing a compat session
|
||||
///
|
||||
/// Returns the fulfilled compat SSO login
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate the timestamps
|
||||
/// * `compat_sso_login`: The compat SSO login to fulfill
|
||||
/// * `compat_session`: The compat session to associate with the compat SSO
|
||||
/// login
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn fulfill(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
@ -51,6 +100,17 @@ pub trait CompatSsoLoginRepository: Send + Sync {
|
||||
) -> Result<CompatSsoLogin, Self::Error>;
|
||||
|
||||
/// Mark a compat SSO login as exchanged
|
||||
///
|
||||
/// Returns the exchanged compat SSO login
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate the timestamps
|
||||
/// * `compat_sso_login`: The compat SSO login to mark as exchanged
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn exchange(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
@ -58,6 +118,15 @@ pub trait CompatSsoLoginRepository: Send + Sync {
|
||||
) -> Result<CompatSsoLogin, Self::Error>;
|
||||
|
||||
/// Get a paginated list of compat SSO logins for a user
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The user to get the compat SSO logins for
|
||||
/// * `pagination`: The pagination parameters
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn list_paginated(
|
||||
&mut self,
|
||||
user: &User,
|
||||
|
@ -19,14 +19,11 @@
|
||||
clippy::all,
|
||||
clippy::str_to_string,
|
||||
clippy::future_not_send,
|
||||
rustdoc::broken_intra_doc_links
|
||||
rustdoc::broken_intra_doc_links,
|
||||
missing_docs
|
||||
)]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions
|
||||
)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use rand_core::CryptoRngCore;
|
||||
|
||||
@ -103,5 +100,8 @@ macro_rules! repository_impl {
|
||||
};
|
||||
}
|
||||
|
||||
/// A boxed [`Clock`]
|
||||
pub type BoxClock = Box<dyn Clock + Send>;
|
||||
|
||||
/// A boxed random number generator
|
||||
pub type BoxRng = Box<dyn CryptoRngCore + Send>;
|
||||
|
@ -20,20 +20,57 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{repository_impl, Clock};
|
||||
|
||||
/// An [`OAuth2AccessTokenRepository`] helps interacting with [`AccessToken`]
|
||||
/// saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait OAuth2AccessTokenRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup an access token by its ID
|
||||
///
|
||||
/// Returns the access token if it exists, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the access token to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<AccessToken>, Self::Error>;
|
||||
|
||||
/// Find an access token by its token
|
||||
///
|
||||
/// Returns the access token if it exists, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `access_token`: The token of the access token to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find_by_token(
|
||||
&mut self,
|
||||
access_token: &str,
|
||||
) -> Result<Option<AccessToken>, Self::Error>;
|
||||
|
||||
/// Add a new access token to the database
|
||||
///
|
||||
/// Returns the newly created access token
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: A random number generator
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `session`: The session the access token is associated with
|
||||
/// * `access_token`: The access token to add
|
||||
/// * `expires_after`: The duration after which the access token expires
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -44,6 +81,17 @@ pub trait OAuth2AccessTokenRepository: Send + Sync {
|
||||
) -> Result<AccessToken, Self::Error>;
|
||||
|
||||
/// Revoke an access token
|
||||
///
|
||||
/// Returns the revoked access token
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `access_token`: The access token to revoke
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn revoke(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
@ -51,6 +99,16 @@ pub trait OAuth2AccessTokenRepository: Send + Sync {
|
||||
) -> Result<AccessToken, Self::Error>;
|
||||
|
||||
/// Cleanup expired access tokens
|
||||
///
|
||||
/// Returns the number of access tokens that were cleaned up
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to get the current time
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn cleanup_expired(&mut self, clock: &dyn Clock) -> Result<usize, Self::Error>;
|
||||
}
|
||||
|
||||
|
@ -23,10 +23,38 @@ use url::Url;
|
||||
|
||||
use crate::{repository_impl, Clock};
|
||||
|
||||
/// An [`OAuth2AuthorizationGrantRepository`] helps interacting with
|
||||
/// [`AuthorizationGrant`] saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait OAuth2AuthorizationGrantRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Create a new authorization grant
|
||||
///
|
||||
/// Returns the newly created authorization grant
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: A random number generator
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `client`: The client that requested the authorization grant
|
||||
/// * `redirect_uri`: The redirect URI the client requested
|
||||
/// * `scope`: The scope the client requested
|
||||
/// * `code`: The authorization code used by this grant, if the `code`
|
||||
/// `response_type` was requested
|
||||
/// * `state`: The state the client sent, if set
|
||||
/// * `nonce`: The nonce the client sent, if set
|
||||
/// * `max_age`: The maximum age since the user last authenticated, if asked
|
||||
/// by the client
|
||||
/// * `response_mode`: The response mode the client requested
|
||||
/// * `response_type_id_token`: Whether the `id_token` `response_type` was
|
||||
/// requested
|
||||
/// * `requires_consent`: Whether the client explicitly requested consent
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn add(
|
||||
&mut self,
|
||||
@ -44,11 +72,47 @@ pub trait OAuth2AuthorizationGrantRepository: Send + Sync {
|
||||
requires_consent: bool,
|
||||
) -> Result<AuthorizationGrant, Self::Error>;
|
||||
|
||||
/// Lookup an authorization grant by its ID
|
||||
///
|
||||
/// Returns the authorization grant if found, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the authorization grant to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<AuthorizationGrant>, Self::Error>;
|
||||
|
||||
/// Find an authorization grant by its code
|
||||
///
|
||||
/// Returns the authorization grant if found, `None` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `code`: The code of the authorization grant to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find_by_code(&mut self, code: &str)
|
||||
-> Result<Option<AuthorizationGrant>, Self::Error>;
|
||||
|
||||
/// Fulfill an authorization grant, by giving the [`Session`] that it
|
||||
/// created
|
||||
///
|
||||
/// Returns the updated authorization grant
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `session`: The session that was created using this authorization grant
|
||||
/// * `authorization_grant`: The authorization grant to fulfill
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn fulfill(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
@ -56,12 +120,35 @@ pub trait OAuth2AuthorizationGrantRepository: Send + Sync {
|
||||
authorization_grant: AuthorizationGrant,
|
||||
) -> Result<AuthorizationGrant, Self::Error>;
|
||||
|
||||
/// Mark an authorization grant as exchanged
|
||||
///
|
||||
/// Returns the updated authorization grant
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `authorization_grant`: The authorization grant to mark as exchanged
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn exchange(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
authorization_grant: AuthorizationGrant,
|
||||
) -> Result<AuthorizationGrant, Self::Error>;
|
||||
|
||||
/// Unset the `requires_consent` flag on an authorization grant
|
||||
///
|
||||
/// Returns the updated authorization grant
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `authorization_grant`: The authorization grant to update
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn give_consent(
|
||||
&mut self,
|
||||
authorization_grant: AuthorizationGrant,
|
||||
|
@ -25,22 +25,82 @@ use url::Url;
|
||||
|
||||
use crate::{repository_impl, Clock};
|
||||
|
||||
/// An [`OAuth2ClientRepository`] helps interacting with [`Client`] saved in the
|
||||
/// storage backend
|
||||
#[async_trait]
|
||||
pub trait OAuth2ClientRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup an OAuth2 client by its ID
|
||||
///
|
||||
/// Returns `None` if the client does not exist
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the client to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<Client>, Self::Error>;
|
||||
|
||||
/// Find an OAuth2 client by its client ID
|
||||
async fn find_by_client_id(&mut self, client_id: &str) -> Result<Option<Client>, Self::Error> {
|
||||
let Ok(id) = client_id.parse() else { return Ok(None) };
|
||||
self.lookup(id).await
|
||||
}
|
||||
|
||||
/// Load a batch of OAuth2 clients by their IDs
|
||||
///
|
||||
/// Returns a map of client IDs to clients. If a client does not exist, it
|
||||
/// is not present in the map.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `ids`: The IDs of the clients to load
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn load_batch(
|
||||
&mut self,
|
||||
ids: BTreeSet<Ulid>,
|
||||
) -> Result<BTreeMap<Ulid, Client>, Self::Error>;
|
||||
|
||||
/// Add a new OAuth2 client
|
||||
///
|
||||
/// Returns the client that was added
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `redirect_uris`: The list of redirect URIs used by this client
|
||||
/// * `encrypted_client_secret`: The encrypted client secret, if any
|
||||
/// * `grant_types`: The list of grant types this client can use
|
||||
/// * `contacts`: The list of contacts for this client
|
||||
/// * `client_name`: The human-readable name of this client, if given
|
||||
/// * `logo_uri`: The URI of the logo of this client, if given
|
||||
/// * `client_uri`: The URI of a website of this client, if given
|
||||
/// * `policy_uri`: The URI of the privacy policy of this client, if given
|
||||
/// * `tos_uri`: The URI of the terms of service of this client, if given
|
||||
/// * `jwks_uri`: The URI of the JWKS of this client, if given
|
||||
/// * `jwks`: The JWKS of this client, if given
|
||||
/// * `id_token_signed_response_alg`: The algorithm used to sign the ID
|
||||
/// token
|
||||
/// * `userinfo_signed_response_alg`: The algorithm used to sign the user
|
||||
/// info. If none, the user info endpoint will not sign the response
|
||||
/// * `token_endpoint_auth_method`: The authentication method used by this
|
||||
/// client when calling the token endpoint
|
||||
/// * `token_endpoint_auth_signing_alg`: The algorithm used to sign the JWT
|
||||
/// when using the `client_secret_jwt` or `private_key_jwt` authentication
|
||||
/// methods
|
||||
/// * `initiate_login_uri`: The URI used to initiate a login, if given
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn add(
|
||||
&mut self,
|
||||
@ -64,6 +124,24 @@ pub trait OAuth2ClientRepository: Send + Sync {
|
||||
initiate_login_uri: Option<Url>,
|
||||
) -> Result<Client, Self::Error>;
|
||||
|
||||
/// Add or replace a client from the configuration
|
||||
///
|
||||
/// Returns the client that was added or replaced
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `client_id`: The client ID
|
||||
/// * `client_auth_method`: The authentication method this client uses
|
||||
/// * `encrypted_client_secret`: The encrypted client secret, if any
|
||||
/// * `jwks`: The client JWKS, if any
|
||||
/// * `jwks_uri`: The client JWKS URI, if any
|
||||
/// * `redirect_uris`: The list of redirect URIs used by this client
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn add_from_config(
|
||||
&mut self,
|
||||
@ -77,12 +155,36 @@ pub trait OAuth2ClientRepository: Send + Sync {
|
||||
redirect_uris: Vec<Url>,
|
||||
) -> Result<Client, Self::Error>;
|
||||
|
||||
/// Get the list of scopes that the user has given consent for the given
|
||||
/// client
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `client`: The client to get the consent for
|
||||
/// * `user`: The user to get the consent for
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn get_consent_for_user(
|
||||
&mut self,
|
||||
client: &Client,
|
||||
user: &User,
|
||||
) -> Result<Scope, Self::Error>;
|
||||
|
||||
/// Give consent for a set of scopes for the given client and user
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `client`: The client to give the consent for
|
||||
/// * `user`: The user to give the consent for
|
||||
/// * `scope`: The scope to give consent for
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn give_consent_for_user(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
|
@ -12,8 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Repositories to interact with entities related to the OAuth 2.0 protocol
|
||||
|
||||
mod access_token;
|
||||
pub mod authorization_grant;
|
||||
mod authorization_grant;
|
||||
mod client;
|
||||
mod refresh_token;
|
||||
mod session;
|
||||
|
@ -19,20 +19,58 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{repository_impl, Clock};
|
||||
|
||||
/// An [`OAuth2RefreshTokenRepository`] helps interacting with [`RefreshToken`]
|
||||
/// saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait OAuth2RefreshTokenRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup a refresh token by its ID
|
||||
///
|
||||
/// Returns `None` if no [`RefreshToken`] was found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the [`RefreshToken`] to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<RefreshToken>, Self::Error>;
|
||||
|
||||
/// Find a refresh token by its token
|
||||
///
|
||||
/// Returns `None` if no [`RefreshToken`] was found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `token`: The token of the [`RefreshToken`] to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find_by_token(
|
||||
&mut self,
|
||||
refresh_token: &str,
|
||||
) -> Result<Option<RefreshToken>, Self::Error>;
|
||||
|
||||
/// Add a new refresh token to the database
|
||||
///
|
||||
/// Returns the newly created [`RefreshToken`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `session`: The [`Session`] in which to create the [`RefreshToken`]
|
||||
/// * `access_token`: The [`AccessToken`] created alongside this
|
||||
/// [`RefreshToken`]
|
||||
/// * `refresh_token`: The refresh token to store
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -43,6 +81,18 @@ pub trait OAuth2RefreshTokenRepository: Send + Sync {
|
||||
) -> Result<RefreshToken, Self::Error>;
|
||||
|
||||
/// Consume a refresh token
|
||||
///
|
||||
/// Returns the updated [`RefreshToken`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `refresh_token`: The [`RefreshToken`] to consume
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails, or if the
|
||||
/// token was already consumed
|
||||
async fn consume(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
|
@ -19,12 +19,41 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{pagination::Page, repository_impl, Clock, Pagination};
|
||||
|
||||
/// An [`OAuth2SessionRepository`] helps interacting with [`Session`]
|
||||
/// saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait OAuth2SessionRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup an [`Session`] by its ID
|
||||
///
|
||||
/// Returns `None` if no [`Session`] was found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the [`Session`] to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<Session>, Self::Error>;
|
||||
|
||||
/// Create a new [`Session`] from an [`AuthorizationGrant`]
|
||||
///
|
||||
/// Returns the newly created [`Session`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `grant`: The [`AuthorizationGrant`] to create the [`Session`] from
|
||||
/// * `user_session`: The [`BrowserSession`] of the user which completed the
|
||||
/// authorization
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn create_from_grant(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -33,9 +62,31 @@ pub trait OAuth2SessionRepository: Send + Sync {
|
||||
user_session: &BrowserSession,
|
||||
) -> Result<Session, Self::Error>;
|
||||
|
||||
/// Mark a [`Session`] as finished
|
||||
///
|
||||
/// Returns the updated [`Session`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `session`: The [`Session`] to mark as finished
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn finish(&mut self, clock: &dyn Clock, session: Session)
|
||||
-> Result<Session, Self::Error>;
|
||||
|
||||
/// Get a paginated list of [`Session`]s for a [`User`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The [`User`] to get the [`Session`]s for
|
||||
/// * `pagination`: The pagination parameters
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn list_paginated(
|
||||
&mut self,
|
||||
user: &User,
|
||||
|
@ -22,17 +22,29 @@ use ulid::Ulid;
|
||||
#[error("Either 'first' or 'last' must be specified")]
|
||||
pub struct InvalidPagination;
|
||||
|
||||
/// Pagination parameters
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Pagination {
|
||||
/// The cursor to start from
|
||||
pub before: Option<Ulid>,
|
||||
|
||||
/// The cursor to end at
|
||||
pub after: Option<Ulid>,
|
||||
|
||||
/// The maximum number of items to return
|
||||
pub count: usize,
|
||||
|
||||
/// In which direction to paginate
|
||||
pub direction: PaginationDirection,
|
||||
}
|
||||
|
||||
/// The direction to paginate
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PaginationDirection {
|
||||
/// Paginate forward
|
||||
Forward,
|
||||
|
||||
/// Paginate backward
|
||||
Backward,
|
||||
}
|
||||
|
||||
@ -124,13 +136,24 @@ impl Pagination {
|
||||
}
|
||||
}
|
||||
|
||||
/// A page of results returned by a paginated query
|
||||
pub struct Page<T> {
|
||||
/// When paginating forwards, this is true if there are more items after
|
||||
pub has_next_page: bool,
|
||||
|
||||
/// When paginating backwards, this is true if there are more items before
|
||||
pub has_previous_page: bool,
|
||||
|
||||
/// The items in the page
|
||||
pub edges: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Page<T> {
|
||||
/// Map the items in this page with the given function
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `f`: The function to map the items with
|
||||
#[must_use]
|
||||
pub fn map<F, T2>(self, f: F) -> Page<T2>
|
||||
where
|
||||
@ -144,6 +167,15 @@ impl<T> Page<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to map the items in this page with the given fallible function
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `f`: The fallible function to map the items with
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns the first error encountered while mapping the items
|
||||
pub fn try_map<F, E, T2>(self, f: F) -> Result<Page<T2>, E>
|
||||
where
|
||||
F: FnMut(T) -> Result<T2, E>,
|
||||
|
@ -19,14 +19,39 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{pagination::Page, repository_impl, Clock, Pagination};
|
||||
|
||||
/// An [`UpstreamOAuthLinkRepository`] helps interacting with
|
||||
/// [`UpstreamOAuthLink`] with the storage backend
|
||||
#[async_trait]
|
||||
pub trait UpstreamOAuthLinkRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup an upstream OAuth link by its ID
|
||||
///
|
||||
/// Returns `None` if the link does not exist
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the upstream OAuth link to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<UpstreamOAuthLink>, Self::Error>;
|
||||
|
||||
/// Find an upstream OAuth link for a provider by its subject
|
||||
///
|
||||
/// Returns `None` if no matching upstream OAuth link was found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `upstream_oauth_provider`: The upstream OAuth provider on which to
|
||||
/// find the link
|
||||
/// * `subject`: The subject of the upstream OAuth link to find
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find_by_subject(
|
||||
&mut self,
|
||||
upstream_oauth_provider: &UpstreamOAuthProvider,
|
||||
@ -34,6 +59,20 @@ pub trait UpstreamOAuthLinkRepository: Send + Sync {
|
||||
) -> Result<Option<UpstreamOAuthLink>, Self::Error>;
|
||||
|
||||
/// Add a new upstream OAuth link
|
||||
///
|
||||
/// Returns the newly created upstream OAuth link
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `upsream_oauth_provider`: The upstream OAuth provider for which to
|
||||
/// create the link
|
||||
/// * `subject`: The subject of the upstream OAuth link to create
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -43,6 +82,17 @@ pub trait UpstreamOAuthLinkRepository: Send + Sync {
|
||||
) -> Result<UpstreamOAuthLink, Self::Error>;
|
||||
|
||||
/// Associate an upstream OAuth link to a user
|
||||
///
|
||||
/// Returns the updated upstream OAuth link
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `upstream_oauth_link`: The upstream OAuth link to update
|
||||
/// * `user`: The user to associate to the upstream OAuth link
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn associate_to_user(
|
||||
&mut self,
|
||||
upstream_oauth_link: &UpstreamOAuthLink,
|
||||
@ -50,6 +100,15 @@ pub trait UpstreamOAuthLinkRepository: Send + Sync {
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Get a paginated list of upstream OAuth links on a user
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The user for which to get the upstream OAuth links
|
||||
/// * `pagination`: The pagination parameters
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn list_paginated(
|
||||
&mut self,
|
||||
user: &User,
|
||||
|
@ -12,6 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Repositories to interact with entities related to the upstream OAuth 2.0
|
||||
//! providers
|
||||
|
||||
mod link;
|
||||
mod provider;
|
||||
mod session;
|
||||
|
@ -21,14 +21,47 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{pagination::Page, repository_impl, Clock, Pagination};
|
||||
|
||||
/// An [`UpstreamOAuthProviderRepository`] helps interacting with
|
||||
/// [`UpstreamOAuthProvider`] saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait UpstreamOAuthProviderRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup an upstream OAuth provider by its ID
|
||||
///
|
||||
/// Returns `None` if the provider was not found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the provider to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<UpstreamOAuthProvider>, Self::Error>;
|
||||
|
||||
/// Add a new upstream OAuth provider
|
||||
///
|
||||
/// Returns the newly created provider
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: A random number generator
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `issuer`: The OIDC issuer of the provider
|
||||
/// * `scope`: The scope to request during the authorization flow
|
||||
/// * `token_endpoint_auth_method`: The token endpoint authentication method
|
||||
/// * `token_endpoint_auth_signing_alg`: The JWT signing algorithm to use
|
||||
/// when then `client_secret_jwt` or `private_key_jwt` authentication
|
||||
/// methods are used
|
||||
/// * `client_id`: The client ID to use when authenticating to the upstream
|
||||
/// * `encrypted_client_secret`: The encrypted client secret to use when
|
||||
/// authenticating to the upstream
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn add(
|
||||
&mut self,
|
||||
@ -43,12 +76,24 @@ pub trait UpstreamOAuthProviderRepository: Send + Sync {
|
||||
) -> Result<UpstreamOAuthProvider, Self::Error>;
|
||||
|
||||
/// Get a paginated list of upstream OAuth providers
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `pagination`: The pagination parameters
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn list_paginated(
|
||||
&mut self,
|
||||
pagination: Pagination,
|
||||
) -> Result<Page<UpstreamOAuthProvider>, Self::Error>;
|
||||
|
||||
/// Get all upstream OAuth providers
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn all(&mut self) -> Result<Vec<UpstreamOAuthProvider>, Self::Error>;
|
||||
}
|
||||
|
||||
|
@ -19,17 +19,48 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{repository_impl, Clock};
|
||||
|
||||
/// An [`UpstreamOAuthSessionRepository`] helps interacting with
|
||||
/// [`UpstreamOAuthAuthorizationSession`] saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait UpstreamOAuthSessionRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup a session by its ID
|
||||
///
|
||||
/// Returns `None` if the session does not exist
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: the ID of the session to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(
|
||||
&mut self,
|
||||
id: Ulid,
|
||||
) -> Result<Option<UpstreamOAuthAuthorizationSession>, Self::Error>;
|
||||
|
||||
/// Add a session to the database
|
||||
///
|
||||
/// Returns the newly created session
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: the random number generator to use
|
||||
/// * `clock`: the clock source
|
||||
/// * `upstream_oauth_provider`: the upstream OAuth provider for which to
|
||||
/// create the session
|
||||
/// * `state`: the authorization grant `state` parameter sent to the
|
||||
/// upstream OAuth provider
|
||||
/// * `code_challenge_verifier`: the code challenge verifier used in this
|
||||
/// session, if PKCE is being used
|
||||
/// * `nonce`: the `nonce` used in this session
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -41,6 +72,20 @@ pub trait UpstreamOAuthSessionRepository: Send + Sync {
|
||||
) -> Result<UpstreamOAuthAuthorizationSession, Self::Error>;
|
||||
|
||||
/// Mark a session as completed and associate the given link
|
||||
///
|
||||
/// Returns the updated session
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: the clock source
|
||||
/// * `upstream_oauth_authorization_session`: the session to update
|
||||
/// * `upstream_oauth_link`: the link to associate with the session
|
||||
/// * `id_token`: the ID token returned by the upstream OAuth provider, if
|
||||
/// present
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn complete_with_link(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
@ -50,6 +95,17 @@ pub trait UpstreamOAuthSessionRepository: Send + Sync {
|
||||
) -> Result<UpstreamOAuthAuthorizationSession, Self::Error>;
|
||||
|
||||
/// Mark a session as consumed
|
||||
///
|
||||
/// Returns the updated session
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: the clock source
|
||||
/// * `upstream_oauth_authorization_session`: the session to consume
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn consume(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
|
@ -19,22 +19,105 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{pagination::Page, repository_impl, Clock, Pagination};
|
||||
|
||||
/// A [`UserEmailRepository`] helps interacting with [`UserEmail`] saved in the
|
||||
/// storage backend
|
||||
#[async_trait]
|
||||
pub trait UserEmailRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup an [`UserEmail`] by its ID
|
||||
///
|
||||
/// Returns `None` if no [`UserEmail`] was found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the [`UserEmail`] to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<UserEmail>, Self::Error>;
|
||||
|
||||
/// Lookup an [`UserEmail`] by its email address for a [`User`]
|
||||
///
|
||||
/// Returns `None` if no matching [`UserEmail`] was found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The [`User`] for whom to lookup the [`UserEmail`]
|
||||
/// * `email`: The email address to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find(&mut self, user: &User, email: &str) -> Result<Option<UserEmail>, Self::Error>;
|
||||
|
||||
/// Get the primary [`UserEmail`] of a [`User`]
|
||||
///
|
||||
/// Returns `None` if no the user has no primary [`UserEmail`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The [`User`] for whom to lookup the primary [`UserEmail`]
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn get_primary(&mut self, user: &User) -> Result<Option<UserEmail>, Self::Error>;
|
||||
|
||||
/// Get all [`UserEmail`] of a [`User`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The [`User`] for whom to lookup the [`UserEmail`]
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn all(&mut self, user: &User) -> Result<Vec<UserEmail>, Self::Error>;
|
||||
|
||||
/// List [`UserEmail`] of a [`User`] with the given pagination
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The [`User`] for whom to lookup the [`UserEmail`]
|
||||
/// * `pagination`: The pagination parameters
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn list_paginated(
|
||||
&mut self,
|
||||
user: &User,
|
||||
pagination: Pagination,
|
||||
) -> Result<Page<UserEmail>, Self::Error>;
|
||||
|
||||
/// Count the [`UserEmail`] of a [`User`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The [`User`] for whom to count the [`UserEmail`]
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn count(&mut self, user: &User) -> Result<usize, Self::Error>;
|
||||
|
||||
/// Create a new [`UserEmail`] for a [`User`]
|
||||
///
|
||||
/// Returns the newly created [`UserEmail`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock to use
|
||||
/// * `user`: The [`User`] for whom to create the [`UserEmail`]
|
||||
/// * `email`: The email address of the [`UserEmail`]
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -42,16 +125,61 @@ pub trait UserEmailRepository: Send + Sync {
|
||||
user: &User,
|
||||
email: String,
|
||||
) -> Result<UserEmail, Self::Error>;
|
||||
|
||||
/// Delete a [`UserEmail`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user_email`: The [`UserEmail`] to delete
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn remove(&mut self, user_email: UserEmail) -> Result<(), Self::Error>;
|
||||
|
||||
/// Mark a [`UserEmail`] as verified
|
||||
///
|
||||
/// Returns the updated [`UserEmail`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock to use
|
||||
/// * `user_email`: The [`UserEmail`] to mark as verified
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn mark_as_verified(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
user_email: UserEmail,
|
||||
) -> Result<UserEmail, Self::Error>;
|
||||
|
||||
/// Mark a [`UserEmail`] as primary
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user_email`: The [`UserEmail`] to mark as primary
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn set_as_primary(&mut self, user_email: &UserEmail) -> Result<(), Self::Error>;
|
||||
|
||||
/// Add a [`UserEmailVerification`] for a [`UserEmail`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock to use
|
||||
/// * `user_email`: The [`UserEmail`] for which to add the
|
||||
/// [`UserEmailVerification`]
|
||||
/// * `max_age`: The duration for which the [`UserEmailVerification`] is
|
||||
/// valid
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add_verification_code(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -61,6 +189,20 @@ pub trait UserEmailRepository: Send + Sync {
|
||||
code: String,
|
||||
) -> Result<UserEmailVerification, Self::Error>;
|
||||
|
||||
/// Find a [`UserEmailVerification`] for a [`UserEmail`] by its code
|
||||
///
|
||||
/// Returns `None` if no matching [`UserEmailVerification`] was found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock to use
|
||||
/// * `user_email`: The [`UserEmail`] for which to lookup the
|
||||
/// [`UserEmailVerification`]
|
||||
/// * `code`: The code used to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find_verification_code(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
@ -68,6 +210,18 @@ pub trait UserEmailRepository: Send + Sync {
|
||||
code: &str,
|
||||
) -> Result<Option<UserEmailVerification>, Self::Error>;
|
||||
|
||||
/// Consume a [`UserEmailVerification`]
|
||||
///
|
||||
/// Returns the consumed [`UserEmailVerification`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock to use
|
||||
/// * `verification`: The [`UserEmailVerification`] to consume
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn consume_verification_code(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
|
@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Repositories to interact with entities related to user accounts
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mas_data_model::User;
|
||||
use rand_core::RngCore;
|
||||
@ -27,18 +29,70 @@ pub use self::{
|
||||
email::UserEmailRepository, password::UserPasswordRepository, session::BrowserSessionRepository,
|
||||
};
|
||||
|
||||
/// A [`UserRepository`] helps interacting with [`User`] saved in the storage
|
||||
/// backend
|
||||
#[async_trait]
|
||||
pub trait UserRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup a [`User`] by its ID
|
||||
///
|
||||
/// Returns `None` if no [`User`] was found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the [`User`] to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<User>, Self::Error>;
|
||||
|
||||
/// Find a [`User`] by its username
|
||||
///
|
||||
/// Returns `None` if no [`User`] was found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `username`: The username of the [`User`] to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn find_by_username(&mut self, username: &str) -> Result<Option<User>, Self::Error>;
|
||||
|
||||
/// Create a new [`User`]
|
||||
///
|
||||
/// Returns the newly created [`User`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: A random number generator to generate the [`User`] ID
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `username`: The username of the [`User`]
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
clock: &dyn Clock,
|
||||
username: String,
|
||||
) -> Result<User, Self::Error>;
|
||||
|
||||
/// Check if a [`User`] exists
|
||||
///
|
||||
/// Returns `true` if the [`User`] exists, `false` otherwise
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `username`: The username of the [`User`] to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
|
||||
}
|
||||
|
||||
|
@ -18,11 +18,42 @@ use rand_core::RngCore;
|
||||
|
||||
use crate::{repository_impl, Clock};
|
||||
|
||||
/// A [`UserPasswordRepository`] helps interacting with [`Password`] saved in
|
||||
/// the storage backend
|
||||
#[async_trait]
|
||||
pub trait UserPasswordRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Get the active password for a user
|
||||
///
|
||||
/// Returns `None` if the user has no password set
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The user to get the password for
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if underlying repository fails
|
||||
async fn active(&mut self, user: &User) -> Result<Option<Password>, Self::Error>;
|
||||
|
||||
/// Set a new password for a user
|
||||
///
|
||||
/// Returns the newly created [`Password`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `user`: The user to set the password for
|
||||
/// * `version`: The version of the hashing scheme used
|
||||
/// * `hashed_password`: The hashed password
|
||||
/// * `upgraded_from`: The password this password was upgraded from, if any
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
|
@ -19,29 +19,105 @@ use ulid::Ulid;
|
||||
|
||||
use crate::{pagination::Page, repository_impl, Clock, Pagination};
|
||||
|
||||
/// A [`BrowserSessionRepository`] helps interacting with [`BrowserSession`]
|
||||
/// saved in the storage backend
|
||||
#[async_trait]
|
||||
pub trait BrowserSessionRepository: Send + Sync {
|
||||
/// The error type returned by the repository
|
||||
type Error;
|
||||
|
||||
/// Lookup a [`BrowserSession`] by its ID
|
||||
///
|
||||
/// Returns `None` if the session is not found
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `id`: The ID of the session to lookup
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn lookup(&mut self, id: Ulid) -> Result<Option<BrowserSession>, Self::Error>;
|
||||
|
||||
/// Create a new [`BrowserSession`] for a [`User`]
|
||||
///
|
||||
/// Returns the newly created [`BrowserSession`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `user`: The user to create the session for
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn add(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
clock: &dyn Clock,
|
||||
user: &User,
|
||||
) -> Result<BrowserSession, Self::Error>;
|
||||
|
||||
/// Finish a [`BrowserSession`]
|
||||
///
|
||||
/// Returns the finished session
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `user_session`: The session to finish
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn finish(
|
||||
&mut self,
|
||||
clock: &dyn Clock,
|
||||
user_session: BrowserSession,
|
||||
) -> Result<BrowserSession, Self::Error>;
|
||||
|
||||
/// List active [`BrowserSession`] for a [`User`] with the given pagination
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The user to list the sessions for
|
||||
/// * `pagination`: The pagination parameters
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn list_active_paginated(
|
||||
&mut self,
|
||||
user: &User,
|
||||
pagination: Pagination,
|
||||
) -> Result<Page<BrowserSession>, Self::Error>;
|
||||
|
||||
/// Count active [`BrowserSession`] for a [`User`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The user to count the sessions for
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn count_active(&mut self, user: &User) -> Result<usize, Self::Error>;
|
||||
|
||||
/// Authenticate a [`BrowserSession`] with the given [`Password`]
|
||||
///
|
||||
/// Returns the updated [`BrowserSession`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `user_session`: The session to authenticate
|
||||
/// * `user_password`: The password which was used to authenticate
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn authenticate_with_password(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
@ -50,6 +126,21 @@ pub trait BrowserSessionRepository: Send + Sync {
|
||||
user_password: &Password,
|
||||
) -> Result<BrowserSession, Self::Error>;
|
||||
|
||||
/// Authenticate a [`BrowserSession`] with the given [`UpstreamOAuthLink`]
|
||||
///
|
||||
/// Returns the updated [`BrowserSession`]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `rng`: The random number generator to use
|
||||
/// * `clock`: The clock used to generate timestamps
|
||||
/// * `user_session`: The session to authenticate
|
||||
/// * `upstream_oauth_link`: The upstream OAuth link which was used to
|
||||
/// authenticate
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn authenticate_with_upstream(
|
||||
&mut self,
|
||||
rng: &mut (dyn RngCore + Send),
|
||||
|
Reference in New Issue
Block a user