1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-07 17:03:01 +03:00

Show a proper 'link expired' page

This commit is contained in:
Quentin Gliech
2024-06-26 15:27:19 +02:00
parent f9f2f4a3be
commit 96df94104e
6 changed files with 117 additions and 24 deletions

View File

@@ -28,8 +28,8 @@ use mas_policy::Policy;
use mas_router::UrlBuilder; use mas_router::UrlBuilder;
use mas_storage::{BoxClock, BoxRepository, BoxRng}; use mas_storage::{BoxClock, BoxRepository, BoxRng};
use mas_templates::{ use mas_templates::{
EmptyContext, ErrorContext, FieldError, FormState, RecoveryFinishContext, EmptyContext, ErrorContext, FieldError, FormState, RecoveryExpiredContext,
RecoveryFinishFormField, TemplateContext, Templates, RecoveryFinishContext, RecoveryFinishFormField, TemplateContext, Templates,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zeroize::Zeroizing; use zeroize::Zeroizing;
@@ -78,12 +78,10 @@ pub(crate) async fn get(
.context("Unknown session")?; .context("Unknown session")?;
if !ticket.active(clock.now()) || session.consumed_at.is_some() { if !ticket.active(clock.now()) || session.consumed_at.is_some() {
// TODO: render a 'link expired' page let context = RecoveryExpiredContext::new(session)
let rendered = templates.render_error( .with_csrf(csrf_token.form_value())
&ErrorContext::new() .with_language(locale);
.with_code("Link expired") let rendered = templates.render_recovery_expired(&context)?;
.with_language(&locale),
)?;
return Ok((cookie_jar, Html(rendered)).into_response()); return Ok((cookie_jar, Html(rendered)).into_response());
} }
@@ -155,12 +153,10 @@ pub(crate) async fn post(
.context("Unknown session")?; .context("Unknown session")?;
if !ticket.active(clock.now()) || session.consumed_at.is_some() { if !ticket.active(clock.now()) || session.consumed_at.is_some() {
// TODO: render a 'link expired' page let context = RecoveryExpiredContext::new(session)
let rendered = templates.render_error( .with_csrf(csrf_token.form_value())
&ErrorContext::new() .with_language(locale);
.with_code("Link expired") let rendered = templates.render_recovery_expired(&context)?;
.with_language(&locale),
)?;
return Ok((cookie_jar, Html(rendered)).into_response()); return Ok((cookie_jar, Html(rendered)).into_response());
} }

View File

@@ -1056,6 +1056,39 @@ impl TemplateContext for RecoveryProgressContext {
} }
} }
/// Context used by the `pages/recovery/expired.html` template
#[derive(Serialize)]
pub struct RecoveryExpiredContext {
session: UserRecoverySession,
}
impl RecoveryExpiredContext {
/// Constructs a context for the recovery expired page
#[must_use]
pub fn new(session: UserRecoverySession) -> Self {
Self { session }
}
}
impl TemplateContext for RecoveryExpiredContext {
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 }]
}
}
/// Fields of the account recovery finish form /// Fields of the account recovery finish form
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]

View File

