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

handlers: box the rng and clock, and extract it from the state

This commit is contained in:
Quentin Gliech
2023-01-18 17:32:54 +01:00
parent 8c585b20f0
commit 9005931e2a
52 changed files with 291 additions and 193 deletions

2
Cargo.lock generated
View File

@ -3105,7 +3105,7 @@ dependencies = [
"mas-iana", "mas-iana",
"mas-jose", "mas-jose",
"oauth2-types", "oauth2-types",
"rand 0.8.5", "rand_core 0.6.4",
"thiserror", "thiserror",
"ulid", "ulid",
"url", "url",

View File

@ -15,6 +15,7 @@
use axum_extra::extract::cookie::{Cookie, PrivateCookieJar}; use axum_extra::extract::cookie::{Cookie, PrivateCookieJar};
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use data_encoding::{DecodeError, BASE64URL_NOPAD}; use data_encoding::{DecodeError, BASE64URL_NOPAD};
use mas_storage::Clock;
use rand::{Rng, RngCore}; use rand::{Rng, RngCore};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::{serde_as, TimestampSeconds}; use serde_with::{serde_as, TimestampSeconds};
@ -108,22 +109,27 @@ pub struct ProtectedForm<T> {
} }
pub trait CsrfExt { pub trait CsrfExt {
fn csrf_token<R>(self, now: DateTime<Utc>, rng: R) -> (CsrfToken, Self) fn csrf_token<C, R>(self, clock: &C, rng: R) -> (CsrfToken, Self)
where where
R: RngCore; R: RngCore,
fn verify_form<T>(&self, now: DateTime<Utc>, form: ProtectedForm<T>) -> Result<T, CsrfError>; C: Clock;
fn verify_form<C, T>(&self, clock: &C, form: ProtectedForm<T>) -> Result<T, CsrfError>
where
C: Clock;
} }
impl<K> CsrfExt for PrivateCookieJar<K> { impl<K> CsrfExt for PrivateCookieJar<K> {
fn csrf_token<R>(self, now: DateTime<Utc>, rng: R) -> (CsrfToken, Self) fn csrf_token<C, R>(self, clock: &C, rng: R) -> (CsrfToken, Self)
where where
R: RngCore, R: RngCore,
C: Clock,
{ {
let jar = self; let jar = self;
let mut cookie = jar.get("csrf").unwrap_or_else(|| Cookie::new("csrf", "")); let mut cookie = jar.get("csrf").unwrap_or_else(|| Cookie::new("csrf", ""));
cookie.set_path("/"); cookie.set_path("/");
cookie.set_http_only(true); cookie.set_http_only(true);
let now = clock.now();
let new_token = cookie let new_token = cookie
.decode() .decode()
.ok() .ok()
@ -136,10 +142,13 @@ impl<K> CsrfExt for PrivateCookieJar<K> {
(new_token, jar) (new_token, jar)
} }
fn verify_form<T>(&self, now: DateTime<Utc>, form: ProtectedForm<T>) -> Result<T, CsrfError> { fn verify_form<C, T>(&self, clock: &C, form: ProtectedForm<T>) -> Result<T, CsrfError>
where
C: Clock,
{
let cookie = self.get("csrf").ok_or(CsrfError::Missing)?; let cookie = self.get("csrf").ok_or(CsrfError::Missing)?;
let token: CsrfToken = cookie.decode()?; let token: CsrfToken = cookie.decode()?;
let token = token.verify_expiration(now)?; let token = token.verify_expiration(clock.now())?;
token.verify_form_value(&form.csrf)?; token.verify_form_value(&form.csrf)?;
Ok(form.inner) Ok(form.inner)
} }

View File

@ -24,13 +24,12 @@ use axum::{
response::{IntoResponse, Response}, response::{IntoResponse, Response},
BoxError, BoxError,
}; };
use chrono::{DateTime, Utc};
use headers::{authorization::Bearer, Authorization, Header, HeaderMapExt, HeaderName}; use headers::{authorization::Bearer, Authorization, Header, HeaderMapExt, HeaderName};
use http::{header::WWW_AUTHENTICATE, HeaderMap, HeaderValue, Request, StatusCode}; use http::{header::WWW_AUTHENTICATE, HeaderMap, HeaderValue, Request, StatusCode};
use mas_data_model::Session; use mas_data_model::Session;
use mas_storage::{ use mas_storage::{
oauth2::{OAuth2AccessTokenRepository, OAuth2SessionRepository}, oauth2::{OAuth2AccessTokenRepository, OAuth2SessionRepository},
Repository, Clock, Repository,
}; };
use serde::{de::DeserializeOwned, Deserialize}; use serde::{de::DeserializeOwned, Deserialize};
use thiserror::Error; use thiserror::Error;
@ -86,10 +85,10 @@ pub struct UserAuthorization<F = ()> {
impl<F: Send> UserAuthorization<F> { impl<F: Send> UserAuthorization<F> {
// TODO: take scopes to validate as parameter // TODO: take scopes to validate as parameter
pub async fn protected_form<R: Repository>( pub async fn protected_form<R: Repository, C: Clock>(
self, self,
repo: &mut R, repo: &mut R,
now: DateTime<Utc>, clock: &C,
) -> Result<(Session, F), AuthorizationVerificationError<R::Error>> { ) -> Result<(Session, F), AuthorizationVerificationError<R::Error>> {
let form = match self.form { let form = match self.form {
Some(f) => f, Some(f) => f,
@ -98,7 +97,7 @@ impl<F: Send> UserAuthorization<F> {
let (token, session) = self.access_token.fetch(repo).await?; let (token, session) = self.access_token.fetch(repo).await?;
if !token.is_valid(now) || !session.is_valid() { if !token.is_valid(clock.now()) || !session.is_valid() {
return Err(AuthorizationVerificationError::InvalidToken); return Err(AuthorizationVerificationError::InvalidToken);
} }
@ -106,14 +105,14 @@ impl<F: Send> UserAuthorization<F> {
} }
// TODO: take scopes to validate as parameter // TODO: take scopes to validate as parameter
pub async fn protected<R: Repository>( pub async fn protected<R: Repository, C: Clock>(
self, self,
repo: &mut R, repo: &mut R,
now: DateTime<Utc>, clock: &C,
) -> Result<Session, AuthorizationVerificationError<R::Error>> { ) -> Result<Session, AuthorizationVerificationError<R::Error>> {
let (token, session) = self.access_token.fetch(repo).await?; let (token, session) = self.access_token.fetch(repo).await?;
if !token.is_valid(now) || !session.is_valid() { if !token.is_valid(clock.now()) || !session.is_valid() {
return Err(AuthorizationVerificationError::InvalidToken); return Err(AuthorizationVerificationError::InvalidToken);
} }

View File

@ -15,7 +15,7 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use crc::{Crc, CRC_32_ISO_HDLC}; use crc::{Crc, CRC_32_ISO_HDLC};
use mas_iana::oauth::OAuthTokenTypeHint; use mas_iana::oauth::OAuthTokenTypeHint;
use rand::{distributions::Alphanumeric, Rng}; use rand::{distributions::Alphanumeric, Rng, RngCore};
use thiserror::Error; use thiserror::Error;
use ulid::Ulid; use ulid::Ulid;
@ -193,7 +193,7 @@ impl TokenType {
/// AccessToken.generate(thread_rng()); /// AccessToken.generate(thread_rng());
/// RefreshToken.generate(thread_rng()); /// RefreshToken.generate(thread_rng());
/// ``` /// ```
pub fn generate(self, rng: impl Rng) -> String { pub fn generate(self, rng: &mut (impl RngCore + ?Sized)) -> String {
let random_part: String = rng let random_part: String = rng
.sample_iter(&Alphanumeric) .sample_iter(&Alphanumeric)
.take(30) .take(30)

View File

@ -12,15 +12,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::sync::Arc; use std::{convert::Infallible, sync::Arc};
use axum::extract::FromRef; use axum::{
async_trait,
extract::{FromRef, FromRequestParts},
};
use mas_axum_utils::http_client_factory::HttpClientFactory; use mas_axum_utils::http_client_factory::HttpClientFactory;
use mas_email::Mailer; use mas_email::Mailer;
use mas_keystore::{Encrypter, Keystore}; use mas_keystore::{Encrypter, Keystore};
use mas_policy::PolicyFactory; use mas_policy::PolicyFactory;
use mas_router::UrlBuilder; use mas_router::UrlBuilder;
use mas_storage::{BoxClock, BoxRng, SystemClock};
use mas_templates::Templates; use mas_templates::Templates;
use rand::SeedableRng;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{passwords::PasswordManager, MatrixHomeserver}; use crate::{passwords::PasswordManager, MatrixHomeserver};
@ -105,3 +110,33 @@ impl FromRef<AppState> for PasswordManager {
input.password_manager.clone() input.password_manager.clone()
} }
} }
#[async_trait]
impl FromRequestParts<AppState> for BoxClock {
type Rejection = Infallible;
async fn from_request_parts(
_parts: &mut axum::http::request::Parts,
_state: &AppState,
) -> Result<Self, Self::Rejection> {
let clock = SystemClock::default();
Ok(Box::new(clock))
}
}
#[async_trait]
impl FromRequestParts<AppState> for BoxRng {
type Rejection = Infallible;
async fn from_request_parts(
_parts: &mut axum::http::request::Parts,
_state: &AppState,
) -> Result<Self, Self::Rejection> {
// This rng is used to source the local rng
#[allow(clippy::disallowed_methods)]
let rng = rand::thread_rng();
let rng = rand_chacha::ChaChaRng::from_rng(rng).expect("Failed to seed RNG");
Ok(Box::new(rng))
}
}

View File

@ -22,9 +22,10 @@ use mas_storage::{
CompatSsoLoginRepository, CompatSsoLoginRepository,
}, },
user::{UserPasswordRepository, UserRepository}, user::{UserPasswordRepository, UserRepository},
Clock, Repository, SystemClock, BoxClock, BoxRng, Clock, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none, DurationMilliSeconds}; use serde_with::{serde_as, skip_serializing_none, DurationMilliSeconds};
use sqlx::PgPool; use sqlx::PgPool;
@ -154,7 +155,6 @@ pub enum RouteError {
InvalidLoginToken, InvalidLoginToken,
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl IntoResponse for RouteError { impl IntoResponse for RouteError {
@ -194,18 +194,29 @@ impl IntoResponse for RouteError {
#[tracing::instrument(skip_all, err)] #[tracing::instrument(skip_all, err)]
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(password_manager): State<PasswordManager>, State(password_manager): State<PasswordManager>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(homeserver): State<MatrixHomeserver>, State(homeserver): State<MatrixHomeserver>,
Json(input): Json<RequestBody>, Json(input): Json<RequestBody>,
) -> Result<impl IntoResponse, RouteError> { ) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (session, user) = match input.credentials { let (session, user) = match input.credentials {
Credentials::Password { Credentials::Password {
identifier: Identifier::User { user }, identifier: Identifier::User { user },
password, password,
} => user_password_login(&password_manager, &mut repo, user, password).await?, } => {
user_password_login(
&mut rng,
&clock,
&password_manager,
&mut repo,
user,
password,
)
.await?
}
Credentials::Token { token } => token_login(&mut repo, &clock, &token).await?, Credentials::Token { token } => token_login(&mut repo, &clock, &token).await?,
@ -254,7 +265,7 @@ pub(crate) async fn post(
async fn token_login( async fn token_login(
repo: &mut PgRepository, repo: &mut PgRepository,
clock: &SystemClock, clock: &dyn Clock,
token: &str, token: &str,
) -> Result<(CompatSession, User), RouteError> { ) -> Result<(CompatSession, User), RouteError> {
let login = repo let login = repo
@ -319,13 +330,13 @@ async fn token_login(
} }
async fn user_password_login( async fn user_password_login(
mut rng: &mut (impl RngCore + CryptoRng + Send),
clock: &impl Clock,
password_manager: &PasswordManager, password_manager: &PasswordManager,
repo: &mut PgRepository, repo: &mut PgRepository,
username: String, username: String,
password: String, password: String,
) -> Result<(CompatSession, User), RouteError> { ) -> Result<(CompatSession, User), RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
// Find the user // Find the user
let user = repo let user = repo
.user() .user()
@ -358,7 +369,7 @@ async fn user_password_login(
repo.user_password() repo.user_password()
.add( .add(
&mut rng, &mut rng,
&clock, clock,
&user, &user,
version, version,
hashed_password, hashed_password,
@ -371,7 +382,7 @@ async fn user_password_login(
let device = Device::generate(&mut rng); let device = Device::generate(&mut rng);
let session = repo let session = repo
.compat_session() .compat_session()
.add(&mut rng, &clock, &user, device) .add(&mut rng, clock, &user, device)
.await?; .await?;
Ok((session, user)) Ok((session, user))

View File

@ -31,7 +31,7 @@ use mas_keystore::Encrypter;
use mas_router::{CompatLoginSsoAction, PostAuthAction, Route}; use mas_router::{CompatLoginSsoAction, PostAuthAction, Route};
use mas_storage::{ use mas_storage::{
compat::{CompatSessionRepository, CompatSsoLoginRepository}, compat::{CompatSessionRepository, CompatSsoLoginRepository},
Clock, Repository, BoxClock, BoxRng, Clock, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{CompatSsoContext, ErrorContext, TemplateContext, Templates}; use mas_templates::{CompatSsoContext, ErrorContext, TemplateContext, Templates};
@ -54,17 +54,18 @@ pub struct Params {
} }
pub async fn get( pub async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(templates): State<Templates>, State(templates): State<Templates>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Path(id): Path<Ulid>, Path(id): Path<Ulid>,
Query(params): Query<Params>, Query(params): Query<Params>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let maybe_session = session_info.load_session(&mut repo).await?; let maybe_session = session_info.load_session(&mut repo).await?;
@ -117,6 +118,8 @@ pub async fn get(
} }
pub async fn post( pub async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(templates): State<Templates>, State(templates): State<Templates>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
@ -124,11 +127,10 @@ pub async fn post(
Query(params): Query<Params>, Query(params): Query<Params>,
Form(form): Form<ProtectedForm<()>>, Form(form): Form<ProtectedForm<()>>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
cookie_jar.verify_form(clock.now(), form)?; cookie_jar.verify_form(&clock, form)?;
let maybe_session = session_info.load_session(&mut repo).await?; let maybe_session = session_info.load_session(&mut repo).await?;

View File

@ -19,7 +19,7 @@ use axum::{
}; };
use hyper::StatusCode; use hyper::StatusCode;
use mas_router::{CompatLoginSsoAction, CompatLoginSsoComplete, UrlBuilder}; use mas_router::{CompatLoginSsoAction, CompatLoginSsoComplete, UrlBuilder};
use mas_storage::{compat::CompatSsoLoginRepository, Repository}; use mas_storage::{compat::CompatSsoLoginRepository, BoxClock, BoxRng, Repository};
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use rand::distributions::{Alphanumeric, DistString}; use rand::distributions::{Alphanumeric, DistString};
use serde::Deserialize; use serde::Deserialize;
@ -49,7 +49,6 @@ pub enum RouteError {
InvalidRedirectUrl, InvalidRedirectUrl,
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl IntoResponse for RouteError { impl IntoResponse for RouteError {
@ -58,14 +57,13 @@ impl IntoResponse for RouteError {
} }
} }
#[tracing::instrument(skip(pool, url_builder), err)]
pub async fn get( pub async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(url_builder): State<UrlBuilder>, State(url_builder): State<UrlBuilder>,
Query(params): Query<Params>, Query(params): Query<Params>,
) -> Result<impl IntoResponse, RouteError> { ) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
// Check the redirectUrl parameter // Check the redirectUrl parameter
let redirect_url = params.redirect_url.ok_or(RouteError::MissingRedirectUrl)?; let redirect_url = params.redirect_url.ok_or(RouteError::MissingRedirectUrl)?;
let redirect_url = Url::parse(&redirect_url).map_err(|_| RouteError::InvalidRedirectUrl)?; let redirect_url = Url::parse(&redirect_url).map_err(|_| RouteError::InvalidRedirectUrl)?;

View File

@ -18,7 +18,7 @@ use hyper::StatusCode;
use mas_data_model::TokenType; use mas_data_model::TokenType;
use mas_storage::{ use mas_storage::{
compat::{CompatAccessTokenRepository, CompatSessionRepository}, compat::{CompatAccessTokenRepository, CompatSessionRepository},
Clock, Repository, SystemClock, BoxClock, Clock, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use sqlx::PgPool; use sqlx::PgPool;
@ -42,7 +42,6 @@ pub enum RouteError {
InvalidAuthorization, InvalidAuthorization,
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl IntoResponse for RouteError { impl IntoResponse for RouteError {
@ -69,10 +68,10 @@ impl IntoResponse for RouteError {
} }
pub(crate) async fn post( pub(crate) async fn post(
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
maybe_authorization: Option<TypedHeader<Authorization<Bearer>>>, maybe_authorization: Option<TypedHeader<Authorization<Bearer>>>,
) -> Result<impl IntoResponse, RouteError> { ) -> Result<impl IntoResponse, RouteError> {
let clock = SystemClock::default();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let TypedHeader(authorization) = maybe_authorization.ok_or(RouteError::MissingAuthorization)?; let TypedHeader(authorization) = maybe_authorization.ok_or(RouteError::MissingAuthorization)?;

View File

@ -18,7 +18,7 @@ use hyper::StatusCode;
use mas_data_model::{TokenFormatError, TokenType}; use mas_data_model::{TokenFormatError, TokenType};
use mas_storage::{ use mas_storage::{
compat::{CompatAccessTokenRepository, CompatRefreshTokenRepository, CompatSessionRepository}, compat::{CompatAccessTokenRepository, CompatRefreshTokenRepository, CompatSessionRepository},
Clock, Repository, BoxClock, BoxRng, Clock, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -70,7 +70,6 @@ impl IntoResponse for RouteError {
} }
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl From<TokenFormatError> for RouteError { impl From<TokenFormatError> for RouteError {
@ -89,10 +88,11 @@ pub struct ResponseBody {
} }
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
Json(input): Json<RequestBody>, Json(input): Json<RequestBody>,
) -> Result<impl IntoResponse, RouteError> { ) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let token_type = TokenType::check(&input.refresh_token)?; let token_type = TokenType::check(&input.refresh_token)?;

View File

@ -28,7 +28,7 @@ use std::{convert::Infallible, sync::Arc, time::Duration};
use axum::{ use axum::{
body::{Bytes, HttpBody}, body::{Bytes, HttpBody},
extract::FromRef, extract::{FromRef, FromRequestParts},
response::{Html, IntoResponse}, response::{Html, IntoResponse},
routing::{get, on, post, MethodFilter}, routing::{get, on, post, MethodFilter},
Router, Router,
@ -40,9 +40,9 @@ use mas_http::CorsLayerExt;
use mas_keystore::{Encrypter, Keystore}; use mas_keystore::{Encrypter, Keystore};
use mas_policy::PolicyFactory; use mas_policy::PolicyFactory;
use mas_router::{Route, UrlBuilder}; use mas_router::{Route, UrlBuilder};
use mas_storage::{BoxClock, BoxRng};
use mas_templates::{ErrorContext, Templates}; use mas_templates::{ErrorContext, Templates};
use passwords::PasswordManager; use passwords::PasswordManager;
use rand::SeedableRng;
use sqlx::PgPool; use sqlx::PgPool;
use tower::util::AndThenLayer; use tower::util::AndThenLayer;
use tower_http::cors::{Any, CorsLayer}; use tower_http::cors::{Any, CorsLayer};
@ -116,6 +116,8 @@ where
S: Clone + Send + Sync + 'static, S: Clone + Send + Sync + 'static,
Keystore: FromRef<S>, Keystore: FromRef<S>,
UrlBuilder: FromRef<S>, UrlBuilder: FromRef<S>,
BoxClock: FromRequestParts<S>,
BoxRng: FromRequestParts<S>,
{ {
Router::new() Router::new()
.route( .route(
@ -155,6 +157,8 @@ where
PgPool: FromRef<S>, PgPool: FromRef<S>,
Encrypter: FromRef<S>, Encrypter: FromRef<S>,
HttpClientFactory: FromRef<S>, HttpClientFactory: FromRef<S>,
BoxClock: FromRequestParts<S>,
BoxRng: FromRequestParts<S>,
{ {
// All those routes are API-like, with a common CORS layer // All those routes are API-like, with a common CORS layer
Router::new() Router::new()
@ -208,6 +212,8 @@ where
PgPool: FromRef<S>, PgPool: FromRef<S>,
MatrixHomeserver: FromRef<S>, MatrixHomeserver: FromRef<S>,
PasswordManager: FromRef<S>, PasswordManager: FromRef<S>,
BoxClock: FromRequestParts<S>,
BoxRng: FromRequestParts<S>,
{ {
Router::new() Router::new()
.route( .route(
@ -255,6 +261,8 @@ where
Keystore: FromRef<S>, Keystore: FromRef<S>,
HttpClientFactory: FromRef<S>, HttpClientFactory: FromRef<S>,
PasswordManager: FromRef<S>, PasswordManager: FromRef<S>,
BoxClock: FromRequestParts<S>,
BoxRng: FromRequestParts<S>,
{ {
Router::new() Router::new()
.route( .route(
@ -407,16 +415,3 @@ async fn test_state(pool: PgPool) -> Result<AppState, anyhow::Error> {
password_manager, password_manager,
}) })
} }
// XXX: that should be moved somewhere else
fn clock_and_rng() -> (mas_storage::SystemClock, rand_chacha::ChaChaRng) {
let clock = mas_storage::SystemClock::default();
// This rng is used to source the local rng
#[allow(clippy::disallowed_methods)]
let rng = rand::thread_rng();
let rng = rand_chacha::ChaChaRng::from_rng(rng).expect("Failed to seed RNG");
(clock, rng)
}

View File

@ -27,7 +27,7 @@ use mas_policy::PolicyFactory;
use mas_router::{PostAuthAction, Route}; use mas_router::{PostAuthAction, Route};
use mas_storage::{ use mas_storage::{
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository, OAuth2SessionRepository}, oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository, OAuth2SessionRepository},
Repository, BoxClock, BoxRng, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::Templates; use mas_templates::Templates;
@ -70,7 +70,6 @@ impl IntoResponse for RouteError {
} }
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl_from_error_for_route!(mas_policy::LoadError); impl_from_error_for_route!(mas_policy::LoadError);
impl_from_error_for_route!(mas_policy::InstanciateError); impl_from_error_for_route!(mas_policy::InstanciateError);
@ -79,6 +78,8 @@ impl_from_error_for_route!(super::callback::IntoCallbackDestinationError);
impl_from_error_for_route!(super::callback::CallbackDestinationError); impl_from_error_for_route!(super::callback::CallbackDestinationError);
pub(crate) async fn get( pub(crate) async fn get(
rng: BoxRng,
clock: BoxClock,
State(policy_factory): State<Arc<PolicyFactory>>, State(policy_factory): State<Arc<PolicyFactory>>,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
@ -108,7 +109,7 @@ pub(crate) async fn get(
return Ok((cookie_jar, mas_router::Login::and_then(continue_grant).go()).into_response()); return Ok((cookie_jar, mas_router::Login::and_then(continue_grant).go()).into_response());
}; };
match complete(grant, session, &policy_factory, repo).await { match complete(rng, clock, grant, session, &policy_factory, repo).await {
Ok(params) => { Ok(params) => {
let res = callback_destination.go(&templates, params).await?; let res = callback_destination.go(&templates, params).await?;
Ok((cookie_jar, res).into_response()) Ok((cookie_jar, res).into_response())
@ -149,7 +150,6 @@ pub enum GrantCompletionError {
NoSuchClient, NoSuchClient,
} }
impl_from_error_for_route!(GrantCompletionError: sqlx::Error);
impl_from_error_for_route!(GrantCompletionError: mas_storage_pg::DatabaseError); impl_from_error_for_route!(GrantCompletionError: mas_storage_pg::DatabaseError);
impl_from_error_for_route!(GrantCompletionError: super::callback::IntoCallbackDestinationError); impl_from_error_for_route!(GrantCompletionError: super::callback::IntoCallbackDestinationError);
impl_from_error_for_route!(GrantCompletionError: mas_policy::LoadError); impl_from_error_for_route!(GrantCompletionError: mas_policy::LoadError);
@ -157,13 +157,13 @@ impl_from_error_for_route!(GrantCompletionError: mas_policy::InstanciateError);
impl_from_error_for_route!(GrantCompletionError: mas_policy::EvaluationError); impl_from_error_for_route!(GrantCompletionError: mas_policy::EvaluationError);
pub(crate) async fn complete( pub(crate) async fn complete(
mut rng: BoxRng,
clock: BoxClock,
grant: AuthorizationGrant, grant: AuthorizationGrant,
browser_session: BrowserSession, browser_session: BrowserSession,
policy_factory: &PolicyFactory, policy_factory: &PolicyFactory,
mut repo: PgRepository, mut repo: PgRepository,
) -> Result<AuthorizationResponse<Option<AccessTokenResponse>>, GrantCompletionError> { ) -> Result<AuthorizationResponse<Option<AccessTokenResponse>>, GrantCompletionError> {
let (clock, mut rng) = crate::clock_and_rng();
// Verify that the grant is in a pending stage // Verify that the grant is in a pending stage
if !grant.stage.is_pending() { if !grant.stage.is_pending() {
return Err(GrantCompletionError::NotPending); return Err(GrantCompletionError::NotPending);

View File

@ -27,7 +27,7 @@ use mas_policy::PolicyFactory;
use mas_router::{PostAuthAction, Route}; use mas_router::{PostAuthAction, Route};
use mas_storage::{ use mas_storage::{
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository}, oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository},
Repository, BoxClock, BoxRng, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::Templates; use mas_templates::Templates;
@ -91,7 +91,6 @@ impl IntoResponse for RouteError {
} }
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl_from_error_for_route!(self::callback::CallbackDestinationError); impl_from_error_for_route!(self::callback::CallbackDestinationError);
impl_from_error_for_route!(mas_policy::LoadError); impl_from_error_for_route!(mas_policy::LoadError);
@ -133,13 +132,14 @@ fn resolve_response_mode(
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(policy_factory): State<Arc<PolicyFactory>>, State(policy_factory): State<Arc<PolicyFactory>>,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Form(params): Form<Params>, Form(params): Form<Params>,
) -> Result<Response, RouteError> { ) -> Result<Response, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
// First, figure out what client it is // First, figure out what client it is
@ -334,7 +334,15 @@ pub(crate) async fn get(
// Else, we immediately try to complete the authorization grant // Else, we immediately try to complete the authorization grant
Some(user_session) if prompt.contains(&Prompt::None) => { Some(user_session) if prompt.contains(&Prompt::None) => {
// With prompt=none, we should get back to the client immediately // With prompt=none, we should get back to the client immediately
match self::complete::complete(grant, user_session, &policy_factory, repo).await match self::complete::complete(
rng,
clock,
grant,
user_session,
&policy_factory,
repo,
)
.await
{ {
Ok(params) => callback_destination.go(&templates, params).await?, Ok(params) => callback_destination.go(&templates, params).await?,
Err(GrantCompletionError::RequiresConsent) => { Err(GrantCompletionError::RequiresConsent) => {
@ -373,7 +381,15 @@ pub(crate) async fn get(
Some(user_session) => { Some(user_session) => {
let grant_id = grant.id; let grant_id = grant.id;
// Else, we show the relevant reauth/consent page if necessary // Else, we show the relevant reauth/consent page if necessary
match self::complete::complete(grant, user_session, &policy_factory, repo).await match self::complete::complete(
rng,
clock,
grant,
user_session,
&policy_factory,
repo,
)
.await
{ {
Ok(params) => callback_destination.go(&templates, params).await?, Ok(params) => callback_destination.go(&templates, params).await?,
Err( Err(

View File

@ -30,7 +30,7 @@ use mas_policy::PolicyFactory;
use mas_router::{PostAuthAction, Route}; use mas_router::{PostAuthAction, Route};
use mas_storage::{ use mas_storage::{
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository}, oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository},
Clock, Repository, BoxClock, BoxRng, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{ConsentContext, PolicyViolationContext, TemplateContext, Templates}; use mas_templates::{ConsentContext, PolicyViolationContext, TemplateContext, Templates};
@ -61,7 +61,6 @@ pub enum RouteError {
NoSuchClient, NoSuchClient,
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_templates::TemplateError); impl_from_error_for_route!(mas_templates::TemplateError);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl_from_error_for_route!(mas_policy::LoadError); impl_from_error_for_route!(mas_policy::LoadError);
@ -75,13 +74,14 @@ impl IntoResponse for RouteError {
} }
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(policy_factory): State<Arc<PolicyFactory>>, State(policy_factory): State<Arc<PolicyFactory>>,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Path(grant_id): Path<Ulid>, Path(grant_id): Path<Ulid>,
) -> Result<Response, RouteError> { ) -> Result<Response, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
@ -99,7 +99,7 @@ pub(crate) async fn get(
} }
if let Some(session) = maybe_session { if let Some(session) = maybe_session {
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let mut policy = policy_factory.instantiate().await?; let mut policy = policy_factory.instantiate().await?;
let res = policy let res = policy
@ -130,16 +130,17 @@ pub(crate) async fn get(
} }
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(policy_factory): State<Arc<PolicyFactory>>, State(policy_factory): State<Arc<PolicyFactory>>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Path(grant_id): Path<Ulid>, Path(grant_id): Path<Ulid>,
Form(form): Form<ProtectedForm<()>>, Form(form): Form<ProtectedForm<()>>,
) -> Result<Response, RouteError> { ) -> Result<Response, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
cookie_jar.verify_form(clock.now(), form)?; cookie_jar.verify_form(&clock, form)?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();

View File

@ -25,7 +25,7 @@ use mas_storage::{
compat::{CompatAccessTokenRepository, CompatRefreshTokenRepository, CompatSessionRepository}, compat::{CompatAccessTokenRepository, CompatRefreshTokenRepository, CompatSessionRepository},
oauth2::{OAuth2AccessTokenRepository, OAuth2RefreshTokenRepository, OAuth2SessionRepository}, oauth2::{OAuth2AccessTokenRepository, OAuth2RefreshTokenRepository, OAuth2SessionRepository},
user::{BrowserSessionRepository, UserRepository}, user::{BrowserSessionRepository, UserRepository},
Clock, Repository, SystemClock, BoxClock, Clock, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use oauth2_types::{ use oauth2_types::{
@ -97,7 +97,6 @@ impl IntoResponse for RouteError {
} }
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl From<TokenFormatError> for RouteError { impl From<TokenFormatError> for RouteError {
@ -125,12 +124,12 @@ const API_SCOPE: ScopeToken = ScopeToken::from_static("urn:matrix:org.matrix.msc
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub(crate) async fn post( pub(crate) async fn post(
clock: BoxClock,
State(http_client_factory): State<HttpClientFactory>, State(http_client_factory): State<HttpClientFactory>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(encrypter): State<Encrypter>, State(encrypter): State<Encrypter>,
client_authorization: ClientAuthorization<IntrospectionRequest>, client_authorization: ClientAuthorization<IntrospectionRequest>,
) -> Result<impl IntoResponse, RouteError> { ) -> Result<impl IntoResponse, RouteError> {
let clock = SystemClock::default();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let client = client_authorization let client = client_authorization

View File

@ -19,7 +19,7 @@ use hyper::StatusCode;
use mas_iana::oauth::OAuthClientAuthenticationMethod; use mas_iana::oauth::OAuthClientAuthenticationMethod;
use mas_keystore::Encrypter; use mas_keystore::Encrypter;
use mas_policy::{PolicyFactory, Violation}; use mas_policy::{PolicyFactory, Violation};
use mas_storage::{oauth2::OAuth2ClientRepository, Repository}; use mas_storage::{oauth2::OAuth2ClientRepository, BoxClock, BoxRng, Repository};
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use oauth2_types::{ use oauth2_types::{
errors::{ClientError, ClientErrorCode}, errors::{ClientError, ClientErrorCode},
@ -49,7 +49,6 @@ pub(crate) enum RouteError {
PolicyDenied(Vec<Violation>), PolicyDenied(Vec<Violation>),
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl_from_error_for_route!(mas_policy::LoadError); impl_from_error_for_route!(mas_policy::LoadError);
impl_from_error_for_route!(mas_policy::InstanciateError); impl_from_error_for_route!(mas_policy::InstanciateError);
@ -108,12 +107,13 @@ impl IntoResponse for RouteError {
#[tracing::instrument(skip_all, err)] #[tracing::instrument(skip_all, err)]
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(policy_factory): State<Arc<PolicyFactory>>, State(policy_factory): State<Arc<PolicyFactory>>,
State(encrypter): State<Encrypter>, State(encrypter): State<Encrypter>,
Json(body): Json<ClientMetadata>, Json(body): Json<ClientMetadata>,
) -> Result<impl IntoResponse, RouteError> { ) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
info!(?body, "Client registration"); info!(?body, "Client registration");
// Validate the body // Validate the body

View File

@ -37,7 +37,7 @@ use mas_storage::{
OAuth2RefreshTokenRepository, OAuth2SessionRepository, OAuth2RefreshTokenRepository, OAuth2SessionRepository,
}, },
user::BrowserSessionRepository, user::BrowserSessionRepository,
Clock, Repository, BoxClock, BoxRng, Clock, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use oauth2_types::{ use oauth2_types::{
@ -151,7 +151,6 @@ impl IntoResponse for RouteError {
} }
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl_from_error_for_route!(mas_keystore::WrongAlgorithmError); impl_from_error_for_route!(mas_keystore::WrongAlgorithmError);
impl_from_error_for_route!(mas_jose::claims::ClaimError); impl_from_error_for_route!(mas_jose::claims::ClaimError);
@ -160,6 +159,8 @@ impl_from_error_for_route!(mas_jose::jwt::JwtSignatureError);
#[tracing::instrument(skip_all, err)] #[tracing::instrument(skip_all, err)]
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(http_client_factory): State<HttpClientFactory>, State(http_client_factory): State<HttpClientFactory>,
State(key_store): State<Keystore>, State(key_store): State<Keystore>,
State(url_builder): State<UrlBuilder>, State(url_builder): State<UrlBuilder>,
@ -189,10 +190,19 @@ pub(crate) async fn post(
let reply = match form { let reply = match form {
AccessTokenRequest::AuthorizationCode(grant) => { AccessTokenRequest::AuthorizationCode(grant) => {
authorization_code_grant(&grant, &client, &key_store, &url_builder, repo).await? authorization_code_grant(
&mut rng,
&clock,
&grant,
&client,
&key_store,
&url_builder,
repo,
)
.await?
} }
AccessTokenRequest::RefreshToken(grant) => { AccessTokenRequest::RefreshToken(grant) => {
refresh_token_grant(&grant, &client, repo).await? refresh_token_grant(&mut rng, &clock, &grant, &client, repo).await?
} }
_ => { _ => {
return Err(RouteError::InvalidGrant); return Err(RouteError::InvalidGrant);
@ -208,14 +218,14 @@ pub(crate) async fn post(
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
async fn authorization_code_grant( async fn authorization_code_grant(
mut rng: &mut BoxRng,
clock: &impl Clock,
grant: &AuthorizationCodeGrant, grant: &AuthorizationCodeGrant,
client: &Client, client: &Client,
key_store: &Keystore, key_store: &Keystore,
url_builder: &UrlBuilder, url_builder: &UrlBuilder,
mut repo: PgRepository, mut repo: PgRepository,
) -> Result<AccessTokenResponse, RouteError> { ) -> Result<AccessTokenResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let authz_grant = repo let authz_grant = repo
.oauth2_authorization_grant() .oauth2_authorization_grant()
.find_by_code(&grant.code) .find_by_code(&grant.code)
@ -244,7 +254,7 @@ async fn authorization_code_grant(
.lookup(session_id) .lookup(session_id)
.await? .await?
.ok_or(RouteError::NoSuchOAuthSession)?; .ok_or(RouteError::NoSuchOAuthSession)?;
repo.oauth2_session().finish(&clock, session).await?; repo.oauth2_session().finish(clock, session).await?;
repo.save().await?; repo.save().await?;
} }
@ -302,12 +312,12 @@ async fn authorization_code_grant(
let access_token = repo let access_token = repo
.oauth2_access_token() .oauth2_access_token()
.add(&mut rng, &clock, &session, access_token_str, ttl) .add(&mut rng, clock, &session, access_token_str, ttl)
.await?; .await?;
let refresh_token = repo let refresh_token = repo
.oauth2_refresh_token() .oauth2_refresh_token()
.add(&mut rng, &clock, &session, &access_token, refresh_token_str) .add(&mut rng, clock, &session, &access_token, refresh_token_str)
.await?; .await?;
let id_token = if session.scope.contains(&scope::OPENID) { let id_token = if session.scope.contains(&scope::OPENID) {
@ -357,7 +367,7 @@ async fn authorization_code_grant(
} }
repo.oauth2_authorization_grant() repo.oauth2_authorization_grant()
.exchange(&clock, authz_grant) .exchange(clock, authz_grant)
.await?; .await?;
repo.save().await?; repo.save().await?;
@ -366,12 +376,12 @@ async fn authorization_code_grant(
} }
async fn refresh_token_grant( async fn refresh_token_grant(
mut rng: &mut BoxRng,
clock: &impl Clock,
grant: &RefreshTokenGrant, grant: &RefreshTokenGrant,
client: &Client, client: &Client,
mut repo: PgRepository, mut repo: PgRepository,
) -> Result<AccessTokenResponse, RouteError> { ) -> Result<AccessTokenResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let refresh_token = repo let refresh_token = repo
.oauth2_refresh_token() .oauth2_refresh_token()
.find_by_token(&grant.refresh_token) .find_by_token(&grant.refresh_token)
@ -399,14 +409,14 @@ async fn refresh_token_grant(
let new_access_token = repo let new_access_token = repo
.oauth2_access_token() .oauth2_access_token()
.add(&mut rng, &clock, &session, access_token_str.clone(), ttl) .add(&mut rng, clock, &session, access_token_str.clone(), ttl)
.await?; .await?;
let new_refresh_token = repo let new_refresh_token = repo
.oauth2_refresh_token() .oauth2_refresh_token()
.add( .add(
&mut rng, &mut rng,
&clock, clock,
&session, &session,
&new_access_token, &new_access_token,
refresh_token_str, refresh_token_str,
@ -415,13 +425,13 @@ async fn refresh_token_grant(
let refresh_token = repo let refresh_token = repo
.oauth2_refresh_token() .oauth2_refresh_token()
.consume(&clock, refresh_token) .consume(clock, refresh_token)
.await?; .await?;
if let Some(access_token_id) = refresh_token.access_token_id { if let Some(access_token_id) = refresh_token.access_token_id {
if let Some(access_token) = repo.oauth2_access_token().lookup(access_token_id).await? { if let Some(access_token) = repo.oauth2_access_token().lookup(access_token_id).await? {
repo.oauth2_access_token() repo.oauth2_access_token()
.revoke(&clock, access_token) .revoke(clock, access_token)
.await?; .await?;
} }
} }

View File

@ -31,7 +31,7 @@ use mas_router::UrlBuilder;
use mas_storage::{ use mas_storage::{
oauth2::OAuth2ClientRepository, oauth2::OAuth2ClientRepository,
user::{BrowserSessionRepository, UserEmailRepository}, user::{BrowserSessionRepository, UserEmailRepository},
Clock, Repository, BoxClock, BoxRng, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use oauth2_types::scope; use oauth2_types::scope;
@ -79,7 +79,6 @@ pub enum RouteError {
NoSuchBrowserSession, NoSuchBrowserSession,
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl_from_error_for_route!(mas_keystore::WrongAlgorithmError); impl_from_error_for_route!(mas_keystore::WrongAlgorithmError);
impl_from_error_for_route!(mas_jose::jwt::JwtSignatureError); impl_from_error_for_route!(mas_jose::jwt::JwtSignatureError);
@ -99,15 +98,16 @@ impl IntoResponse for RouteError {
} }
pub async fn get( pub async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(url_builder): State<UrlBuilder>, State(url_builder): State<UrlBuilder>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(key_store): State<Keystore>, State(key_store): State<Keystore>,
user_authorization: UserAuthorization, user_authorization: UserAuthorization,
) -> Result<Response, RouteError> { ) -> Result<Response, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let session = user_authorization.protected(&mut repo, clock.now()).await?; let session = user_authorization.protected(&mut repo, &clock).await?;
let browser_session = repo let browser_session = repo
.browser_session() .browser_session()

View File

@ -24,7 +24,7 @@ use mas_oidc_client::requests::authorization_code::AuthorizationRequestData;
use mas_router::UrlBuilder; use mas_router::UrlBuilder;
use mas_storage::{ use mas_storage::{
upstream_oauth2::{UpstreamOAuthProviderRepository, UpstreamOAuthSessionRepository}, upstream_oauth2::{UpstreamOAuthProviderRepository, UpstreamOAuthSessionRepository},
Clock, Repository, BoxClock, BoxRng, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use sqlx::PgPool; use sqlx::PgPool;
@ -43,7 +43,6 @@ pub(crate) enum RouteError {
Internal(Box<dyn std::error::Error>), Internal(Box<dyn std::error::Error>),
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_http::ClientInitError); impl_from_error_for_route!(mas_http::ClientInitError);
impl_from_error_for_route!(mas_oidc_client::error::DiscoveryError); impl_from_error_for_route!(mas_oidc_client::error::DiscoveryError);
impl_from_error_for_route!(mas_oidc_client::error::AuthorizationError); impl_from_error_for_route!(mas_oidc_client::error::AuthorizationError);
@ -59,6 +58,8 @@ impl IntoResponse for RouteError {
} }
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(http_client_factory): State<HttpClientFactory>, State(http_client_factory): State<HttpClientFactory>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(url_builder): State<UrlBuilder>, State(url_builder): State<UrlBuilder>,
@ -66,8 +67,6 @@ pub(crate) async fn get(
Path(provider_id): Path<Ulid>, Path(provider_id): Path<Ulid>,
Query(query): Query<OptionalPostAuthAction>, Query(query): Query<OptionalPostAuthAction>,
) -> Result<impl IntoResponse, RouteError> { ) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let provider = repo let provider = repo
@ -115,7 +114,7 @@ pub(crate) async fn get(
let cookie_jar = UpstreamSessionsCookie::load(&cookie_jar) let cookie_jar = UpstreamSessionsCookie::load(&cookie_jar)
.add(session.id, provider.id, data.state, query.post_auth_action) .add(session.id, provider.id, data.state, query.post_auth_action)
.save(cookie_jar, clock.now()); .save(cookie_jar, &clock);
repo.save().await?; repo.save().await?;

View File

@ -30,7 +30,7 @@ use mas_storage::{
UpstreamOAuthLinkRepository, UpstreamOAuthProviderRepository, UpstreamOAuthLinkRepository, UpstreamOAuthProviderRepository,
UpstreamOAuthSessionRepository, UpstreamOAuthSessionRepository,
}, },
Clock, Repository, BoxClock, BoxRng, Clock, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use oauth2_types::errors::ClientErrorCode; use oauth2_types::errors::ClientErrorCode;
@ -102,7 +102,6 @@ pub(crate) enum RouteError {
impl_from_error_for_route!(mas_storage_pg::DatabaseError); impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl_from_error_for_route!(mas_http::ClientInitError); impl_from_error_for_route!(mas_http::ClientInitError);
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_oidc_client::error::DiscoveryError); impl_from_error_for_route!(mas_oidc_client::error::DiscoveryError);
impl_from_error_for_route!(mas_oidc_client::error::JwksError); impl_from_error_for_route!(mas_oidc_client::error::JwksError);
impl_from_error_for_route!(mas_oidc_client::error::TokenAuthorizationCodeError); impl_from_error_for_route!(mas_oidc_client::error::TokenAuthorizationCodeError);
@ -122,6 +121,8 @@ impl IntoResponse for RouteError {
#[allow(clippy::too_many_lines, clippy::too_many_arguments)] #[allow(clippy::too_many_lines, clippy::too_many_arguments)]
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(http_client_factory): State<HttpClientFactory>, State(http_client_factory): State<HttpClientFactory>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(url_builder): State<UrlBuilder>, State(url_builder): State<UrlBuilder>,
@ -131,8 +132,6 @@ pub(crate) async fn get(
Path(provider_id): Path<Ulid>, Path(provider_id): Path<Ulid>,
Query(params): Query<QueryParams>, Query(params): Query<QueryParams>,
) -> Result<impl IntoResponse, RouteError> { ) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let provider = repo let provider = repo
@ -268,7 +267,7 @@ pub(crate) async fn get(
let cookie_jar = sessions_cookie let cookie_jar = sessions_cookie
.add_link_to_session(session.id, link.id)? .add_link_to_session(session.id, link.id)?
.save(cookie_jar, clock.now()); .save(cookie_jar, &clock);
repo.save().await?; repo.save().await?;

View File

@ -18,6 +18,7 @@ use axum_extra::extract::{cookie::Cookie, PrivateCookieJar};
use chrono::{DateTime, Duration, NaiveDateTime, Utc}; use chrono::{DateTime, Duration, NaiveDateTime, Utc};
use mas_axum_utils::CookieExt; use mas_axum_utils::CookieExt;
use mas_router::PostAuthAction; use mas_router::PostAuthAction;
use mas_storage::Clock;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use time::OffsetDateTime; use time::OffsetDateTime;
@ -65,11 +66,11 @@ impl UpstreamSessions {
} }
/// Save the upstreams sessions to the cookie jar /// Save the upstreams sessions to the cookie jar
pub fn save<K>( pub fn save<K, C>(self, cookie_jar: PrivateCookieJar<K>, clock: &C) -> PrivateCookieJar<K>
self, where
cookie_jar: PrivateCookieJar<K>, C: Clock,
now: DateTime<Utc>, {
) -> PrivateCookieJar<K> { let now = clock.now();
let this = self.expire(now); let this = self.expire(now);
let mut cookie = Cookie::named(COOKIE_NAME).encode(&this); let mut cookie = Cookie::named(COOKIE_NAME).encode(&this);
cookie.set_path("/"); cookie.set_path("/");

View File

@ -27,7 +27,7 @@ use mas_keystore::Encrypter;
use mas_storage::{ use mas_storage::{
upstream_oauth2::{UpstreamOAuthLinkRepository, UpstreamOAuthSessionRepository}, upstream_oauth2::{UpstreamOAuthLinkRepository, UpstreamOAuthSessionRepository},
user::{BrowserSessionRepository, UserRepository}, user::{BrowserSessionRepository, UserRepository},
Clock, Repository, BoxClock, BoxRng, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{ use mas_templates::{
@ -70,7 +70,6 @@ pub(crate) enum RouteError {
Internal(Box<dyn std::error::Error>), Internal(Box<dyn std::error::Error>),
} }
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_templates::TemplateError); impl_from_error_for_route!(mas_templates::TemplateError);
impl_from_error_for_route!(mas_axum_utils::csrf::CsrfError); impl_from_error_for_route!(mas_axum_utils::csrf::CsrfError);
impl_from_error_for_route!(super::cookie::UpstreamSessionNotFound); impl_from_error_for_route!(super::cookie::UpstreamSessionNotFound);
@ -95,14 +94,14 @@ pub(crate) enum FormData {
} }
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(templates): State<Templates>, State(templates): State<Templates>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Path(link_id): Path<Ulid>, Path(link_id): Path<Ulid>,
) -> Result<impl IntoResponse, RouteError> { ) -> Result<impl IntoResponse, RouteError> {
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (clock, mut rng) = crate::clock_and_rng();
let sessions_cookie = UpstreamSessionsCookie::load(&cookie_jar); let sessions_cookie = UpstreamSessionsCookie::load(&cookie_jar);
let (session_id, _post_auth_action) = sessions_cookie let (session_id, _post_auth_action) = sessions_cookie
.lookup_link(link_id) .lookup_link(link_id)
@ -131,7 +130,7 @@ pub(crate) async fn get(
} }
let (user_session_info, cookie_jar) = cookie_jar.session_info(); let (user_session_info, cookie_jar) = cookie_jar.session_info();
let (csrf_token, mut cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, mut cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let maybe_user_session = user_session_info.load_session(&mut repo).await?; let maybe_user_session = user_session_info.load_session(&mut repo).await?;
let render = match (maybe_user_session, link.user_id) { let render = match (maybe_user_session, link.user_id) {
@ -212,14 +211,15 @@ pub(crate) async fn get(
} }
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Path(link_id): Path<Ulid>, Path(link_id): Path<Ulid>,
Form(form): Form<ProtectedForm<FormData>>, Form(form): Form<ProtectedForm<FormData>>,
) -> Result<impl IntoResponse, RouteError> { ) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let form = cookie_jar.verify_form(clock.now(), form)?; let form = cookie_jar.verify_form(&clock, form)?;
let sessions_cookie = UpstreamSessionsCookie::load(&cookie_jar); let sessions_cookie = UpstreamSessionsCookie::load(&cookie_jar);
let (session_id, post_auth_action) = sessions_cookie let (session_id, post_auth_action) = sessions_cookie
@ -297,7 +297,7 @@ pub(crate) async fn post(
let cookie_jar = sessions_cookie let cookie_jar = sessions_cookie
.consume_link(link_id)? .consume_link(link_id)?
.save(cookie_jar, clock.now()); .save(cookie_jar, &clock);
let cookie_jar = cookie_jar.set_session(&session); let cookie_jar = cookie_jar.set_session(&session);
repo.save().await?; repo.save().await?;

View File

@ -24,7 +24,7 @@ use mas_axum_utils::{
use mas_email::Mailer; use mas_email::Mailer;
use mas_keystore::Encrypter; use mas_keystore::Encrypter;
use mas_router::Route; use mas_router::Route;
use mas_storage::{user::UserEmailRepository, Clock, Repository}; use mas_storage::{user::UserEmailRepository, BoxClock, BoxRng, Repository};
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{EmailAddContext, TemplateContext, Templates}; use mas_templates::{EmailAddContext, TemplateContext, Templates};
use serde::Deserialize; use serde::Deserialize;
@ -39,14 +39,15 @@ pub struct EmailForm {
} }
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
let maybe_session = session_info.load_session(&mut repo).await?; let maybe_session = session_info.load_session(&mut repo).await?;
@ -68,16 +69,17 @@ pub(crate) async fn get(
} }
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(mailer): State<Mailer>, State(mailer): State<Mailer>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Query(query): Query<OptionalPostAuthAction>, Query(query): Query<OptionalPostAuthAction>,
Form(form): Form<ProtectedForm<EmailForm>>, Form(form): Form<ProtectedForm<EmailForm>>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let form = cookie_jar.verify_form(clock.now(), form)?; let form = cookie_jar.verify_form(&clock, form)?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
let maybe_session = session_info.load_session(&mut repo).await?; let maybe_session = session_info.load_session(&mut repo).await?;

View File

@ -28,7 +28,7 @@ use mas_data_model::{BrowserSession, User, UserEmail};
use mas_email::Mailer; use mas_email::Mailer;
use mas_keystore::Encrypter; use mas_keystore::Encrypter;
use mas_router::Route; use mas_router::Route;
use mas_storage::{user::UserEmailRepository, Clock, Repository}; use mas_storage::{user::UserEmailRepository, BoxClock, BoxRng, Clock, Repository};
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{AccountEmailsContext, EmailVerificationContext, TemplateContext, Templates}; use mas_templates::{AccountEmailsContext, EmailVerificationContext, TemplateContext, Templates};
use rand::{distributions::Uniform, Rng}; use rand::{distributions::Uniform, Rng};
@ -49,12 +49,12 @@ pub enum ManagementForm {
} }
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
@ -77,7 +77,7 @@ async fn render(
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
repo: &mut impl Repository, repo: &mut impl Repository,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock, rng);
let emails = repo.user_email().all(&session.user).await?; let emails = repo.user_email().all(&session.user).await?;
@ -124,13 +124,14 @@ async fn start_email_verification(
} }
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
State(mailer): State<Mailer>, State(mailer): State<Mailer>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Form(form): Form<ProtectedForm<ManagementForm>>, Form(form): Form<ProtectedForm<ManagementForm>>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
@ -144,7 +145,7 @@ pub(crate) async fn post(
return Ok((cookie_jar, login.go()).into_response()); return Ok((cookie_jar, login.go()).into_response());
}; };
let form = cookie_jar.verify_form(clock.now(), form)?; let form = cookie_jar.verify_form(&clock, form)?;
match form { match form {
ManagementForm::Add { email } => { ManagementForm::Add { email } => {

View File

@ -24,7 +24,7 @@ use mas_axum_utils::{
}; };
use mas_keystore::Encrypter; use mas_keystore::Encrypter;
use mas_router::Route; use mas_router::Route;
use mas_storage::{user::UserEmailRepository, Clock, Repository, SystemClock}; use mas_storage::{user::UserEmailRepository, BoxClock, BoxRng, Repository};
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{EmailVerificationPageContext, TemplateContext, Templates}; use mas_templates::{EmailVerificationPageContext, TemplateContext, Templates};
use serde::Deserialize; use serde::Deserialize;
@ -39,16 +39,17 @@ pub struct CodeForm {
} }
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
Query(query): Query<OptionalPostAuthAction>, Query(query): Query<OptionalPostAuthAction>,
Path(id): Path<Ulid>, Path(id): Path<Ulid>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
let maybe_session = session_info.load_session(&mut repo).await?; let maybe_session = session_info.load_session(&mut repo).await?;
@ -83,16 +84,16 @@ pub(crate) async fn get(
} }
pub(crate) async fn post( pub(crate) async fn post(
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Query(query): Query<OptionalPostAuthAction>, Query(query): Query<OptionalPostAuthAction>,
Path(id): Path<Ulid>, Path(id): Path<Ulid>,
Form(form): Form<ProtectedForm<CodeForm>>, Form(form): Form<ProtectedForm<CodeForm>>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let clock = SystemClock::default();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let form = cookie_jar.verify_form(clock.now(), form)?; let form = cookie_jar.verify_form(&clock, form)?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
let maybe_session = session_info.load_session(&mut repo).await?; let maybe_session = session_info.load_session(&mut repo).await?;

View File

@ -25,21 +25,22 @@ use mas_keystore::Encrypter;
use mas_router::Route; use mas_router::Route;
use mas_storage::{ use mas_storage::{
user::{BrowserSessionRepository, UserEmailRepository}, user::{BrowserSessionRepository, UserEmailRepository},
Clock, Repository, BoxClock, BoxRng, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{AccountContext, TemplateContext, Templates}; use mas_templates::{AccountContext, TemplateContext, Templates};
use sqlx::PgPool; use sqlx::PgPool;
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
let maybe_session = session_info.load_session(&mut repo).await?; let maybe_session = session_info.load_session(&mut repo).await?;

View File

@ -27,7 +27,7 @@ use mas_keystore::Encrypter;
use mas_router::Route; use mas_router::Route;
use mas_storage::{ use mas_storage::{
user::{BrowserSessionRepository, UserPasswordRepository}, user::{BrowserSessionRepository, UserPasswordRepository},
Clock, Repository, BoxClock, BoxRng, Clock, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{EmptyContext, TemplateContext, Templates}; use mas_templates::{EmptyContext, TemplateContext, Templates};
@ -46,11 +46,12 @@ pub struct ChangeForm {
} }
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
@ -72,7 +73,7 @@ async fn render(
session: BrowserSession, session: BrowserSession,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock, rng);
let ctx = EmptyContext let ctx = EmptyContext
.with_session(session) .with_session(session)
@ -84,16 +85,17 @@ async fn render(
} }
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(password_manager): State<PasswordManager>, State(password_manager): State<PasswordManager>,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Form(form): Form<ProtectedForm<ChangeForm>>, Form(form): Form<ProtectedForm<ChangeForm>>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let form = cookie_jar.verify_form(clock.now(), form)?; let form = cookie_jar.verify_form(&clock, form)?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();

View File

@ -20,21 +20,22 @@ use axum_extra::extract::PrivateCookieJar;
use mas_axum_utils::{csrf::CsrfExt, FancyError, SessionInfoExt}; use mas_axum_utils::{csrf::CsrfExt, FancyError, SessionInfoExt};
use mas_keystore::Encrypter; use mas_keystore::Encrypter;
use mas_router::UrlBuilder; use mas_router::UrlBuilder;
use mas_storage::Clock; use mas_storage::{BoxClock, BoxRng};
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{IndexContext, TemplateContext, Templates}; use mas_templates::{IndexContext, TemplateContext, Templates};
use sqlx::PgPool; use sqlx::PgPool;
pub async fn get( pub async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>, State(templates): State<Templates>,
State(url_builder): State<UrlBuilder>, State(url_builder): State<UrlBuilder>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<impl IntoResponse, FancyError> { ) -> Result<impl IntoResponse, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
let session = session_info.load_session(&mut repo).await?; let session = session_info.load_session(&mut repo).await?;

View File

@ -26,7 +26,7 @@ use mas_keystore::Encrypter;
use mas_storage::{ use mas_storage::{
upstream_oauth2::UpstreamOAuthProviderRepository, upstream_oauth2::UpstreamOAuthProviderRepository,
user::{BrowserSessionRepository, UserPasswordRepository, UserRepository}, user::{BrowserSessionRepository, UserPasswordRepository, UserRepository},
Clock, Repository, BoxClock, BoxRng, Clock, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{ use mas_templates::{
@ -51,15 +51,16 @@ impl ToFormState for LoginForm {
} }
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
Query(query): Query<OptionalPostAuthAction>, Query(query): Query<OptionalPostAuthAction>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
let maybe_session = session_info.load_session(&mut repo).await?; let maybe_session = session_info.load_session(&mut repo).await?;
@ -83,6 +84,8 @@ pub(crate) async fn get(
} }
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(password_manager): State<PasswordManager>, State(password_manager): State<PasswordManager>,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
@ -90,12 +93,11 @@ pub(crate) async fn post(
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Form(form): Form<ProtectedForm<LoginForm>>, Form(form): Form<ProtectedForm<LoginForm>>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let form = cookie_jar.verify_form(clock.now(), form)?; let form = cookie_jar.verify_form(&clock, form)?;
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
// Validate the form // Validate the form
let state = { let state = {

View File

@ -23,19 +23,19 @@ use mas_axum_utils::{
}; };
use mas_keystore::Encrypter; use mas_keystore::Encrypter;
use mas_router::{PostAuthAction, Route}; use mas_router::{PostAuthAction, Route};
use mas_storage::{user::BrowserSessionRepository, Clock, Repository, SystemClock}; use mas_storage::{user::BrowserSessionRepository, BoxClock, Repository};
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use sqlx::PgPool; use sqlx::PgPool;
pub(crate) async fn post( pub(crate) async fn post(
clock: BoxClock,
State(pool): State<PgPool>, State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Form(form): Form<ProtectedForm<Option<PostAuthAction>>>, Form(form): Form<ProtectedForm<Option<PostAuthAction>>>,
) -> Result<impl IntoResponse, FancyError> { ) -> Result<impl IntoResponse, FancyError> {
let clock = SystemClock::default();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let form = cookie_jar.verify_form(clock.now(), form)?; let form = cookie_jar.verify_form(&clock, form)?;
let (session_info, mut cookie_jar) = cookie_jar.session_info(); let (session_info, mut cookie_jar) = cookie_jar.session_info();

View File

@ -26,7 +26,7 @@ use mas_keystore::Encrypter;
use mas_router::Route; use mas_router::Route;
use mas_storage::{ use mas_storage::{
user::{BrowserSessionRepository, UserPasswordRepository}, user::{BrowserSessionRepository, UserPasswordRepository},
Clock, Repository, BoxClock, BoxRng, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{ReauthContext, TemplateContext, Templates}; use mas_templates::{ReauthContext, TemplateContext, Templates};
@ -43,15 +43,16 @@ pub(crate) struct ReauthForm {
} }
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
Query(query): Query<OptionalPostAuthAction>, Query(query): Query<OptionalPostAuthAction>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
let maybe_session = session_info.load_session(&mut repo).await?; let maybe_session = session_info.load_session(&mut repo).await?;
@ -80,16 +81,17 @@ pub(crate) async fn get(
} }
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(password_manager): State<PasswordManager>, State(password_manager): State<PasswordManager>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
Query(query): Query<OptionalPostAuthAction>, Query(query): Query<OptionalPostAuthAction>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Form(form): Form<ProtectedForm<ReauthForm>>, Form(form): Form<ProtectedForm<ReauthForm>>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let form = cookie_jar.verify_form(clock.now(), form)?; let form = cookie_jar.verify_form(&clock, form)?;
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();

View File

@ -33,7 +33,7 @@ use mas_policy::PolicyFactory;
use mas_router::Route; use mas_router::Route;
use mas_storage::{ use mas_storage::{
user::{BrowserSessionRepository, UserEmailRepository, UserPasswordRepository, UserRepository}, user::{BrowserSessionRepository, UserEmailRepository, UserPasswordRepository, UserRepository},
Clock, Repository, BoxClock, BoxRng, Repository,
}; };
use mas_storage_pg::PgRepository; use mas_storage_pg::PgRepository;
use mas_templates::{ use mas_templates::{
@ -61,15 +61,16 @@ impl ToFormState for RegisterForm {
} }
pub(crate) async fn get( pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>, State(templates): State<Templates>,
State(pool): State<PgPool>, State(pool): State<PgPool>,
Query(query): Query<OptionalPostAuthAction>, Query(query): Query<OptionalPostAuthAction>,
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let (session_info, cookie_jar) = cookie_jar.session_info(); let (session_info, cookie_jar) = cookie_jar.session_info();
let maybe_session = session_info.load_session(&mut repo).await?; let maybe_session = session_info.load_session(&mut repo).await?;
@ -93,6 +94,8 @@ pub(crate) async fn get(
#[allow(clippy::too_many_lines, clippy::too_many_arguments)] #[allow(clippy::too_many_lines, clippy::too_many_arguments)]
pub(crate) async fn post( pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(password_manager): State<PasswordManager>, State(password_manager): State<PasswordManager>,
State(mailer): State<Mailer>, State(mailer): State<Mailer>,
State(policy_factory): State<Arc<PolicyFactory>>, State(policy_factory): State<Arc<PolicyFactory>>,
@ -102,12 +105,11 @@ pub(crate) async fn post(
cookie_jar: PrivateCookieJar<Encrypter>, cookie_jar: PrivateCookieJar<Encrypter>,
Form(form): Form<ProtectedForm<RegisterForm>>, Form(form): Form<ProtectedForm<RegisterForm>>,
) -> Result<Response, FancyError> { ) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?; let mut repo = PgRepository::from_pool(&pool).await?;
let form = cookie_jar.verify_form(clock.now(), form)?; let form = cookie_jar.verify_form(&clock, form)?;
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock.now(), &mut rng); let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
// Validate the form // Validate the form
let state = { let state = {

View File

@ -30,7 +30,7 @@ use oauth2_types::{
requests::GrantType, requests::GrantType,
scope::{Scope, ScopeToken}, scope::{Scope, ScopeToken},
}; };
use rand::{Rng, RngCore}; use rand::RngCore;
use sqlx::PgConnection; use sqlx::PgConnection;
use tracing::{info_span, Instrument}; use tracing::{info_span, Instrument};
use ulid::Ulid; use ulid::Ulid;
@ -534,7 +534,7 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
)] )]
async fn add_from_config( async fn add_from_config(
&mut self, &mut self,
mut rng: impl Rng + Send, rng: &mut (dyn RngCore + Send),
clock: &dyn Clock, clock: &dyn Clock,
client_id: Ulid, client_id: Ulid,
client_auth_method: OAuthClientAuthenticationMethod, client_auth_method: OAuthClientAuthenticationMethod,
@ -597,7 +597,7 @@ impl<'c> OAuth2ClientRepository for PgOAuth2ClientRepository<'c> {
.iter() .iter()
.map(|uri| { .map(|uri| {
( (
Uuid::from(Ulid::from_datetime_with_source(now.into(), &mut rng)), Uuid::from(Ulid::from_datetime_with_source(now.into(), &mut *rng)),
uri.as_str().to_owned(), uri.as_str().to_owned(),
) )
}) })

View File

@ -10,7 +10,7 @@ async-trait = "0.1.60"
chrono = "0.4.23" chrono = "0.4.23"
thiserror = "1.0.38" thiserror = "1.0.38"
rand = "0.8.5" rand_core = "0.6.4"
url = "2.3.1" url = "2.3.1"
ulid = "1.0.0" ulid = "1.0.0"

View File

@ -28,6 +28,12 @@ pub trait Clock: Sync {
fn now(&self) -> DateTime<Utc>; fn now(&self) -> DateTime<Utc>;
} }
impl<C: Clock + ?Sized> Clock for Box<C> {
fn now(&self) -> DateTime<Utc> {
(**self).now()
}
}
/// A clock which uses the system time /// A clock which uses the system time
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct SystemClock { pub struct SystemClock {

View File

@ -15,7 +15,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use chrono::Duration; use chrono::Duration;
use mas_data_model::{CompatAccessToken, CompatSession}; use mas_data_model::{CompatAccessToken, CompatSession};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::Clock; use crate::Clock;

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{CompatAccessToken, CompatRefreshToken, CompatSession}; use mas_data_model::{CompatAccessToken, CompatRefreshToken, CompatSession};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::Clock; use crate::Clock;

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{CompatSession, Device, User}; use mas_data_model::{CompatSession, Device, User};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::Clock; use crate::Clock;

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{CompatSession, CompatSsoLogin, User}; use mas_data_model::{CompatSession, CompatSsoLogin, User};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use url::Url; use url::Url;

View File

@ -37,8 +37,13 @@ pub(crate) mod repository;
pub mod upstream_oauth2; pub mod upstream_oauth2;
pub mod user; pub mod user;
use rand_core::CryptoRngCore;
pub use self::{ pub use self::{
clock::{Clock, SystemClock}, clock::{Clock, SystemClock},
pagination::{Page, Pagination}, pagination::{Page, Pagination},
repository::Repository, repository::Repository,
}; };
pub type BoxClock = Box<dyn Clock + Send>;
pub type BoxRng = Box<dyn CryptoRngCore + Send>;

View File

@ -15,7 +15,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use chrono::Duration; use chrono::Duration;
use mas_data_model::{AccessToken, Session}; use mas_data_model::{AccessToken, Session};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::Clock; use crate::Clock;

View File

@ -17,7 +17,7 @@ use std::num::NonZeroU32;
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{AuthorizationCode, AuthorizationGrant, Client, Session}; use mas_data_model::{AuthorizationCode, AuthorizationGrant, Client, Session};
use oauth2_types::{requests::ResponseMode, scope::Scope}; use oauth2_types::{requests::ResponseMode, scope::Scope};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use url::Url; use url::Url;

View File

@ -19,7 +19,7 @@ use mas_data_model::{Client, User};
use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod}; use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
use mas_jose::jwk::PublicJsonWebKeySet; use mas_jose::jwk::PublicJsonWebKeySet;
use oauth2_types::{requests::GrantType, scope::Scope}; use oauth2_types::{requests::GrantType, scope::Scope};
use rand::{Rng, RngCore}; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use url::Url; use url::Url;
@ -67,7 +67,7 @@ pub trait OAuth2ClientRepository: Send + Sync {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn add_from_config( async fn add_from_config(
&mut self, &mut self,
mut rng: impl Rng + Send, rng: &mut (dyn RngCore + Send),
clock: &dyn Clock, clock: &dyn Clock,
client_id: Ulid, client_id: Ulid,
client_auth_method: OAuthClientAuthenticationMethod, client_auth_method: OAuthClientAuthenticationMethod,

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{AccessToken, RefreshToken, Session}; use mas_data_model::{AccessToken, RefreshToken, Session};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::Clock; use crate::Clock;

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{AuthorizationGrant, BrowserSession, Session, User}; use mas_data_model::{AuthorizationGrant, BrowserSession, Session, User};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::{pagination::Page, Clock, Pagination}; use crate::{pagination::Page, Clock, Pagination};

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{UpstreamOAuthLink, UpstreamOAuthProvider, User}; use mas_data_model::{UpstreamOAuthLink, UpstreamOAuthProvider, User};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::{pagination::Page, Clock, Pagination}; use crate::{pagination::Page, Clock, Pagination};

View File

@ -16,7 +16,7 @@ use async_trait::async_trait;
use mas_data_model::UpstreamOAuthProvider; use mas_data_model::UpstreamOAuthProvider;
use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod}; use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
use oauth2_types::scope::Scope; use oauth2_types::scope::Scope;
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::{pagination::Page, Clock, Pagination}; use crate::{pagination::Page, Clock, Pagination};

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{UpstreamOAuthAuthorizationSession, UpstreamOAuthLink, UpstreamOAuthProvider}; use mas_data_model::{UpstreamOAuthAuthorizationSession, UpstreamOAuthLink, UpstreamOAuthProvider};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::Clock; use crate::Clock;

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{User, UserEmail, UserEmailVerification}; use mas_data_model::{User, UserEmail, UserEmailVerification};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::{pagination::Page, Clock, Pagination}; use crate::{pagination::Page, Clock, Pagination};

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::User; use mas_data_model::User;
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::Clock; use crate::Clock;

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{Password, User}; use mas_data_model::{Password, User};
use rand::RngCore; use rand_core::RngCore;
use crate::Clock; use crate::Clock;

View File

@ -14,7 +14,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use mas_data_model::{BrowserSession, Password, UpstreamOAuthLink, User}; use mas_data_model::{BrowserSession, Password, UpstreamOAuthLink, User};
use rand::RngCore; use rand_core::RngCore;
use ulid::Ulid; use ulid::Ulid;
use crate::{pagination::Page, Clock, Pagination}; use crate::{pagination::Page, Clock, Pagination};