1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-11-20 12:02:22 +03:00

Recovery progress page

This commit is contained in:
Quentin Gliech
2024-06-25 13:58:20 +02:00
parent c156a3891e
commit 2e4d868385
8 changed files with 182 additions and 7 deletions

View File

@@ -385,6 +385,10 @@ where
mas_router::AccountRecoveryStart::route(),
get(self::views::recovery::start::get).post(self::views::recovery::start::post),
)
.route(
mas_router::AccountRecoveryProgress::route(),
get(self::views::recovery::progress::get),
)
.route(
mas_router::OAuth2AuthorizationEndpoint::route(),
get(self::oauth2::authorization::get),

View File

@@ -12,4 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.
pub mod progress;
pub mod start;

View File

@@ -0,0 +1,64 @@
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use axum::{
extract::{Path, State},
response::{Html, IntoResponse, Response},
};
use mas_axum_utils::{cookies::CookieJar, csrf::CsrfExt, FancyError, SessionInfoExt};
use mas_router::UrlBuilder;
use mas_storage::{BoxClock, BoxRepository, BoxRng};
use mas_templates::{RecoveryProgressContext, TemplateContext, Templates};
use ulid::Ulid;
use crate::PreferredLanguage;
pub(crate) async fn get(
mut rng: BoxRng,
clock: BoxClock,
mut repo: BoxRepository,
State(templates): State<Templates>,
State(url_builder): State<UrlBuilder>,
PreferredLanguage(locale): PreferredLanguage,
cookie_jar: CookieJar,
Path(id): Path<Ulid>,
) -> Result<Response, FancyError> {
let (session_info, cookie_jar) = cookie_jar.session_info();
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
let maybe_session = session_info.load_session(&mut repo).await?;
if maybe_session.is_some() {
// TODO: redirect to continue whatever action was going on
return Ok((cookie_jar, url_builder.redirect(&mas_router::Index)).into_response());
}
let Some(recovery_session) = repo.user_recovery().lookup_session(id).await? else {
// XXX: is that the right thing to do?
return Ok((
cookie_jar,
url_builder.redirect(&mas_router::AccountRecoveryStart),
)
.into_response());
};
let context = RecoveryProgressContext::new(recovery_session)
.with_csrf(csrf_token.form_value())
.with_language(locale);
repo.save().await?;
let rendered = templates.render_recovery_progress(&context)?;
Ok((cookie_jar, Html(rendered)).into_response())
}

View File

@@ -1023,6 +1023,39 @@ impl TemplateContext for RecoveryStartContext {
}
}
/// Context used by the `pages/recovery/progress.html` template
#[derive(Serialize)]
pub struct RecoveryProgressContext {
session: UserRecoverySession,
}
impl RecoveryProgressContext {
/// Constructs a context for the recovery progress page
#[must_use]
pub fn new(session: UserRecoverySession) -> Self {
Self { session }
}
}
impl TemplateContext for RecoveryProgressContext {
fn sample(now: chrono::DateTime<Utc>, rng: &mut impl Rng) -> Vec<Self>
where
Self: Sized,
{
let session = UserRecoverySession {
id: Ulid::from_datetime_with_source(now.into(), rng),
email: "name@mail.com".to_owned(),
user_agent: UserAgent::parse("Mozilla/5.0".to_owned()),
ip_address: None,
locale: "en".to_owned(),
created_at: now,
consumed_at: None,
};
vec![Self { session }]
}
}
/// Context used by the `pages/upstream_oauth2/{link_mismatch,do_login}.html`
/// templates
#[derive(Serialize)]

View File

