mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-08-28 11:54:11 +00:00
Add a welcome-back mode to /login for trusted id_token_hint targets
When /login continues an authorization grant carrying a trusted target_user_id (the resolved outcome of a verified id_token_hint), the logged-out branch now streamlines re-authentication: - if the target's last authentication was via an upstream provider, auto-redirect to that provider's authorize endpoint; - otherwise render a dedicated "welcome back" template naming the target user with a password field, posting to the existing /login POST. Untrusted login_hint keeps today's generic pre-fill behaviour. Also deletes the orphaned templates/pages/reauth.html. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1b248e3e10
commit
a87b3f0e5e
+1018
-15
File diff suppressed because it is too large
Load Diff
@@ -616,6 +616,68 @@ impl LoginContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used by the `login/welcome_back.html` template
|
||||
///
|
||||
/// Rendered for the streamlined re-authentication of a known, trusted target
|
||||
/// user (resolved from an `id_token_hint`) when their last authentication was
|
||||
/// password-based.
|
||||
#[derive(Serialize)]
|
||||
pub struct WelcomeBackContext {
|
||||
username: String,
|
||||
matrix_user: MatrixUser,
|
||||
form: FormState<LoginFormField>,
|
||||
next: Option<PostAuthContext>,
|
||||
}
|
||||
|
||||
impl TemplateContext for WelcomeBackContext {
|
||||
fn sample<R: Rng>(
|
||||
_now: chrono::DateTime<Utc>,
|
||||
_rng: &mut R,
|
||||
_locales: &[DataLocale],
|
||||
) -> BTreeMap<SampleIdentifier, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
sample_list(vec![WelcomeBackContext {
|
||||
username: "alice".to_owned(),
|
||||
matrix_user: MatrixUser {
|
||||
mxid: "@alice:example.com".to_owned(),
|
||||
display_name: Some("Alice".to_owned()),
|
||||
},
|
||||
form: FormState::default(),
|
||||
next: None,
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl WelcomeBackContext {
|
||||
/// Create a new context for the given target username and Matrix user
|
||||
#[must_use]
|
||||
pub fn new(username: String, matrix_user: MatrixUser) -> Self {
|
||||
Self {
|
||||
username,
|
||||
matrix_user,
|
||||
form: FormState::default(),
|
||||
next: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the form state
|
||||
#[must_use]
|
||||
pub fn with_form_state(self, form: FormState<LoginFormField>) -> Self {
|
||||
Self { form, ..self }
|
||||
}
|
||||
|
||||
/// Add a post authentication action to the context
|
||||
#[must_use]
|
||||
pub fn with_post_action(self, context: PostAuthContext) -> Self {
|
||||
Self {
|
||||
next: Some(context),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fields of the registration form
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
// Copyright 2025, 2026 Element Creations Ltd.
|
||||
// Copyright 2024, 2025 New Vector Ltd.
|
||||
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
@@ -51,7 +51,8 @@ pub use self::{
|
||||
RegisterStepsRegistrationTokenFormField, RegisterStepsVerifyEmailContext,
|
||||
RegisterStepsVerifyEmailFormField, SiteBranding, SiteConfigExt, SiteFeatures,
|
||||
TemplateContext, UpstreamExistingLinkContext, UpstreamRegister, UpstreamRegisterFormField,
|
||||
UpstreamSuggestLink, WithCaptcha, WithCsrf, WithLanguage, WithOptionalSession, WithSession,
|
||||
UpstreamSuggestLink, WelcomeBackContext, WithCaptcha, WithCsrf, WithLanguage,
|
||||
WithOptionalSession, WithSession,
|
||||
},
|
||||
forms::{FieldError, FormError, FormField, FormState, ToFormState},
|
||||
};
|
||||
@@ -371,6 +372,9 @@ register_templates! {
|
||||
/// Render the login page
|
||||
pub fn render_login(WithLanguage<WithCsrf<LoginContext>>) { "pages/login.html" }
|
||||
|
||||
/// Render the streamlined "welcome back" re-authentication page
|
||||
pub fn render_welcome_back(WithLanguage<WithCsrf<WelcomeBackContext>>) { "pages/login/welcome_back.html" }
|
||||
|
||||
/// Render the registration page
|
||||
pub fn render_register(WithLanguage<WithCsrf<RegisterContext>>) { "pages/register/index.html" }
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
{#
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
-#}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<header class="page-heading">
|
||||
<div class="icon">
|
||||
{{ icon.user_profile_solid() }}
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<h1 class="title">{{ _("mas.login.welcome_back.headline") }}</h1>
|
||||
<p class="text">{{ _("mas.login.welcome_back.description") }}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex flex-col gap-6">
|
||||
{% set initial -%}
|
||||
{%- if matrix_user.display_name -%}{{- matrix_user.display_name[0] | upper -}}{%- else -%}{{- matrix_user.mxid[1] | upper -}}{%- endif -%}
|
||||
{%- endset %}
|
||||
<section class="flex items-center p-4 gap-4 border border-[var(--cpd-color-gray-400)] rounded-xl">
|
||||
<div class="avatar-placeholder" data-color="{{ matrix_user.mxid | id_color_hash }}">{{ initial }}</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-primary cpd-text-body-lg-semibold">{{ matrix_user.display_name or username }}</div>
|
||||
<div class="text-secondary cpd-text-body-md-regular">{{ matrix_user.mxid }}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<form method="POST" class="cpd-form-root">
|
||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||
<input type="hidden" name="username" value="{{ username }}" />
|
||||
|
||||
{% if form.errors is not empty %}
|
||||
{% for error in form.errors %}
|
||||
<div class="text-critical font-medium">
|
||||
{{ errors.form_error_message(error=error) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% call(f) field.field(label=_("common.password"), name="password", form_state=form) %}
|
||||
<input {{ field.attributes(f) }} class="cpd-text-control" type="password" autocomplete="current-password" required autofocus />
|
||||
{% endcall %}
|
||||
|
||||
{% if features.account_recovery %}
|
||||
{{ button.link_text(text=_("mas.login.forgot_password"), href="/recover", class="self-center") }}
|
||||
{% endif %}
|
||||
|
||||
{{ button.button(text=_("action.continue")) }}
|
||||
</form>
|
||||
|
||||
{% if next and next.kind == "continue_authorization_grant" %}
|
||||
{{ back_to_client.link(
|
||||
text=_("action.cancel"),
|
||||
destructive=True,
|
||||
uri=next.grant.redirect_uri,
|
||||
mode=next.grant.response_mode,
|
||||
params=dict(error="access_denied", state=next.grant.state)
|
||||
) }}
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock content %}
|
||||
@@ -1,54 +0,0 @@
|
||||
{#
|
||||
Copyright 2024, 2025 New Vector Ltd.
|
||||
Copyright 2021-2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
-#}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<header class="page-heading">
|
||||
<div class="icon">
|
||||
{{ icon.lock() }}
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<h1 class="title">Hi {{ current_session.user.username }}</h1>
|
||||
<p class="text">To continue, please verify it's you:</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex flex-col gap-6">
|
||||
<form method="POST" class="cpd-form-root">
|
||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
||||
{# TODO: errors #}
|
||||
|
||||
{% call(f) field.field(label=_("common.password"), name="password", form_state=form) %}
|
||||
<input {{ field.attributes(f) }} class="cpd-text-control" type="password" autocomplete="password" required />
|
||||
{% endcall %}
|
||||
|
||||
{{ button.button(text=_("action.continue")) }}
|
||||
</form>
|
||||
|
||||
{% if next and next.kind == "continue_authorization_grant" %}
|
||||
{{ back_to_client.link(
|
||||
text=_("action.cancel"),
|
||||
destructive=True,
|
||||
uri=next.grant.redirect_uri,
|
||||
mode=next.grant.response_mode,
|
||||
params=dict(error="access_denied", state=next.grant.state)
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="flex gap-1 justify-center items-center">
|
||||
<p class="cpd-text-secondary cpd-text-body-md-regular">
|
||||
Not {{ current_session.user.username }}?
|
||||
</p>
|
||||
|
||||
{% set post_logout_action = next["params"] | default({}) %}
|
||||
{{ logout.button(text="Sign out", csrf_token=csrf_token, post_logout_action=post_logout_action, as_link=true) }}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
+14
-4
@@ -6,11 +6,11 @@
|
||||
},
|
||||
"cancel": "Cancel",
|
||||
"@cancel": {
|
||||
"context": "pages/consent.html:81:11-29, pages/device_consent.html:179:13-31, pages/device_link.html:45:33-51, pages/policy_violation.html:70:15-33, pages/reauth.html:37:13-31"
|
||||
"context": "pages/consent.html:81:11-29, pages/device_consent.html:179:13-31, pages/device_link.html:45:33-51, pages/login/welcome_back.html:59:13-31, pages/policy_violation.html:70:15-33"
|
||||
},
|
||||
"continue": "Continue",
|
||||
"@continue": {
|
||||
"context": "form_post.html:25:28-48, pages/consent.html:71:28-48, pages/device_link.html:42:28-48, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:77:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/registration_token.html:41:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:52:28-48"
|
||||
"context": "form_post.html:25:28-48, pages/consent.html:71:28-48, pages/device_link.html:42:28-48, pages/login.html:68:30-50, pages/login/welcome_back.html:54:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:77:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/registration_token.html:41:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:52:28-48"
|
||||
},
|
||||
"create_account": "Create Account",
|
||||
"@create_account": {
|
||||
@@ -91,7 +91,7 @@
|
||||
},
|
||||
"password": "Password",
|
||||
"@password": {
|
||||
"context": "pages/login.html:56:37-57, pages/reauth.html:28:35-55, pages/register/password.html:45:33-53"
|
||||
"context": "pages/login.html:56:37-57, pages/login/welcome_back.html:46:35-55, pages/register/password.html:45:33-53"
|
||||
},
|
||||
"password_confirm": "Confirm password",
|
||||
"@password_confirm": {
|
||||
@@ -466,7 +466,7 @@
|
||||
},
|
||||
"forgot_password": "Forgot password?",
|
||||
"@forgot_password": {
|
||||
"context": "pages/login.html:61:35-65",
|
||||
"context": "pages/login.html:61:35-65, pages/login/welcome_back.html:51:33-63",
|
||||
"description": "On the login page, link to the account recovery process"
|
||||
},
|
||||
"headline": "Sign in",
|
||||
@@ -490,6 +490,16 @@
|
||||
"username_or_email": "Username or Email",
|
||||
"@username_or_email": {
|
||||
"context": "pages/login.html:47:39-71"
|
||||
},
|
||||
"welcome_back": {
|
||||
"description": "Re-enter your password to continue:",
|
||||
"@description": {
|
||||
"context": "pages/login/welcome_back.html:18:25-64"
|
||||
},
|
||||
"headline": "Confirm it's you",
|
||||
"@headline": {
|
||||
"context": "pages/login/welcome_back.html:17:27-63"
|
||||
}
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
|
||||
Reference in New Issue
Block a user