From 90dbc5d6ffcc19968430cef1202b4e51a98225f2 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 25 Jan 2023 16:09:36 +0100 Subject: [PATCH] storage: document all the repository traits and methods --- crates/storage-pg/src/compat/access_token.rs | 4 + crates/storage-pg/src/compat/mod.rs | 3 + crates/storage-pg/src/compat/refresh_token.rs | 4 + crates/storage-pg/src/compat/session.rs | 3 + crates/storage-pg/src/compat/sso_login.rs | 4 + crates/storage-pg/src/lib.rs | 48 ++++-- crates/storage-pg/src/oauth2/access_token.rs | 4 + .../src/oauth2/authorization_grant.rs | 4 + crates/storage-pg/src/oauth2/client.rs | 3 + crates/storage-pg/src/oauth2/mod.rs | 5 +- crates/storage-pg/src/oauth2/refresh_token.rs | 4 + crates/storage-pg/src/oauth2/session.rs | 3 + crates/storage-pg/src/repository.rs | 8 + crates/storage-pg/src/tracing.rs | 2 + crates/storage-pg/src/upstream_oauth2/link.rs | 4 + crates/storage-pg/src/upstream_oauth2/mod.rs | 5 + .../src/upstream_oauth2/provider.rs | 4 + .../storage-pg/src/upstream_oauth2/session.rs | 4 + crates/storage-pg/src/user/email.rs | 3 + crates/storage-pg/src/user/mod.rs | 5 + crates/storage-pg/src/user/password.rs | 3 + crates/storage-pg/src/user/session.rs | 4 + crates/storage-pg/src/user/tests.rs | 1 + crates/storage/src/compat/access_token.rs | 45 +++++ crates/storage/src/compat/mod.rs | 2 + crates/storage/src/compat/refresh_token.rs | 46 ++++++ crates/storage/src/compat/session.rs | 37 +++++ crates/storage/src/compat/sso_login.rs | 69 ++++++++ crates/storage/src/lib.rs | 12 +- crates/storage/src/oauth2/access_token.rs | 58 +++++++ .../storage/src/oauth2/authorization_grant.rs | 87 ++++++++++ crates/storage/src/oauth2/client.rs | 102 ++++++++++++ crates/storage/src/oauth2/mod.rs | 4 +- crates/storage/src/oauth2/refresh_token.rs | 50 ++++++ crates/storage/src/oauth2/session.rs | 51 ++++++ crates/storage/src/pagination.rs | 32 ++++ crates/storage/src/upstream_oauth2/link.rs | 59 +++++++ crates/storage/src/upstream_oauth2/mod.rs | 3 + .../storage/src/upstream_oauth2/provider.rs | 45 +++++ crates/storage/src/upstream_oauth2/session.rs | 56 +++++++ crates/storage/src/user/email.rs | 154 ++++++++++++++++++ crates/storage/src/user/mod.rs | 54 ++++++ crates/storage/src/user/password.rs | 31 ++++ crates/storage/src/user/session.rs | 91 +++++++++++ 44 files changed, 1202 insertions(+), 18 deletions(-) diff --git a/crates/storage-pg/src/compat/access_token.rs b/crates/storage-pg/src/compat/access_token.rs index 822c3a8a..70fabac7 100644 --- a/crates/storage-pg/src/compat/access_token.rs +++ b/crates/storage-pg/src/compat/access_token.rs @@ -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 } } diff --git a/crates/storage-pg/src/compat/mod.rs b/crates/storage-pg/src/compat/mod.rs index 5d99f332..ae2a40b2 100644 --- a/crates/storage-pg/src/compat/mod.rs +++ b/crates/storage-pg/src/compat/mod.rs @@ -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; diff --git a/crates/storage-pg/src/compat/refresh_token.rs b/crates/storage-pg/src/compat/refresh_token.rs index 991e1438..0811119a 100644 --- a/crates/storage-pg/src/compat/refresh_token.rs +++ b/crates/storage-pg/src/compat/refresh_token.rs @@ -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 } } diff --git a/crates/storage-pg/src/compat/session.rs b/crates/storage-pg/src/compat/session.rs index 16208b4f..283a9a59 100644 --- a/crates/storage-pg/src/compat/session.rs +++ b/crates/storage-pg/src/compat/session.rs @@ -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 } } diff --git a/crates/storage-pg/src/compat/sso_login.rs b/crates/storage-pg/src/compat/sso_login.rs index 1b8e0225..ae9ca083 100644 --- a/crates/storage-pg/src/compat/sso_login.rs +++ b/crates/storage-pg/src/compat/sso_login.rs @@ -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 } } diff --git a/crates/storage-pg/src/lib.rs b/crates/storage-pg/src/lib.rs index 08c89db2..046cc4b8 100644 --- a/crates/storage-pg/src/lib.rs +++ b/crates/storage-pg/src/lib.rs @@ -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 LookupResultExt for Result { #[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>, }, @@ -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, + /// The source of the error #[source] source: Option>, } @@ -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( 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; diff --git a/crates/storage-pg/src/oauth2/access_token.rs b/crates/storage-pg/src/oauth2/access_token.rs index ecd5798b..e809fa53 100644 --- a/crates/storage-pg/src/oauth2/access_token.rs +++ b/crates/storage-pg/src/oauth2/access_token.rs @@ -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 } } diff --git a/crates/storage-pg/src/oauth2/authorization_grant.rs b/crates/storage-pg/src/oauth2/authorization_grant.rs index 92116a62..f62edae3 100644 --- a/crates/storage-pg/src/oauth2/authorization_grant.rs +++ b/crates/storage-pg/src/oauth2/authorization_grant.rs @@ -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 } } diff --git a/crates/storage-pg/src/oauth2/client.rs b/crates/storage-pg/src/oauth2/client.rs index e17245aa..cc2ed8b8 100644 --- a/crates/storage-pg/src/oauth2/client.rs +++ b/crates/storage-pg/src/oauth2/client.rs @@ -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 } } diff --git a/crates/storage-pg/src/oauth2/mod.rs b/crates/storage-pg/src/oauth2/mod.rs index edad2beb..3e496141 100644 --- a/crates/storage-pg/src/oauth2/mod.rs +++ b/crates/storage-pg/src/oauth2/mod.rs @@ -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; diff --git a/crates/storage-pg/src/oauth2/refresh_token.rs b/crates/storage-pg/src/oauth2/refresh_token.rs index ba2fa533..ae723f7c 100644 --- a/crates/storage-pg/src/oauth2/refresh_token.rs +++ b/crates/storage-pg/src/oauth2/refresh_token.rs @@ -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 } } diff --git a/crates/storage-pg/src/oauth2/session.rs b/crates/storage-pg/src/oauth2/session.rs index 891c2278..aa667f25 100644 --- a/crates/storage-pg/src/oauth2/session.rs +++ b/crates/storage-pg/src/oauth2/session.rs @@ -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 } } diff --git a/crates/storage-pg/src/repository.rs b/crates/storage-pg/src/repository.rs index 0f1fdfb4..da81d3af 100644 --- a/crates/storage-pg/src/repository.rs +++ b/crates/storage-pg/src/repository.rs @@ -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 { let txn = pool.begin().await?; Ok(PgRepository { txn }) diff --git a/crates/storage-pg/src/tracing.rs b/crates/storage-pg/src/tracing.rs index 1210816c..b0bc0b7f 100644 --- a/crates/storage-pg/src/tracing.rs +++ b/crates/storage-pg/src/tracing.rs @@ -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 { diff --git a/crates/storage-pg/src/upstream_oauth2/link.rs b/crates/storage-pg/src/upstream_oauth2/link.rs index c38b344b..0e14f3fd 100644 --- a/crates/storage-pg/src/upstream_oauth2/link.rs +++ b/crates/storage-pg/src/upstream_oauth2/link.rs @@ -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 } } diff --git a/crates/storage-pg/src/upstream_oauth2/mod.rs b/crates/storage-pg/src/upstream_oauth2/mod.rs index 9ff7699e..5bf97514 100644 --- a/crates/storage-pg/src/upstream_oauth2/mod.rs +++ b/crates/storage-pg/src/upstream_oauth2/mod.rs @@ -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/"; diff --git a/crates/storage-pg/src/upstream_oauth2/provider.rs b/crates/storage-pg/src/upstream_oauth2/provider.rs index dc1b0c85..d4ecbe47 100644 --- a/crates/storage-pg/src/upstream_oauth2/provider.rs +++ b/crates/storage-pg/src/upstream_oauth2/provider.rs @@ -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 } } diff --git a/crates/storage-pg/src/upstream_oauth2/session.rs b/crates/storage-pg/src/upstream_oauth2/session.rs index 3cdef0c7..5780ab8d 100644 --- a/crates/storage-pg/src/upstream_oauth2/session.rs +++ b/crates/storage-pg/src/upstream_oauth2/session.rs @@ -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 } } diff --git a/crates/storage-pg/src/user/email.rs b/crates/storage-pg/src/user/email.rs index b9d732de..28b9d395 100644 --- a/crates/storage-pg/src/user/email.rs +++ b/crates/storage-pg/src/user/email.rs @@ -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 } } diff --git a/crates/storage-pg/src/user/mod.rs b/crates/storage-pg/src/user/mod.rs index 8ec6170f..0554c8b2 100644 --- a/crates/storage-pg/src/user/mod.rs +++ b/crates/storage-pg/src/user/mod.rs @@ -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 } } diff --git a/crates/storage-pg/src/user/password.rs b/crates/storage-pg/src/user/password.rs index 696b30e7..1dfd90d1 100644 --- a/crates/storage-pg/src/user/password.rs +++ b/crates/storage-pg/src/user/password.rs @@ -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 } } diff --git a/crates/storage-pg/src/user/session.rs b/crates/storage-pg/src/user/session.rs index e5616fff..ff91726e 100644 --- a/crates/storage-pg/src/user/session.rs +++ b/crates/storage-pg/src/user/session.rs @@ -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 } } diff --git a/crates/storage-pg/src/user/tests.rs b/crates/storage-pg/src/user/tests.rs index 29f828ab..29ebe19f 100644 --- a/crates/storage-pg/src/user/tests.rs +++ b/crates/storage-pg/src/user/tests.rs @@ -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"; diff --git a/crates/storage/src/compat/access_token.rs b/crates/storage/src/compat/access_token.rs index c6d4eb7f..c6d3979e 100644 --- a/crates/storage/src/compat/access_token.rs +++ b/crates/storage/src/compat/access_token.rs @@ -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, 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, 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; /// 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, diff --git a/crates/storage/src/compat/mod.rs b/crates/storage/src/compat/mod.rs index 634c04a7..eb971edd 100644 --- a/crates/storage/src/compat/mod.rs +++ b/crates/storage/src/compat/mod.rs @@ -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; diff --git a/crates/storage/src/compat/refresh_token.rs b/crates/storage/src/compat/refresh_token.rs index 3fd916da..c9b3aabe 100644 --- a/crates/storage/src/compat/refresh_token.rs +++ b/crates/storage/src/compat/refresh_token.rs @@ -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, 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, 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; /// 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, diff --git a/crates/storage/src/compat/session.rs b/crates/storage/src/compat/session.rs index f867a332..fb9dea73 100644 --- a/crates/storage/src/compat/session.rs +++ b/crates/storage/src/compat/session.rs @@ -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, 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; /// 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, diff --git a/crates/storage/src/compat/sso_login.rs b/crates/storage/src/compat/sso_login.rs index a6fa0735..7c823d62 100644 --- a/crates/storage/src/compat/sso_login.rs +++ b/crates/storage/src/compat/sso_login.rs @@ -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, 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, 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; /// 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; /// 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; /// 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, diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 69bc2881..cffec045 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -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; + +/// A boxed random number generator pub type BoxRng = Box; diff --git a/crates/storage/src/oauth2/access_token.rs b/crates/storage/src/oauth2/access_token.rs index 8a536243..3fba2399 100644 --- a/crates/storage/src/oauth2/access_token.rs +++ b/crates/storage/src/oauth2/access_token.rs @@ -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, 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, 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; /// 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; /// 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; } diff --git a/crates/storage/src/oauth2/authorization_grant.rs b/crates/storage/src/oauth2/authorization_grant.rs index 8852f796..623ea596 100644 --- a/crates/storage/src/oauth2/authorization_grant.rs +++ b/crates/storage/src/oauth2/authorization_grant.rs @@ -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; + /// 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, 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, 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; + /// 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; + /// 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, diff --git a/crates/storage/src/oauth2/client.rs b/crates/storage/src/oauth2/client.rs index 98acaaf7..18f0108b 100644 --- a/crates/storage/src/oauth2/client.rs +++ b/crates/storage/src/oauth2/client.rs @@ -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, Self::Error>; + /// Find an OAuth2 client by its client ID async fn find_by_client_id(&mut self, client_id: &str) -> Result, 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, ) -> Result, 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, ) -> Result; + /// 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, ) -> Result; + /// 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; + /// 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), diff --git a/crates/storage/src/oauth2/mod.rs b/crates/storage/src/oauth2/mod.rs index eaa5e317..75823c27 100644 --- a/crates/storage/src/oauth2/mod.rs +++ b/crates/storage/src/oauth2/mod.rs @@ -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; diff --git a/crates/storage/src/oauth2/refresh_token.rs b/crates/storage/src/oauth2/refresh_token.rs index e8ac63ce..a0e2c44a 100644 --- a/crates/storage/src/oauth2/refresh_token.rs +++ b/crates/storage/src/oauth2/refresh_token.rs @@ -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, 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, 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; /// 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, diff --git a/crates/storage/src/oauth2/session.rs b/crates/storage/src/oauth2/session.rs index f348d9e6..880992a6 100644 --- a/crates/storage/src/oauth2/session.rs +++ b/crates/storage/src/oauth2/session.rs @@ -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, 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; + /// 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; + /// 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, diff --git a/crates/storage/src/pagination.rs b/crates/storage/src/pagination.rs index 6af45641..d8d8bc1c 100644 --- a/crates/storage/src/pagination.rs +++ b/crates/storage/src/pagination.rs @@ -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, + + /// The cursor to end at pub after: Option, + + /// 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 { + /// 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, } impl Page { + /// Map the items in this page with the given function + /// + /// # Parameters + /// + /// * `f`: The function to map the items with #[must_use] pub fn map(self, f: F) -> Page where @@ -144,6 +167,15 @@ impl Page { } } + /// 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(self, f: F) -> Result, E> where F: FnMut(T) -> Result, diff --git a/crates/storage/src/upstream_oauth2/link.rs b/crates/storage/src/upstream_oauth2/link.rs index 0057f2d6..9b8a4f1c 100644 --- a/crates/storage/src/upstream_oauth2/link.rs +++ b/crates/storage/src/upstream_oauth2/link.rs @@ -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, 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, 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; /// 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, diff --git a/crates/storage/src/upstream_oauth2/mod.rs b/crates/storage/src/upstream_oauth2/mod.rs index 1648a644..25221752 100644 --- a/crates/storage/src/upstream_oauth2/mod.rs +++ b/crates/storage/src/upstream_oauth2/mod.rs @@ -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; diff --git a/crates/storage/src/upstream_oauth2/provider.rs b/crates/storage/src/upstream_oauth2/provider.rs index 8aaca0da..663af2c9 100644 --- a/crates/storage/src/upstream_oauth2/provider.rs +++ b/crates/storage/src/upstream_oauth2/provider.rs @@ -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, 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; /// 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, Self::Error>; /// Get all upstream OAuth providers + /// + /// # Errors + /// + /// Returns [`Self::Error`] if the underlying repository fails async fn all(&mut self) -> Result, Self::Error>; } diff --git a/crates/storage/src/upstream_oauth2/session.rs b/crates/storage/src/upstream_oauth2/session.rs index e878444b..2d8f14be 100644 --- a/crates/storage/src/upstream_oauth2/session.rs +++ b/crates/storage/src/upstream_oauth2/session.rs @@ -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, 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; /// 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; /// 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, diff --git a/crates/storage/src/user/email.rs b/crates/storage/src/user/email.rs index 4c8601c2..9ae81534 100644 --- a/crates/storage/src/user/email.rs +++ b/crates/storage/src/user/email.rs @@ -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, 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, 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, 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, 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, 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; + /// 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; + + /// 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; + /// 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; + /// 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, 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, diff --git a/crates/storage/src/user/mod.rs b/crates/storage/src/user/mod.rs index 49003335..a611b459 100644 --- a/crates/storage/src/user/mod.rs +++ b/crates/storage/src/user/mod.rs @@ -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, 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, 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; + + /// 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; } diff --git a/crates/storage/src/user/password.rs b/crates/storage/src/user/password.rs index 06f03f55..7ef5c7ad 100644 --- a/crates/storage/src/user/password.rs +++ b/crates/storage/src/user/password.rs @@ -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, 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), diff --git a/crates/storage/src/user/session.rs b/crates/storage/src/user/session.rs index 0dfc581c..5e9defbe 100644 --- a/crates/storage/src/user/session.rs +++ b/crates/storage/src/user/session.rs @@ -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, 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; + + /// 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; + + /// 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, 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; + /// 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; + /// 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),