diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index 286ceb4b3..882684751 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -383,11 +383,6 @@ where get(self::views::account::emails::verify::get) .post(self::views::account::emails::verify::post), ) - .route( - mas_router::AccountAddEmail::route(), - get(self::views::account::emails::add::get) - .post(self::views::account::emails::add::post), - ) .route( mas_router::AccountRecoveryStart::route(), get(self::views::recovery::start::get).post(self::views::recovery::start::post), diff --git a/crates/handlers/src/views/account/emails/add.rs b/crates/handlers/src/views/account/emails/add.rs deleted file mode 100644 index fbdbccdd5..000000000 --- a/crates/handlers/src/views/account/emails/add.rs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2024 New Vector Ltd. -// Copyright 2022-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 axum::{ - extract::{Form, Query, State}, - response::{Html, IntoResponse, Response}, -}; -use mas_axum_utils::{ - cookies::CookieJar, - csrf::{CsrfExt, ProtectedForm}, - FancyError, SessionInfoExt, -}; -use mas_data_model::SiteConfig; -use mas_policy::Policy; -use mas_router::UrlBuilder; -use mas_storage::{ - queue::{QueueJobRepositoryExt as _, VerifyEmailJob}, - user::UserEmailRepository, - BoxClock, BoxRepository, BoxRng, -}; -use mas_templates::{EmailAddContext, ErrorContext, TemplateContext, Templates}; -use serde::Deserialize; - -use crate::{views::shared::OptionalPostAuthAction, BoundActivityTracker, PreferredLanguage}; - -#[derive(Deserialize, Debug)] -pub struct EmailForm { - email: String, -} - -#[tracing::instrument(name = "handlers.views.account_email_add.get", skip_all, err)] -pub(crate) async fn get( - mut rng: BoxRng, - clock: BoxClock, - PreferredLanguage(locale): PreferredLanguage, - State(templates): State, - State(url_builder): State, - State(site_config): State, - activity_tracker: BoundActivityTracker, - mut repo: BoxRepository, - cookie_jar: CookieJar, -) -> Result { - let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng); - let (session_info, cookie_jar) = cookie_jar.session_info(); - - let maybe_session = session_info.load_session(&mut repo).await?; - - let Some(session) = maybe_session else { - let login = mas_router::Login::default(); - return Ok((cookie_jar, url_builder.redirect(&login)).into_response()); - }; - - if !site_config.email_change_allowed { - // XXX: this may not be the best error message, it's not translatable - return Err(FancyError::new( - ErrorContext::new() - .with_description("Email change is not allowed".to_owned()) - .with_details("The site configuration does not allow email changes".to_owned()), - )); - } - - activity_tracker - .record_browser_session(&clock, &session) - .await; - - let ctx = EmailAddContext::new() - .with_session(session) - .with_csrf(csrf_token.form_value()) - .with_language(locale); - - let content = templates.render_account_add_email(&ctx)?; - - Ok((cookie_jar, Html(content)).into_response()) -} - -#[tracing::instrument(name = "handlers.views.account_email_add.post", skip_all, err)] -pub(crate) async fn post( - mut rng: BoxRng, - clock: BoxClock, - mut repo: BoxRepository, - PreferredLanguage(locale): PreferredLanguage, - mut policy: Policy, - cookie_jar: CookieJar, - State(url_builder): State, - State(site_config): State, - activity_tracker: BoundActivityTracker, - Query(query): Query, - Form(form): Form>, -) -> Result { - let form = cookie_jar.verify_form(&clock, form)?; - let (session_info, cookie_jar) = cookie_jar.session_info(); - - let maybe_session = session_info.load_session(&mut repo).await?; - - let Some(session) = maybe_session else { - let login = mas_router::Login::default(); - return Ok((cookie_jar, url_builder.redirect(&login)).into_response()); - }; - - // XXX: we really should show human readable errors on the form here - if !site_config.email_change_allowed { - return Err(FancyError::new( - ErrorContext::new() - .with_description("Email change is not allowed".to_owned()) - .with_details("The site configuration does not allow email changes".to_owned()), - )); - } - - // Validate the email address - if form.email.parse::().is_err() { - return Err(anyhow::anyhow!("Invalid email address").into()); - } - - // Run the email policy - let res = policy.evaluate_email(&form.email).await?; - if !res.valid() { - return Err(FancyError::new( - ErrorContext::new() - .with_description(format!("Email address {:?} denied by policy", form.email)) - .with_details(format!("{res}")), - )); - } - - // Find an existing email address - let existing_user_email = repo.user_email().find(&session.user, &form.email).await?; - let user_email = if let Some(user_email) = existing_user_email { - user_email - } else { - repo.user_email() - .add(&mut rng, &clock, &session.user, form.email) - .await? - }; - - // If the email was not confirmed, send a confirmation email & redirect to the - // verify page - let next = if user_email.confirmed_at.is_none() { - repo.queue_job() - .schedule_job( - &mut rng, - &clock, - VerifyEmailJob::new(&user_email).with_language(locale.to_string()), - ) - .await?; - - let next = mas_router::AccountVerifyEmail::new(user_email.id); - let next = if let Some(action) = query.post_auth_action { - next.and_then(action) - } else { - next - }; - - url_builder.redirect(&next) - } else { - query.go_next_or_default(&url_builder, &mas_router::Account::default()) - }; - - repo.save().await?; - - activity_tracker - .record_browser_session(&clock, &session) - .await; - - Ok((cookie_jar, next).into_response()) -} diff --git a/crates/handlers/src/views/account/emails/mod.rs b/crates/handlers/src/views/account/emails/mod.rs index 935562d04..86aa5de33 100644 --- a/crates/handlers/src/views/account/emails/mod.rs +++ b/crates/handlers/src/views/account/emails/mod.rs @@ -1,8 +1,7 @@ -// Copyright 2024 New Vector Ltd. +// Copyright 2024, 2025 New Vector Ltd. // Copyright 2022-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. -pub mod add; pub mod verify; diff --git a/crates/router/src/endpoints.rs b/crates/router/src/endpoints.rs index 32faf919e..d47368167 100644 --- a/crates/router/src/endpoints.rs +++ b/crates/router/src/endpoints.rs @@ -488,31 +488,6 @@ impl Route for AccountVerifyEmail { } } -/// `GET /add-email` -#[derive(Default, Debug, Clone)] -pub struct AccountAddEmail { - post_auth_action: Option, -} - -impl Route for AccountAddEmail { - type Query = PostAuthAction; - fn route() -> &'static str { - "/add-email" - } - - fn query(&self) -> Option<&Self::Query> { - self.post_auth_action.as_ref() - } -} - -impl AccountAddEmail { - #[must_use] - pub fn and_then(mut self, action: PostAuthAction) -> Self { - self.post_auth_action = Some(action); - self - } -} - /// Actions parameters as defined by MSC2965 #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "action")] diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 8e8dd2e4e..0fe138112 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -992,51 +992,6 @@ impl TemplateContext for EmailVerificationPageContext { } } -/// Fields of the account email add form -#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum EmailAddFormField { - /// The email - Email, -} - -impl FormField for EmailAddFormField { - fn keep(&self) -> bool { - match self { - Self::Email => true, - } - } -} - -/// Context used by the `pages/account/verify.html` templates -#[derive(Serialize, Default)] -pub struct EmailAddContext { - form: FormState, -} - -impl EmailAddContext { - /// Constructs a context for the email add page - #[must_use] - pub fn new() -> Self { - Self::default() - } - - /// Set the form state - #[must_use] - pub fn with_form_state(form: FormState) -> Self { - Self { form } - } -} - -impl TemplateContext for EmailAddContext { - fn sample(_now: chrono::DateTime, _rng: &mut impl Rng) -> Vec - where - Self: Sized, - { - vec![Self::default()] - } -} - /// Fields of the account recovery start form #[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)] #[serde(rename_all = "snake_case")] diff --git a/crates/templates/src/lib.rs b/crates/templates/src/lib.rs index 7faea6b2c..3ed01afc2 100644 --- a/crates/templates/src/lib.rs +++ b/crates/templates/src/lib.rs @@ -35,16 +35,15 @@ mod macros; pub use self::{ context::{ ApiDocContext, AppContext, CompatSsoContext, ConsentContext, DeviceConsentContext, - DeviceLinkContext, DeviceLinkFormField, EmailAddContext, EmailRecoveryContext, - EmailVerificationContext, EmailVerificationPageContext, EmptyContext, ErrorContext, - FormPostContext, IndexContext, LoginContext, LoginFormField, NotFoundContext, - PasswordRegisterContext, PolicyViolationContext, PostAuthContext, PostAuthContextInner, - ReauthContext, ReauthFormField, RecoveryExpiredContext, RecoveryFinishContext, - RecoveryFinishFormField, RecoveryProgressContext, RecoveryStartContext, - RecoveryStartFormField, RegisterContext, RegisterFormField, SiteBranding, SiteConfigExt, - SiteFeatures, TemplateContext, UpstreamExistingLinkContext, UpstreamRegister, - UpstreamRegisterFormField, UpstreamSuggestLink, WithCaptcha, WithCsrf, WithLanguage, - WithOptionalSession, WithSession, + DeviceLinkContext, DeviceLinkFormField, EmailRecoveryContext, EmailVerificationContext, + EmailVerificationPageContext, EmptyContext, ErrorContext, FormPostContext, IndexContext, + LoginContext, LoginFormField, NotFoundContext, PasswordRegisterContext, + PolicyViolationContext, PostAuthContext, PostAuthContextInner, ReauthContext, + ReauthFormField, RecoveryExpiredContext, RecoveryFinishContext, RecoveryFinishFormField, + RecoveryProgressContext, RecoveryStartContext, RecoveryStartFormField, RegisterContext, + RegisterFormField, SiteBranding, SiteConfigExt, SiteFeatures, TemplateContext, + UpstreamExistingLinkContext, UpstreamRegister, UpstreamRegisterFormField, + UpstreamSuggestLink, WithCaptcha, WithCsrf, WithLanguage, WithOptionalSession, WithSession, }, forms::{FieldError, FormError, FormField, FormState, ToFormState}, }; @@ -347,9 +346,6 @@ register_templates! { /// Render the email verification page pub fn render_account_verify_email(WithLanguage>>) { "pages/account/emails/verify.html" } - /// Render the email verification page - pub fn render_account_add_email(WithLanguage>>) { "pages/account/emails/add.html" } - /// Render the account recovery start page pub fn render_recovery_start(WithLanguage>) { "pages/recovery/start.html" } @@ -433,7 +429,6 @@ impl Templates { check::render_policy_violation(self, now, rng)?; check::render_sso_login(self, now, rng)?; check::render_index(self, now, rng)?; - 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)?; diff --git a/templates/pages/account/emails/add.html b/templates/pages/account/emails/add.html deleted file mode 100644 index d84e4e96b..000000000 --- a/templates/pages/account/emails/add.html +++ /dev/null @@ -1,39 +0,0 @@ -{# -Copyright 2024 New Vector Ltd. -Copyright 2022-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. --#} - -{% extends "base.html" %} - -{% block content %} -
-
- {{ icon.email_solid() }} -
- -
-

{{ _("mas.add_email.heading") }}

-

{{ _("mas.add_email.description") }}

-
-
- -
- {% if form.errors is not empty %} - {% for error in form.errors %} -
- {{ errors.form_error_message(error=error) }} -
- {% endfor %} - {% endif %} - - - {% call(f) field.field(label=_("common.email_address"), name="email", form_state=form) %} - - {% endcall %} - - {{ button.button(text=_("action.continue")) }} -
-{% endblock content %}