1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Data model and repository for the user recovery flow

This commit is contained in:
Quentin Gliech
2024-06-21 13:13:55 +02:00
parent b4814e24f1
commit 43582e7eca
14 changed files with 802 additions and 7 deletions

View File

@ -31,8 +31,8 @@ use crate::{
UpstreamOAuthSessionRepository,
},
user::{
BrowserSessionRepository, UserEmailRepository, UserPasswordRepository, UserRepository,
UserTermsRepository,
BrowserSessionRepository, UserEmailRepository, UserPasswordRepository,
UserRecoveryRepository, UserRepository, UserTermsRepository,
},
MapErr,
};
@ -149,6 +149,10 @@ pub trait RepositoryAccess: Send {
fn user_password<'c>(&'c mut self)
-> Box<dyn UserPasswordRepository<Error = Self::Error> + 'c>;
/// Get an [`UserRecoveryRepository`]
fn user_recovery<'c>(&'c mut self)
-> Box<dyn UserRecoveryRepository<Error = Self::Error> + 'c>;
/// Get an [`UserTermsRepository`]
fn user_terms<'c>(&'c mut self) -> Box<dyn UserTermsRepository<Error = Self::Error> + 'c>;
@ -322,6 +326,12 @@ mod impls {
Box::new(MapErr::new(self.inner.user_password(), &mut self.mapper))
}
fn user_recovery<'c>(
&'c mut self,
) -> Box<dyn crate::user::UserRecoveryRepository<Error = Self::Error> + 'c> {
Box::new(MapErr::new(self.inner.user_recovery(), &mut self.mapper))
}
fn user_terms<'c>(&'c mut self) -> Box<dyn UserTermsRepository<Error = Self::Error> + 'c> {
Box::new(MapErr::new(self.inner.user_terms(), &mut self.mapper))
}
@ -456,6 +466,12 @@ mod impls {
(**self).user_password()
}
fn user_recovery<'c>(
&'c mut self,
) -> Box<dyn crate::user::UserRecoveryRepository<Error = Self::Error> + 'c> {
(**self).user_recovery()
}
fn user_terms<'c>(&'c mut self) -> Box<dyn UserTermsRepository<Error = Self::Error> + 'c> {
(**self).user_terms()
}

View File

@ -23,12 +23,14 @@ use crate::{repository_impl, Clock};
mod email;
mod password;
mod recovery;
mod session;
mod terms;
pub use self::{
email::{UserEmailFilter, UserEmailRepository},
password::UserPasswordRepository,
recovery::UserRecoveryRepository,
session::{BrowserSessionFilter, BrowserSessionRepository},
terms::UserTermsRepository,
};

View File

@ -0,0 +1,167 @@
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::net::IpAddr;
use async_trait::async_trait;
use mas_data_model::{UserAgent, UserEmail, UserRecoverySession, UserRecoveryTicket};
use rand_core::RngCore;
use ulid::Ulid;
use crate::{repository_impl, Clock};
/// A [`UserRecoveryRepository`] helps interacting with [`UserRecoverySession`]
/// and [`UserRecoveryTicket`] saved in the storage backend
#[async_trait]
pub trait UserRecoveryRepository: Send + Sync {
/// The error type returned by the repository
type Error;
/// Lookup an [`UserRecoverySession`] by its ID
///
/// Returns `None` if no [`UserRecoverySession`] was found
///
/// # Parameters
///
/// * `id`: The ID of the [`UserRecoverySession`] to lookup
///
/// # Errors
///
/// Returns [`Self::Error`] if the underlying repository fails
async fn lookup_session(
&mut self,
id: Ulid,
) -> Result<Option<UserRecoverySession>, Self::Error>;
/// Create a new [`UserRecoverySession`] for the given email
///
/// Returns the newly created [`UserRecoverySession`]
///
/// # Parameters
///
/// * `rng`: The random number generator to use
/// * `clock`: The clock to use
/// * `email`: The email to create the session for
/// * `user_agent`: The user agent of the browser which initiated the
/// session
/// * `ip_address`: The IP address of the browser which initiated the
/// session, if known
/// * `locale`: The locale of the browser which initiated the session
///
/// # Errors
///
/// Returns [`Self::Error`] if the underlying repository fails
async fn add_session(
&mut self,
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
email: String,
user_agent: UserAgent,
ip_address: Option<IpAddr>,
locale: String,
) -> Result<UserRecoverySession, Self::Error>;
/// Find a [`UserRecoveryTicket`] by its ticket
///
/// Returns `None` if no [`UserRecoveryTicket`] was found
///
/// # Parameters
///
/// * `ticket`: The ticket of the [`UserRecoveryTicket`] to lookup
///
/// # Errors
///
/// Returns [`Self::Error`] if the underlying repository fails
async fn find_ticket(
&mut self,
ticket: &str,
) -> Result<Option<UserRecoveryTicket>, Self::Error>;
/// Add a [`UserRecoveryTicket`] to the given [`UserRecoverySession`] for
/// the given [`UserEmail`]
///
/// # Parameters
///
/// * `rng`: The random number generator to use
/// * `clock`: The clock to use
/// * `session`: The [`UserRecoverySession`] to add the ticket to
/// * `user_email`: The [`UserEmail`] to add the ticket for
/// * `ticket`: The ticket to add
///
/// # Errors
///
/// Returns [`Self::Error`] if the underlying repository fails
async fn add_ticket(
&mut self,
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
user_recovery_session: &UserRecoverySession,
user_email: &UserEmail,
ticket: String,
) -> Result<UserRecoveryTicket, Self::Error>;
/// Consume a [`UserRecoveryTicket`] and mark the session as used
///
/// # Parameters
///
/// * `clock`: The clock to use to record the time of consumption
/// * `ticket`: The [`UserRecoveryTicket`] to consume
/// * `session`: The [`UserRecoverySession`] to mark as used
///
/// # Errors
///
/// Returns [`Self::Error`] if the underlying repository fails or if the
/// recovery session was already used
async fn consume_ticket(
&mut self,
clock: &dyn Clock,
user_recovery_ticket: UserRecoveryTicket,
user_recovery_session: UserRecoverySession,
) -> Result<UserRecoverySession, Self::Error>;
}
repository_impl!(UserRecoveryRepository:
async fn lookup_session(&mut self, id: Ulid) -> Result<Option<UserRecoverySession>, Self::Error>;
async fn add_session(
&mut self,
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
email: String,
user_agent: UserAgent,
ip_address: Option<IpAddr>,
locale: String,
) -> Result<UserRecoverySession, Self::Error>;
async fn find_ticket(
&mut self,
ticket: &str,
) -> Result<Option<UserRecoveryTicket>, Self::Error>;
async fn add_ticket(
&mut self,
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
user_recovery_session: &UserRecoverySession,
user_email: &UserEmail,
ticket: String,
) -> Result<UserRecoveryTicket, Self::Error>;
async fn consume_ticket(
&mut self,
clock: &dyn Clock,
user_recovery_ticket: UserRecoveryTicket,
user_recovery_session: UserRecoverySession,
) -> Result<UserRecoverySession, Self::Error>;
);