1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-29 22:01:14 +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-jose",
"oauth2-types",
"rand 0.8.5",
"rand_core 0.6.4",
"thiserror",
"ulid",
"url",

View File

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

View File

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

View File

@ -15,7 +15,7 @@
use chrono::{DateTime, Utc};
use crc::{Crc, CRC_32_ISO_HDLC};
use mas_iana::oauth::OAuthTokenTypeHint;
use rand::{distributions::Alphanumeric, Rng};
use rand::{distributions::Alphanumeric, Rng, RngCore};
use thiserror::Error;
use ulid::Ulid;
@ -193,7 +193,7 @@ impl TokenType {
/// AccessToken.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
.sample_iter(&Alphanumeric)
.take(30)

View File

@ -12,15 +12,20 @@
// See the License for the specific language governing permissions and
// 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_email::Mailer;
use mas_keystore::{Encrypter, Keystore};
use mas_policy::PolicyFactory;
use mas_router::UrlBuilder;
use mas_storage::{BoxClock, BoxRng, SystemClock};
use mas_templates::Templates;
use rand::SeedableRng;
use sqlx::PgPool;
use crate::{passwords::PasswordManager, MatrixHomeserver};
@ -105,3 +110,33 @@ impl FromRef<AppState> for PasswordManager {
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,
},
user::{UserPasswordRepository, UserRepository},
Clock, Repository, SystemClock,
BoxClock, BoxRng, Clock, Repository,
};
use mas_storage_pg::PgRepository;
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none, DurationMilliSeconds};
use sqlx::PgPool;
@ -154,7 +155,6 @@ pub enum RouteError {
InvalidLoginToken,
}
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl IntoResponse for RouteError {
@ -194,18 +194,29 @@ impl IntoResponse for RouteError {
#[tracing::instrument(skip_all, err)]
pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(password_manager): State<PasswordManager>,
State(pool): State<PgPool>,
State(homeserver): State<MatrixHomeserver>,
Json(input): Json<RequestBody>,
) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?;
let (session, user) = match input.credentials {
Credentials::Password {
identifier: Identifier::User { user },
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?,
@ -254,7 +265,7 @@ pub(crate) async fn post(
async fn token_login(
repo: &mut PgRepository,
clock: &SystemClock,
clock: &dyn Clock,
token: &str,
) -> Result<(CompatSession, User), RouteError> {
let login = repo
@ -319,13 +330,13 @@ async fn token_login(
}
async fn user_password_login(
mut rng: &mut (impl RngCore + CryptoRng + Send),
clock: &impl Clock,
password_manager: &PasswordManager,
repo: &mut PgRepository,
username: String,
password: String,
) -> Result<(CompatSession, User), RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
// Find the user
let user = repo
.user()
@ -358,7 +369,7 @@ async fn user_password_login(
repo.user_password()
.add(
&mut rng,
&clock,
clock,
&user,
version,
hashed_password,
@ -371,7 +382,7 @@ async fn user_password_login(
let device = Device::generate(&mut rng);
let session = repo
.compat_session()
.add(&mut rng, &clock, &user, device)
.add(&mut rng, clock, &user, device)
.await?;
Ok((session, user))

View File

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

View File

@ -19,7 +19,7 @@ use axum::{
};
use hyper::StatusCode;
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 rand::distributions::{Alphanumeric, DistString};
use serde::Deserialize;
@ -49,7 +49,6 @@ pub enum RouteError {
InvalidRedirectUrl,
}
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl IntoResponse for RouteError {
@ -58,14 +57,13 @@ impl IntoResponse for RouteError {
}
}
#[tracing::instrument(skip(pool, url_builder), err)]
pub async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>,
State(url_builder): State<UrlBuilder>,
Query(params): Query<Params>,
) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
// Check the redirectUrl parameter
let redirect_url = params.redirect_url.ok_or(RouteError::MissingRedirectUrl)?;
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_storage::{
compat::{CompatAccessTokenRepository, CompatSessionRepository},
Clock, Repository, SystemClock,
BoxClock, Clock, Repository,
};
use mas_storage_pg::PgRepository;
use sqlx::PgPool;
@ -42,7 +42,6 @@ pub enum RouteError {
InvalidAuthorization,
}
impl_from_error_for_route!(sqlx::Error);
impl_from_error_for_route!(mas_storage_pg::DatabaseError);
impl IntoResponse for RouteError {
@ -69,10 +68,10 @@ impl IntoResponse for RouteError {
}
pub(crate) async fn post(
clock: BoxClock,
State(pool): State<PgPool>,
maybe_authorization: Option<TypedHeader<Authorization<Bearer>>>,
) -> Result<impl IntoResponse, RouteError> {
let clock = SystemClock::default();
let mut repo = PgRepository::from_pool(&pool).await?;
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_storage::{
compat::{CompatAccessTokenRepository, CompatRefreshTokenRepository, CompatSessionRepository},
Clock, Repository,
BoxClock, BoxRng, Clock, Repository,
};
use mas_storage_pg::PgRepository;
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<TokenFormatError> for RouteError {
@ -89,10 +88,11 @@ pub struct ResponseBody {
}
pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>,
Json(input): Json<RequestBody>,
) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?;
let token_type = TokenType::check(&input.refresh_token)?;

View File

@ -28,7 +28,7 @@ use std::{convert::Infallible, sync::Arc, time::Duration};
use axum::{
body::{Bytes, HttpBody},
extract::FromRef,
extract::{FromRef, FromRequestParts},
response::{Html, IntoResponse},
routing::{get, on, post, MethodFilter},
Router,
@ -40,9 +40,9 @@ use mas_http::CorsLayerExt;
use mas_keystore::{Encrypter, Keystore};
use mas_policy::PolicyFactory;
use mas_router::{Route, UrlBuilder};
use mas_storage::{BoxClock, BoxRng};
use mas_templates::{ErrorContext, Templates};
use passwords::PasswordManager;
use rand::SeedableRng;
use sqlx::PgPool;
use tower::util::AndThenLayer;
use tower_http::cors::{Any, CorsLayer};
@ -116,6 +116,8 @@ where
S: Clone + Send + Sync + 'static,
Keystore: FromRef<S>,
UrlBuilder: FromRef<S>,
BoxClock: FromRequestParts<S>,
BoxRng: FromRequestParts<S>,
{
Router::new()
.route(
@ -155,6 +157,8 @@ where
PgPool: FromRef<S>,
Encrypter: FromRef<S>,
HttpClientFactory: FromRef<S>,
BoxClock: FromRequestParts<S>,
BoxRng: FromRequestParts<S>,
{
// All those routes are API-like, with a common CORS layer
Router::new()
@ -208,6 +212,8 @@ where
PgPool: FromRef<S>,
MatrixHomeserver: FromRef<S>,
PasswordManager: FromRef<S>,
BoxClock: FromRequestParts<S>,
BoxRng: FromRequestParts<S>,
{
Router::new()
.route(
@ -255,6 +261,8 @@ where
Keystore: FromRef<S>,
HttpClientFactory: FromRef<S>,
PasswordManager: FromRef<S>,
BoxClock: FromRequestParts<S>,
BoxRng: FromRequestParts<S>,
{
Router::new()
.route(
@ -407,16 +415,3 @@ async fn test_state(pool: PgPool) -> Result<AppState, anyhow::Error> {
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_storage::{
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository, OAuth2SessionRepository},
Repository,
BoxClock, BoxRng, Repository,
};
use mas_storage_pg::PgRepository;
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_policy::LoadError);
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);
pub(crate) async fn get(
rng: BoxRng,
clock: BoxClock,
State(policy_factory): State<Arc<PolicyFactory>>,
State(templates): State<Templates>,
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());
};
match complete(grant, session, &policy_factory, repo).await {
match complete(rng, clock, grant, session, &policy_factory, repo).await {
Ok(params) => {
let res = callback_destination.go(&templates, params).await?;
Ok((cookie_jar, res).into_response())
@ -149,7 +150,6 @@ pub enum GrantCompletionError {
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: super::callback::IntoCallbackDestinationError);
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);
pub(crate) async fn complete(
mut rng: BoxRng,
clock: BoxClock,
grant: AuthorizationGrant,
browser_session: BrowserSession,
policy_factory: &PolicyFactory,
mut repo: PgRepository,
) -> Result<AuthorizationResponse<Option<AccessTokenResponse>>, GrantCompletionError> {
let (clock, mut rng) = crate::clock_and_rng();
// Verify that the grant is in a pending stage
if !grant.stage.is_pending() {
return Err(GrantCompletionError::NotPending);

View File

@ -27,7 +27,7 @@ use mas_policy::PolicyFactory;
use mas_router::{PostAuthAction, Route};
use mas_storage::{
oauth2::{OAuth2AuthorizationGrantRepository, OAuth2ClientRepository},
Repository,
BoxClock, BoxRng, Repository,
};
use mas_storage_pg::PgRepository;
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!(self::callback::CallbackDestinationError);
impl_from_error_for_route!(mas_policy::LoadError);
@ -133,13 +132,14 @@ fn resolve_response_mode(
#[allow(clippy::too_many_lines)]
pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(policy_factory): State<Arc<PolicyFactory>>,
State(templates): State<Templates>,
State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>,
Form(params): Form<Params>,
) -> Result<Response, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?;
// 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
Some(user_session) if prompt.contains(&Prompt::None) => {
// 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?,
Err(GrantCompletionError::RequiresConsent) => {
@ -373,7 +381,15 @@ pub(crate) async fn get(
Some(user_session) => {
let grant_id = grant.id;
// 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?,
Err(

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@ use mas_storage::{
OAuth2RefreshTokenRepository, OAuth2SessionRepository,
},
user::BrowserSessionRepository,
Clock, Repository,
BoxClock, BoxRng, Clock, Repository,
};
use mas_storage_pg::PgRepository;
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_keystore::WrongAlgorithmError);
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)]
pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(http_client_factory): State<HttpClientFactory>,
State(key_store): State<Keystore>,
State(url_builder): State<UrlBuilder>,
@ -189,10 +190,19 @@ pub(crate) async fn post(
let reply = match form {
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) => {
refresh_token_grant(&grant, &client, repo).await?
refresh_token_grant(&mut rng, &clock, &grant, &client, repo).await?
}
_ => {
return Err(RouteError::InvalidGrant);
@ -208,14 +218,14 @@ pub(crate) async fn post(
#[allow(clippy::too_many_lines)]
async fn authorization_code_grant(
mut rng: &mut BoxRng,
clock: &impl Clock,
grant: &AuthorizationCodeGrant,
client: &Client,
key_store: &Keystore,
url_builder: &UrlBuilder,
mut repo: PgRepository,
) -> Result<AccessTokenResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let authz_grant = repo
.oauth2_authorization_grant()
.find_by_code(&grant.code)
@ -244,7 +254,7 @@ async fn authorization_code_grant(
.lookup(session_id)
.await?
.ok_or(RouteError::NoSuchOAuthSession)?;
repo.oauth2_session().finish(&clock, session).await?;
repo.oauth2_session().finish(clock, session).await?;
repo.save().await?;
}
@ -302,12 +312,12 @@ async fn authorization_code_grant(
let access_token = repo
.oauth2_access_token()
.add(&mut rng, &clock, &session, access_token_str, ttl)
.add(&mut rng, clock, &session, access_token_str, ttl)
.await?;
let refresh_token = repo
.oauth2_refresh_token()
.add(&mut rng, &clock, &session, &access_token, refresh_token_str)
.add(&mut rng, clock, &session, &access_token, refresh_token_str)
.await?;
let id_token = if session.scope.contains(&scope::OPENID) {
@ -357,7 +367,7 @@ async fn authorization_code_grant(
}
repo.oauth2_authorization_grant()
.exchange(&clock, authz_grant)
.exchange(clock, authz_grant)
.await?;
repo.save().await?;
@ -366,12 +376,12 @@ async fn authorization_code_grant(
}
async fn refresh_token_grant(
mut rng: &mut BoxRng,
clock: &impl Clock,
grant: &RefreshTokenGrant,
client: &Client,
mut repo: PgRepository,
) -> Result<AccessTokenResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let refresh_token = repo
.oauth2_refresh_token()
.find_by_token(&grant.refresh_token)
@ -399,14 +409,14 @@ async fn refresh_token_grant(
let new_access_token = repo
.oauth2_access_token()
.add(&mut rng, &clock, &session, access_token_str.clone(), ttl)
.add(&mut rng, clock, &session, access_token_str.clone(), ttl)
.await?;
let new_refresh_token = repo
.oauth2_refresh_token()
.add(
&mut rng,
&clock,
clock,
&session,
&new_access_token,
refresh_token_str,
@ -415,13 +425,13 @@ async fn refresh_token_grant(
let refresh_token = repo
.oauth2_refresh_token()
.consume(&clock, refresh_token)
.consume(clock, refresh_token)
.await?;
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? {
repo.oauth2_access_token()
.revoke(&clock, access_token)
.revoke(clock, access_token)
.await?;
}
}

View File

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

View File

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

View File

@ -30,7 +30,7 @@ use mas_storage::{
UpstreamOAuthLinkRepository, UpstreamOAuthProviderRepository,
UpstreamOAuthSessionRepository,
},
Clock, Repository,
BoxClock, BoxRng, Clock, Repository,
};
use mas_storage_pg::PgRepository;
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_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::JwksError);
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)]
pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(http_client_factory): State<HttpClientFactory>,
State(pool): State<PgPool>,
State(url_builder): State<UrlBuilder>,
@ -131,8 +132,6 @@ pub(crate) async fn get(
Path(provider_id): Path<Ulid>,
Query(params): Query<QueryParams>,
) -> Result<impl IntoResponse, RouteError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?;
let provider = repo
@ -268,7 +267,7 @@ pub(crate) async fn get(
let cookie_jar = sessions_cookie
.add_link_to_session(session.id, link.id)?
.save(cookie_jar, clock.now());
.save(cookie_jar, &clock);
repo.save().await?;

View File

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

View File

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

View File

@ -24,7 +24,7 @@ use mas_axum_utils::{
use mas_email::Mailer;
use mas_keystore::Encrypter;
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_templates::{EmailAddContext, TemplateContext, Templates};
use serde::Deserialize;
@ -39,14 +39,15 @@ pub struct EmailForm {
}
pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>,
State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
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 maybe_session = session_info.load_session(&mut repo).await?;
@ -68,16 +69,17 @@ pub(crate) async fn get(
}
pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(pool): State<PgPool>,
State(mailer): State<Mailer>,
cookie_jar: PrivateCookieJar<Encrypter>,
Query(query): Query<OptionalPostAuthAction>,
Form(form): Form<ProtectedForm<EmailForm>>,
) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
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 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_keystore::Encrypter;
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_templates::{AccountEmailsContext, EmailVerificationContext, TemplateContext, Templates};
use rand::{distributions::Uniform, Rng};
@ -49,12 +49,12 @@ pub enum ManagementForm {
}
pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>,
State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?;
let (session_info, cookie_jar) = cookie_jar.session_info();
@ -77,7 +77,7 @@ async fn render(
cookie_jar: PrivateCookieJar<Encrypter>,
repo: &mut impl Repository,
) -> 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?;
@ -124,13 +124,14 @@ async fn start_email_verification(
}
pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>,
State(pool): State<PgPool>,
State(mailer): State<Mailer>,
cookie_jar: PrivateCookieJar<Encrypter>,
Form(form): Form<ProtectedForm<ManagementForm>>,
) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?;
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());
};
let form = cookie_jar.verify_form(clock.now(), form)?;
let form = cookie_jar.verify_form(&clock, form)?;
match form {
ManagementForm::Add { email } => {

View File

@ -24,7 +24,7 @@ use mas_axum_utils::{
};
use mas_keystore::Encrypter;
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_templates::{EmailVerificationPageContext, TemplateContext, Templates};
use serde::Deserialize;
@ -39,16 +39,17 @@ pub struct CodeForm {
}
pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>,
State(pool): State<PgPool>,
Query(query): Query<OptionalPostAuthAction>,
Path(id): Path<Ulid>,
cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
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 maybe_session = session_info.load_session(&mut repo).await?;
@ -83,16 +84,16 @@ pub(crate) async fn get(
}
pub(crate) async fn post(
clock: BoxClock,
State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>,
Query(query): Query<OptionalPostAuthAction>,
Path(id): Path<Ulid>,
Form(form): Form<ProtectedForm<CodeForm>>,
) -> Result<Response, FancyError> {
let clock = SystemClock::default();
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 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_storage::{
user::{BrowserSessionRepository, UserEmailRepository},
Clock, Repository,
BoxClock, BoxRng, Repository,
};
use mas_storage_pg::PgRepository;
use mas_templates::{AccountContext, TemplateContext, Templates};
use sqlx::PgPool;
pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>,
State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
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 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_storage::{
user::{BrowserSessionRepository, UserPasswordRepository},
Clock, Repository,
BoxClock, BoxRng, Clock, Repository,
};
use mas_storage_pg::PgRepository;
use mas_templates::{EmptyContext, TemplateContext, Templates};
@ -46,11 +46,12 @@ pub struct ChangeForm {
}
pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>,
State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
let mut repo = PgRepository::from_pool(&pool).await?;
let (session_info, cookie_jar) = cookie_jar.session_info();
@ -72,7 +73,7 @@ async fn render(
session: BrowserSession,
cookie_jar: PrivateCookieJar<Encrypter>,
) -> 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
.with_session(session)
@ -84,16 +85,17 @@ async fn render(
}
pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(password_manager): State<PasswordManager>,
State(templates): State<Templates>,
State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>,
Form(form): Form<ProtectedForm<ChangeForm>>,
) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
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();

View File

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

View File

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

View File

@ -23,19 +23,19 @@ use mas_axum_utils::{
};
use mas_keystore::Encrypter;
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 sqlx::PgPool;
pub(crate) async fn post(
clock: BoxClock,
State(pool): State<PgPool>,
cookie_jar: PrivateCookieJar<Encrypter>,
Form(form): Form<ProtectedForm<Option<PostAuthAction>>>,
) -> Result<impl IntoResponse, FancyError> {
let clock = SystemClock::default();
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();

View File

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

View File

@ -33,7 +33,7 @@ use mas_policy::PolicyFactory;
use mas_router::Route;
use mas_storage::{
user::{BrowserSessionRepository, UserEmailRepository, UserPasswordRepository, UserRepository},
Clock, Repository,
BoxClock, BoxRng, Repository,
};
use mas_storage_pg::PgRepository;
use mas_templates::{
@ -61,15 +61,16 @@ impl ToFormState for RegisterForm {
}
pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
State(templates): State<Templates>,
State(pool): State<PgPool>,
Query(query): Query<OptionalPostAuthAction>,
cookie_jar: PrivateCookieJar<Encrypter>,
) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
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 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)]
pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
State(password_manager): State<PasswordManager>,
State(mailer): State<Mailer>,
State(policy_factory): State<Arc<PolicyFactory>>,
@ -102,12 +105,11 @@ pub(crate) async fn post(
cookie_jar: PrivateCookieJar<Encrypter>,
Form(form): Form<ProtectedForm<RegisterForm>>,
) -> Result<Response, FancyError> {
let (clock, mut rng) = crate::clock_and_rng();
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
let state = {

View File

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

View File

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

View File

@ -28,6 +28,12 @@ pub trait Clock: Sync {
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
#[derive(Clone, Default)]
pub struct SystemClock {

View File

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

View File

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

View File

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

View File

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

View File

@ -37,8 +37,13 @@ pub(crate) mod repository;
pub mod upstream_oauth2;
pub mod user;
use rand_core::CryptoRngCore;
pub use self::{
clock::{Clock, SystemClock},
pagination::{Page, Pagination},
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 chrono::Duration;
use mas_data_model::{AccessToken, Session};
use rand::RngCore;
use rand_core::RngCore;
use ulid::Ulid;
use crate::Clock;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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