You've already forked authentication-service
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:
@@ -385,6 +385,10 @@ where
|
|||||||
mas_router::AccountRecoveryStart::route(),
|
mas_router::AccountRecoveryStart::route(),
|
||||||
get(self::views::recovery::start::get).post(self::views::recovery::start::post),
|
get(self::views::recovery::start::get).post(self::views::recovery::start::post),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
mas_router::AccountRecoveryProgress::route(),
|
||||||
|
get(self::views::recovery::progress::get),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
mas_router::OAuth2AuthorizationEndpoint::route(),
|
mas_router::OAuth2AuthorizationEndpoint::route(),
|
||||||
get(self::oauth2::authorization::get),
|
get(self::oauth2::authorization::get),
|
||||||
|
|||||||
@@ -12,4 +12,5 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
pub mod progress;
|
||||||
pub mod start;
|
pub mod start;
|
||||||
|
|||||||
64
crates/handlers/src/views/recovery/progress.rs
Normal file
64
crates/handlers/src/views/recovery/progress.rs
Normal 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())
|
||||||
|
}
|
||||||
@@ -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`
|
/// Context used by the `pages/upstream_oauth2/{link_mismatch,do_login}.html`
|
||||||
/// templates
|
/// templates
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ 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, RecoveryStartContext,
|
PostAuthContextInner, ReauthContext, ReauthFormField, 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},
|
||||||
};
|
};
|
||||||
@@ -350,6 +350,10 @@ register_templates! {
|
|||||||
/// Render the account recovery start page
|
/// Render the account recovery start page
|
||||||
pub fn render_recovery_start(WithLanguage<WithCsrf<RecoveryStartContext>>) { "pages/recovery/start.html" }
|
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
|
/// Render the re-authentication form
|
||||||
pub fn render_reauth(WithLanguage<WithCsrf<WithSession<ReauthContext>>>) { "pages/reauth.html" }
|
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_add_email(self, now, rng)?;
|
||||||
check::render_account_verify_email(self, now, rng)?;
|
check::render_account_verify_email(self, now, rng)?;
|
||||||
check::render_recovery_start(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_reauth(self, now, rng)?;
|
||||||
check::render_form_post::<EmptyContext>(self, now, rng)?;
|
check::render_form_post::<EmptyContext>(self, now, rng)?;
|
||||||
check::render_error(self, now, rng)?;
|
check::render_error(self, now, rng)?;
|
||||||
|
|||||||
@@ -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>
|
<a class="cpd-button {{ class }}" data-kind="secondary" data-size="lg" href="{{ href | prefix_url }}">{{ text }}</a>
|
||||||
{% endmacro %}
|
{% 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(
|
{% macro button(
|
||||||
text,
|
text,
|
||||||
name="",
|
name="",
|
||||||
@@ -33,6 +37,7 @@ limitations under the License.
|
|||||||
class="",
|
class="",
|
||||||
value="",
|
value="",
|
||||||
disabled=False,
|
disabled=False,
|
||||||
|
size="lg",
|
||||||
autocomplete=False,
|
autocomplete=False,
|
||||||
autocorrect=False,
|
autocorrect=False,
|
||||||
autocapitalize=False) %}
|
autocapitalize=False) %}
|
||||||
@@ -43,7 +48,7 @@ limitations under the License.
|
|||||||
{% if disabled %}disabled{% endif %}
|
{% if disabled %}disabled{% endif %}
|
||||||
class="cpd-button {{ class }}"
|
class="cpd-button {{ class }}"
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-size="lg"
|
data-size="{{ size }}"
|
||||||
{% if autocapitalize %}autocapitilize="{{ autocapitilize }}"{% endif %}
|
{% if autocapitalize %}autocapitilize="{{ autocapitilize }}"{% endif %}
|
||||||
{% if autocomplete %}autocomplete="{{ autocomplete }}"{% endif %}
|
{% if autocomplete %}autocomplete="{{ autocomplete }}"{% endif %}
|
||||||
{% if autocorrect %}autocorrect="{{ autocorrect }}"{% endif %}
|
{% if autocorrect %}autocorrect="{{ autocorrect }}"{% endif %}
|
||||||
@@ -80,6 +85,7 @@ limitations under the License.
|
|||||||
class="",
|
class="",
|
||||||
value="",
|
value="",
|
||||||
disabled=False,
|
disabled=False,
|
||||||
|
size="lg",
|
||||||
autocomplete=False,
|
autocomplete=False,
|
||||||
autocorrect=False,
|
autocorrect=False,
|
||||||
autocapitalize=False) %}
|
autocapitalize=False) %}
|
||||||
@@ -89,7 +95,7 @@ limitations under the License.
|
|||||||
type="{{ type }}"
|
type="{{ type }}"
|
||||||
class="cpd-button {{ class }}"
|
class="cpd-button {{ class }}"
|
||||||
data-kind="secondary"
|
data-kind="secondary"
|
||||||
data-size="lg"
|
data-size="{{ size }}"
|
||||||
{% if disabled %}disabled{% endif %}
|
{% if disabled %}disabled{% endif %}
|
||||||
{% if autocapitalize %}autocapitilize="{{ autocapitilize }}"{% endif %}
|
{% if autocapitalize %}autocapitilize="{{ autocapitilize }}"{% endif %}
|
||||||
{% if autocomplete %}autocomplete="{{ autocomplete }}"{% endif %}
|
{% if autocomplete %}autocomplete="{{ autocomplete }}"{% endif %}
|
||||||
|
|||||||
40
templates/pages/recovery/progress.html
Normal file
40
templates/pages/recovery/progress.html
Normal 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 %}
|
||||||
@@ -376,6 +376,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"recovery": {
|
"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": {
|
"start": {
|
||||||
"description": "An email will be sent with a link to reset your password.",
|
"description": "An email will be sent with a link to reset your password.",
|
||||||
"@description": {
|
"@description": {
|
||||||
|
|||||||
Reference in New Issue
Block a user