@@ -46,12 +46,12 @@ pub use self::{
DeviceLinkFormField, EmailAddContext, EmailRecoveryContext, EmailVerificationContext, DeviceLinkFormField, EmailAddContext, EmailRecoveryContext, EmailVerificationContext,
EmailVerificationPageContext, EmptyContext, ErrorContext, FormPostContext, IndexContext, EmailVerificationPageContext, EmptyContext, ErrorContext, FormPostContext, IndexContext,
LoginContext, LoginFormField, NotFoundContext, PolicyViolationContext, PostAuthContext, LoginContext, LoginFormField, NotFoundContext, PolicyViolationContext, PostAuthContext,
PostAuthContextInner, ReauthContext, ReauthFormField, RecoveryFinishContext, PostAuthContextInner, ReauthContext, ReauthFormField, RecoveryExpiredContext,
RecoveryFinishFormField, RecoveryProgressContext, RecoveryStartContext, RecoveryFinishContext, RecoveryFinishFormField, RecoveryProgressContext,
RecoveryStartFormField, RegisterContext, RegisterFormField, SiteBranding, SiteConfigExt, RecoveryStartContext, RecoveryStartFormField, RegisterContext, RegisterFormField,
SiteFeatures, TemplateContext, UpstreamExistingLinkContext, UpstreamRegister, SiteBranding, SiteConfigExt, SiteFeatures, TemplateContext, UpstreamExistingLinkContext,
UpstreamRegisterFormField, UpstreamSuggestLink, WithCaptcha, WithCsrf, WithLanguage, UpstreamRegister, UpstreamRegisterFormField, UpstreamSuggestLink, WithCaptcha, WithCsrf,
WithOptionalSession, WithSession, WithLanguage, WithOptionalSession, WithSession,
}, },
forms::{FieldError, FormError, FormField, FormState, ToFormState}, forms::{FieldError, FormError, FormField, FormState, ToFormState},
}; };
@@ -357,6 +357,9 @@ register_templates! {
/// Render the account recovery finish page /// Render the account recovery finish page
pub fn render_recovery_finish(WithLanguage<WithCsrf<RecoveryFinishContext>>) { "pages/recovery/finish.html" } pub fn render_recovery_finish(WithLanguage<WithCsrf<RecoveryFinishContext>>) { "pages/recovery/finish.html" }
/// Render the account recovery link expired page
pub fn render_recovery_expired(WithLanguage<WithCsrf<RecoveryExpiredContext>>) { "pages/recovery/expired.html" }
/// Render the account recovery disabled page /// Render the account recovery disabled page
pub fn render_recovery_disabled(WithLanguage<EmptyContext>) { "pages/recovery/disabled.html" } pub fn render_recovery_disabled(WithLanguage<EmptyContext>) { "pages/recovery/disabled.html" }
@@ -428,6 +431,7 @@ impl Templates {
check::render_recovery_start(self, now, rng)?; check::render_recovery_start(self, now, rng)?;
check::render_recovery_progress(self, now, rng)?; check::render_recovery_progress(self, now, rng)?;
check::render_recovery_finish(self, now, rng)?; check::render_recovery_finish(self, now, rng)?;
check::render_recovery_expired(self, now, rng)?;
check::render_recovery_disabled(self, now, rng)?; check::render_recovery_disabled(self, now, rng)?;
check::render_reauth(self, now, rng)?; check::render_reauth(self, now, rng)?;
check::render_form_post::<EmptyContext>(self, now, rng)?; check::render_form_post::<EmptyContext>(self, now, rng)?;

View File

@@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
#} #}
{% macro link(text, href="#", class="") %} {% macro link(text, href="#", class="", size="lg") %}
<a class="cpd-button {{ class }}" data-kind="primary" data-size="lg" href="{{ href | prefix_url }}">{{ text }}</a> <a class="cpd-button {{ class }}" data-kind="primary" data-size="{{ size }}" href="{{ href | prefix_url }}">{{ text }}</a>
{% endmacro %} {% endmacro %}
{% macro link_text(text, href="#", class="") %} {% macro link_text(text, href="#", class="") %}
<a class="cpd-link {{ class }}" data-kind="primary" href="{{ href | prefix_url }}">{{ text }}</a> <a class="cpd-link {{ class }}" data-kind="primary" href="{{ href | prefix_url }}">{{ text }}</a>
{% endmacro %} {% endmacro %}
{% macro link_outline(text, href="#", class="") %} {% macro link_outline(text, href="#", class="", size="lg") %}
<a class="cpd-button {{ class }}" data-kind="secondary" data-size="lg" href="{{ href | prefix_url }}">{{ text }}</a> <a class="cpd-button {{ class }}" data-kind="secondary" data-size="{{ size }}" href="{{ href | prefix_url }}">{{ text }}</a>
{% endmacro %} {% endmacro %}
{% macro link_tertiary(text, href="#", class="", size="lg") %} {% macro link_tertiary(text, href="#", class="", size="lg") %}

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 invalid">
{{ icon.error() }}
</div>
<div class="header">
<h1 class="title">{{ _("mas.recovery.expired.heading") }}</h1>
<p class="text [&>span]:font-medium">{{ _("mas.recovery.expired.description", email=session.email) }}</p>
</div>
</header>
<div class="flex flex-col gap-6">
<form class="cpd-form-root" method="POST" action="{{ '/recover/progress/' + session.id | prefix_url }}">
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
{{ button.button(text=_("mas.recovery.expired.resend_email"), type="submit") }}
</form>
{{ button.link_outline(text=_("action.start_over"), href="/login") }}
</div>
{% endblock content %}

View File

@@ -23,6 +23,10 @@
"sign_out": "Sign out", "sign_out": "Sign out",
"@sign_out": { "@sign_out": {
"context": "pages/consent.html:71:28-48, pages/device_consent.html:141:30-50, pages/index.html:36:28-48, pages/policy_violation.html:46:28-48, pages/sso.html:53:28-48, pages/upstream_oauth2/link_mismatch.html:32:24-44, pages/upstream_oauth2/suggest_link.html:40:26-46" "context": "pages/consent.html:71:28-48, pages/device_consent.html:141:30-50, pages/index.html:36:28-48, pages/policy_violation.html:46:28-48, pages/sso.html:53:28-48, pages/upstream_oauth2/link_mismatch.html:32:24-44, pages/upstream_oauth2/suggest_link.html:40:26-46"
},
"start_over": "Start over",
"@start_over": {
"context": "pages/recovery/expired.html:38:32-54"
} }
}, },
"app": { "app": {
@@ -395,6 +399,22 @@
"context": "pages/recovery/disabled.html:26:27-61" "context": "pages/recovery/disabled.html:26:27-61"
} }
}, },
"expired": {
"description": "Request a new email that will be sent to: <span>%(email)s</span>.",
"@description": {
"context": "pages/recovery/expired.html:27:46-104",
"description": "Description on the page shown when a user tries to use an expired recovery link"
},
"heading": "The link to reset your password has expired",
"@heading": {
"context": "pages/recovery/expired.html:26:27-60",
"description": "Title on the page shown when a user tries to use an expired recovery link"
},
"resend_email": "Resend email",
"@resend_email": {
"context": "pages/recovery/expired.html:35:28-66"
}
},
"finish": { "finish": {
"confirm": "Enter new password again", "confirm": "Enter new password again",
"@confirm": { "@confirm": {