@@ -46,11 +46,11 @@ pub use self::{
DeviceLinkFormField, EmailAddContext, EmailRecoveryContext, EmailVerificationContext,
EmailVerificationPageContext, EmptyContext, ErrorContext, FormPostContext, IndexContext,
LoginContext, LoginFormField, NotFoundContext, PolicyViolationContext, PostAuthContext,
PostAuthContextInner, ReauthContext, ReauthFormField, RecoveryStartContext,
RecoveryStartFormField, RegisterContext, RegisterFormField, SiteBranding, SiteConfigExt,
SiteFeatures, TemplateContext, UpstreamExistingLinkContext, UpstreamRegister,
UpstreamRegisterFormField, UpstreamSuggestLink, WithCaptcha, WithCsrf, WithLanguage,
WithOptionalSession, WithSession,
PostAuthContextInner, ReauthContext, ReauthFormField, RecoveryProgressContext,
RecoveryStartContext, RecoveryStartFormField, RegisterContext, RegisterFormField,
SiteBranding, SiteConfigExt, SiteFeatures, TemplateContext, UpstreamExistingLinkContext,
UpstreamRegister, UpstreamRegisterFormField, UpstreamSuggestLink, WithCaptcha, WithCsrf,
WithLanguage, WithOptionalSession, WithSession,
},
forms::{FieldError, FormError, FormField, FormState, ToFormState},
};
@@ -350,6 +350,10 @@ register_templates! {
/// Render the account recovery start page
pub fn render_recovery_start(WithLanguage<WithCsrf<RecoveryStartContext>>) { "pages/recovery/start.html" }
/// Render the account recovery start page
pub fn render_recovery_progress(WithLanguage<WithCsrf<RecoveryProgressContext>>) { "pages/recovery/progress.html" }
/// Render the re-authentication form
pub fn render_reauth(WithLanguage<WithCsrf<WithSession<ReauthContext>>>) { "pages/reauth.html" }
@@ -416,6 +420,7 @@ impl Templates {
check::render_account_add_email(self, now, rng)?;
check::render_account_verify_email(self, now, rng)?;
check::render_recovery_start(self, now, rng)?;
check::render_recovery_progress(self, now, rng)?;
check::render_reauth(self, now, rng)?;
check::render_form_post::<EmptyContext>(self, now, rng)?;
check::render_error(self, now, rng)?;

View File

@@ -26,6 +26,10 @@ limitations under the License.
<a class="cpd-button {{ class }}" data-kind="secondary" data-size="lg" href="{{ href | prefix_url }}">{{ text }}</a>
{% endmacro %}
{% macro link_tertiary(text, href="#", class="", size="lg") %}
<a class="cpd-button {{ class }}" data-kind="tertiary" data-size="{{ size }}" href="{{ href | prefix_url }}">{{ text }}</a>
{% endmacro %}
{% macro button(
text,
name="",
@@ -33,6 +37,7 @@ limitations under the License.
class="",
value="",
disabled=False,
size="lg",
autocomplete=False,
autocorrect=False,
autocapitalize=False) %}
@@ -43,7 +48,7 @@ limitations under the License.
{% if disabled %}disabled{% endif %}
class="cpd-button {{ class }}"
data-kind="primary"
data-size="lg"
data-size="{{ size }}"
{% if autocapitalize %}autocapitilize="{{ autocapitilize }}"{% endif %}
{% if autocomplete %}autocomplete="{{ autocomplete }}"{% endif %}
{% if autocorrect %}autocorrect="{{ autocorrect }}"{% endif %}
@@ -80,6 +85,7 @@ limitations under the License.
class="",
value="",
disabled=False,
size="lg",
autocomplete=False,
autocorrect=False,
autocapitalize=False) %}
@@ -89,7 +95,7 @@ limitations under the License.
type="{{ type }}"
class="cpd-button {{ class }}"
data-kind="secondary"
data-size="lg"
data-size="{{ size }}"
{% if disabled %}disabled{% endif %}
{% if autocapitalize %}autocapitilize="{{ autocapitilize }}"{% endif %}
{% if autocomplete %}autocomplete="{{ autocomplete }}"{% endif %}

View File

@@ -0,0 +1,40 @@
{#
Copyright 2024 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
#}
{% extends "base.html" %}
{% block content %}
<header class="page-heading">
<div class="icon">
{{ icon.send_solid() }}
</div>
<div class="header">
<h1 class="title">{{ _("mas.recovery.progress.heading") }}</h1>
<p class="text [&>span]:font-medium">{{ _("mas.recovery.progress.description", email=session.email) }}</p>
</div>
</header>
<div class="flex flex-col gap-6">
<form class="cpd-form-root" method="POST">
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
{{ button.button_outline(text=_("mas.recovery.progress.resend_email"), type="submit") }}
</form>
{{ button.link_tertiary(text=_("mas.recovery.progress.change_email"), href="/recover") }}
</div>
{% endblock content %}

View File

@@ -376,6 +376,28 @@
}
},
"recovery": {
"progress": {
"change_email": "Try a different email",
"@change_email": {
"context": "pages/recovery/progress.html:38:33-72",
"description": "Button to change the email address for the password recovery link"
},
"description": "We sent an email with a link to reset your password if there's an account using <span>%(email)s</span>.",
"@description": {
"context": "pages/recovery/progress.html:27:46-105",
"description": "The description of the password recovery page, informing the user that an email has been sent to reset their password"
},
"heading": "Check your email",
"@heading": {
"context": "pages/recovery/progress.html:26:27-61",
"description": "The title of the password recovery page, informing the user that an email has been sent to reset their password"
},
"resend_email": "Resend email",
"@resend_email": {
"context": "pages/recovery/progress.html:35:36-75",
"description": "Button to resend the email with the password recovery link"
}
},
"start": {
"description": "An email will be sent with a link to reset your password.",
"@description": {