mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-08-27 22:34:21 +00:00
Remove the reauth view
This commit is contained in:
@@ -371,10 +371,6 @@ where
|
||||
get(self::views::login::get).post(self::views::login::post),
|
||||
)
|
||||
.route(mas_router::Logout::route(), post(self::views::logout::post))
|
||||
.route(
|
||||
mas_router::Reauth::route(),
|
||||
get(self::views::reauth::get).post(self::views::reauth::post),
|
||||
)
|
||||
.route(
|
||||
mas_router::Register::route(),
|
||||
get(self::views::register::get),
|
||||
|
||||
@@ -8,7 +8,6 @@ pub mod app;
|
||||
pub mod index;
|
||||
pub mod login;
|
||||
pub mod logout;
|
||||
pub mod reauth;
|
||||
pub mod recovery;
|
||||
pub mod register;
|
||||
pub mod shared;
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
extract::{Form, Query, State},
|
||||
response::{Html, IntoResponse, Response},
|
||||
};
|
||||
use hyper::StatusCode;
|
||||
use mas_axum_utils::{
|
||||
FancyError, SessionInfoExt,
|
||||
cookies::CookieJar,
|
||||
csrf::{CsrfExt, ProtectedForm},
|
||||
};
|
||||
use mas_router::UrlBuilder;
|
||||
use mas_storage::{
|
||||
BoxClock, BoxRepository, BoxRng,
|
||||
user::{BrowserSessionRepository, UserPasswordRepository},
|
||||
};
|
||||
use mas_templates::{ReauthContext, TemplateContext, Templates};
|
||||
use serde::Deserialize;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use super::shared::OptionalPostAuthAction;
|
||||
use crate::{
|
||||
BoundActivityTracker, PreferredLanguage, SiteConfig,
|
||||
passwords::PasswordManager,
|
||||
session::{SessionOrFallback, load_session_or_fallback},
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub(crate) struct ReauthForm {
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "handlers.views.reauth.get", skip_all, err)]
|
||||
pub(crate) async fn get(
|
||||
mut rng: BoxRng,
|
||||
clock: BoxClock,
|
||||
PreferredLanguage(locale): PreferredLanguage,
|
||||
State(templates): State<Templates>,
|
||||
State(url_builder): State<UrlBuilder>,
|
||||
State(site_config): State<SiteConfig>,
|
||||
activity_tracker: BoundActivityTracker,
|
||||
mut repo: BoxRepository,
|
||||
Query(query): Query<OptionalPostAuthAction>,
|
||||
cookie_jar: CookieJar,
|
||||
) -> Result<Response, FancyError> {
|
||||
if !site_config.password_login_enabled {
|
||||
// XXX: do something better here
|
||||
return Ok(url_builder
|
||||
.redirect(&mas_router::Account::default())
|
||||
.into_response());
|
||||
}
|
||||
|
||||
let (cookie_jar, maybe_session) = match load_session_or_fallback(
|
||||
cookie_jar, &clock, &mut rng, &templates, &locale, &mut repo,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
SessionOrFallback::MaybeSession {
|
||||
cookie_jar,
|
||||
maybe_session,
|
||||
..
|
||||
} => (cookie_jar, maybe_session),
|
||||
SessionOrFallback::Fallback { response } => return Ok(response),
|
||||
};
|
||||
|
||||
let Some(session) = maybe_session else {
|
||||
// If there is no session, redirect to the login screen, keeping the
|
||||
// PostAuthAction
|
||||
let login = mas_router::Login::from(query.post_auth_action);
|
||||
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
|
||||
};
|
||||
|
||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
||||
|
||||
activity_tracker
|
||||
.record_browser_session(&clock, &session)
|
||||
.await;
|
||||
|
||||
let ctx = ReauthContext::default();
|
||||
let next = query.load_context(&mut repo).await?;
|
||||
let ctx = if let Some(next) = next {
|
||||
ctx.with_post_action(next)
|
||||
} else {
|
||||
ctx
|
||||
};
|
||||
let ctx = ctx
|
||||
.with_session(session)
|
||||
.with_csrf(csrf_token.form_value())
|
||||
.with_language(locale);
|
||||
|
||||
let content = templates.render_reauth(&ctx)?;
|
||||
|
||||
Ok((cookie_jar, Html(content)).into_response())
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "handlers.views.reauth.post", skip_all, err)]
|
||||
pub(crate) async fn post(
|
||||
mut rng: BoxRng,
|
||||
clock: BoxClock,
|
||||
PreferredLanguage(locale): PreferredLanguage,
|
||||
State(templates): State<Templates>,
|
||||
State(password_manager): State<PasswordManager>,
|
||||
State(url_builder): State<UrlBuilder>,
|
||||
State(site_config): State<SiteConfig>,
|
||||
mut repo: BoxRepository,
|
||||
Query(query): Query<OptionalPostAuthAction>,
|
||||
cookie_jar: CookieJar,
|
||||
Form(form): Form<ProtectedForm<ReauthForm>>,
|
||||
) -> Result<Response, FancyError> {
|
||||
if !site_config.password_login_enabled {
|
||||
// XXX: do something better here
|
||||
return Ok(StatusCode::METHOD_NOT_ALLOWED.into_response());
|
||||
}
|
||||
|
||||
let form = cookie_jar.verify_form(&clock, form)?;
|
||||
|
||||
let (cookie_jar, maybe_session) = match load_session_or_fallback(
|
||||
cookie_jar, &clock, &mut rng, &templates, &locale, &mut repo,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
SessionOrFallback::MaybeSession {
|
||||
cookie_jar,
|
||||
maybe_session,
|
||||
..
|
||||
} => (cookie_jar, maybe_session),
|
||||
SessionOrFallback::Fallback { response } => return Ok(response),
|
||||
};
|
||||
|
||||
let Some(session) = maybe_session else {
|
||||
// If there is no session, redirect to the login screen, keeping the
|
||||
// PostAuthAction
|
||||
let login = mas_router::Login::from(query.post_auth_action);
|
||||
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
|
||||
};
|
||||
|
||||
// Load the user password
|
||||
let user_password = repo
|
||||
.user_password()
|
||||
.active(&session.user)
|
||||
.await?
|
||||
.context("User has no password")?;
|
||||
|
||||
let password = Zeroizing::new(form.password.as_bytes().to_vec());
|
||||
|
||||
// TODO: recover from errors
|
||||
// Verify the password, and upgrade it on-the-fly if needed
|
||||
let new_password_hash = password_manager
|
||||
.verify_and_upgrade(
|
||||
&mut rng,
|
||||
user_password.version,
|
||||
password,
|
||||
user_password.hashed_password.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let user_password = if let Some((version, new_password_hash)) = new_password_hash {
|
||||
// Save the upgraded password
|
||||
repo.user_password()
|
||||
.add(
|
||||
&mut rng,
|
||||
&clock,
|
||||
&session.user,
|
||||
version,
|
||||
new_password_hash,
|
||||
Some(&user_password),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
user_password
|
||||
};
|
||||
|
||||
// Mark the session as authenticated by the password
|
||||
repo.browser_session()
|
||||
.authenticate_with_password(&mut rng, &clock, &session, &user_password)
|
||||
.await?;
|
||||
|
||||
let cookie_jar = cookie_jar.set_session(&session);
|
||||
repo.save().await?;
|
||||
|
||||
let reply = query.go_next(&url_builder);
|
||||
Ok((cookie_jar, reply).into_response())
|
||||
}
|
||||
@@ -255,66 +255,6 @@ impl SimpleRoute for Logout {
|
||||
const PATH: &'static str = "/logout";
|
||||
}
|
||||
|
||||
/// `GET|POST /reauth`
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Reauth {
|
||||
post_auth_action: Option<PostAuthAction>,
|
||||
}
|
||||
|
||||
impl Reauth {
|
||||
#[must_use]
|
||||
pub fn and_then(action: PostAuthAction) -> Self {
|
||||
Self {
|
||||
post_auth_action: Some(action),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn and_continue_grant(data: Ulid) -> Self {
|
||||
Self {
|
||||
post_auth_action: Some(PostAuthAction::continue_grant(data)),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn and_continue_device_code_grant(data: Ulid) -> Self {
|
||||
Self {
|
||||
post_auth_action: Some(PostAuthAction::continue_device_code_grant(data)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the reauth's post auth action.
|
||||
#[must_use]
|
||||
pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
|
||||
self.post_auth_action.as_ref()
|
||||
}
|
||||
|
||||
pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
|
||||
match &self.post_auth_action {
|
||||
Some(action) => action.go_next(url_builder),
|
||||
None => url_builder.redirect(&Index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Route for Reauth {
|
||||
type Query = PostAuthAction;
|
||||
|
||||
fn route() -> &'static str {
|
||||
"/reauth"
|
||||
}
|
||||
|
||||
fn query(&self) -> Option<&Self::Query> {
|
||||
self.post_auth_action.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<PostAuthAction>> for Reauth {
|
||||
fn from(post_auth_action: Option<PostAuthAction>) -> Self {
|
||||
Self { post_auth_action }
|
||||
}
|
||||
}
|
||||
|
||||
/// `POST /register`
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Register {
|
||||
|
||||
@@ -381,7 +381,7 @@ impl FormField for LoginFormField {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner context used in login and reauth screens. See [`PostAuthContext`].
|
||||
/// Inner context used in login screen. See [`PostAuthContext`].
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum PostAuthContextInner {
|
||||
@@ -420,7 +420,7 @@ pub enum PostAuthContextInner {
|
||||
ManageAccount,
|
||||
}
|
||||
|
||||
/// Context used in login and reauth screens, for the post-auth action to do
|
||||
/// Context used in login screen, for the post-auth action to do
|
||||
#[derive(Serialize)]
|
||||
pub struct PostAuthContext {
|
||||
/// The post auth action params from the URL
|
||||
@@ -734,59 +734,6 @@ impl PolicyViolationContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fields of the reauthentication form
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ReauthFormField {
|
||||
/// The password field
|
||||
Password,
|
||||
}
|
||||
|
||||
impl FormField for ReauthFormField {
|
||||
fn keep(&self) -> bool {
|
||||
match self {
|
||||
Self::Password => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used by the `reauth.html` template
|
||||
#[derive(Serialize, Default)]
|
||||
pub struct ReauthContext {
|
||||
form: FormState<ReauthFormField>,
|
||||
next: Option<PostAuthContext>,
|
||||
}
|
||||
|
||||
impl TemplateContext for ReauthContext {
|
||||
fn sample(_now: chrono::DateTime<Utc>, _rng: &mut impl Rng) -> Vec<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// TODO: samples with errors
|
||||
vec![ReauthContext {
|
||||
form: FormState::default(),
|
||||
next: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
impl ReauthContext {
|
||||
/// Add an error on the reauthentication form
|
||||
#[must_use]
|
||||
pub fn with_form_state(self, form: FormState<ReauthFormField>) -> Self {
|
||||
Self { form, ..self }
|
||||
}
|
||||
|
||||
/// Add a post authentication action to the context
|
||||
#[must_use]
|
||||
pub fn with_post_action(self, next: PostAuthContext) -> Self {
|
||||
Self {
|
||||
next: Some(next),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used by the `sso.html` template
|
||||
#[derive(Serialize)]
|
||||
pub struct CompatSsoContext {
|
||||
|
||||
@@ -38,10 +38,10 @@ pub use self::{
|
||||
DeviceConsentContext, DeviceLinkContext, DeviceLinkFormField, EmailRecoveryContext,
|
||||
EmailVerificationContext, EmptyContext, ErrorContext, FormPostContext, IndexContext,
|
||||
LoginContext, LoginFormField, NotFoundContext, PasswordRegisterContext,
|
||||
PolicyViolationContext, PostAuthContext, PostAuthContextInner, ReauthContext,
|
||||
ReauthFormField, RecoveryExpiredContext, RecoveryFinishContext, RecoveryFinishFormField,
|
||||
RecoveryProgressContext, RecoveryStartContext, RecoveryStartFormField, RegisterContext,
|
||||
RegisterFormField, RegisterStepsDisplayNameContext, RegisterStepsDisplayNameFormField,
|
||||
PolicyViolationContext, PostAuthContext, PostAuthContextInner, RecoveryExpiredContext,
|
||||
RecoveryFinishContext, RecoveryFinishFormField, RecoveryProgressContext,
|
||||
RecoveryStartContext, RecoveryStartFormField, RegisterContext, RegisterFormField,
|
||||
RegisterStepsDisplayNameContext, RegisterStepsDisplayNameFormField,
|
||||
RegisterStepsEmailInUseContext, RegisterStepsVerifyEmailContext,
|
||||
RegisterStepsVerifyEmailFormField, SiteBranding, SiteConfigExt, SiteFeatures,
|
||||
TemplateContext, UpstreamExistingLinkContext, UpstreamRegister, UpstreamRegisterFormField,
|
||||
@@ -372,9 +372,6 @@ register_templates! {
|
||||
/// Render the account recovery disabled page
|
||||
pub fn render_recovery_disabled(WithLanguage<EmptyContext>) { "pages/recovery/disabled.html" }
|
||||
|
||||
/// Render the re-authentication form
|
||||
pub fn render_reauth(WithLanguage<WithCsrf<WithSession<ReauthContext>>>) { "pages/reauth.html" }
|
||||
|
||||
/// Render the form used by the form_post response mode
|
||||
pub fn render_form_post<T: Serialize>(WithLanguage<FormPostContext<T>>) { "form_post.html" }
|
||||
|
||||
@@ -456,7 +453,6 @@ impl Templates {
|
||||
check::render_recovery_expired(self, now, rng)?;
|
||||
check::render_recovery_consumed(self, now, rng)?;
|
||||
check::render_recovery_disabled(self, now, rng)?;
|
||||
check::render_reauth(self, now, rng)?;
|
||||
check::render_form_post::<EmptyContext>(self, now, rng)?;
|
||||
check::render_error(self, now, rng)?;
|
||||
check::render_email_verification_txt(self, now, rng)?;
|
||||
|
||||
Reference in New Issue
Block a user