From 941ab21fc5846a40146d4f43a265ff42210ae306 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 14 Jan 2025 12:57:01 +0100 Subject: [PATCH] Make the password registration create a user_registration --- .../handlers/src/views/register/password.rs | 77 +++++++++++-------- crates/tasks/src/email.rs | 23 +++++- crates/templates/src/context.rs | 8 +- templates/emails/verification.html | 8 +- templates/emails/verification.txt | 8 +- translations/en.json | 14 ++-- 6 files changed, 95 insertions(+), 43 deletions(-) diff --git a/crates/handlers/src/views/register/password.rs b/crates/handlers/src/views/register/password.rs index 5f30a9385..72ff482e0 100644 --- a/crates/handlers/src/views/register/password.rs +++ b/crates/handlers/src/views/register/password.rs @@ -24,8 +24,8 @@ use mas_matrix::BoxHomeserverConnection; use mas_policy::Policy; use mas_router::UrlBuilder; use mas_storage::{ - queue::{ProvisionUserJob, QueueJobRepositoryExt as _}, - user::{BrowserSessionRepository, UserEmailRepository, UserPasswordRepository, UserRepository}, + queue::{QueueJobRepositoryExt as _, SendEmailAuthenticationCodeJob}, + user::{UserEmailRepository, UserRepository}, BoxClock, BoxRepository, BoxRng, RepositoryAccess, }; use mas_templates::{ @@ -141,6 +141,8 @@ pub(crate) async fn post( Form(form): Form>, ) -> Result { let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned())); + + let ip_address = activity_tracker.ip(); if !site_config.password_registration_enabled { return Ok(StatusCode::METHOD_NOT_ALLOWED.into_response()); } @@ -296,49 +298,62 @@ pub(crate) async fn post( return Ok((cookie_jar, Html(content)).into_response()); } - let user = repo.user().add(&mut rng, &clock, form.username).await?; - - if let Some(tos_uri) = &site_config.tos_uri { - repo.user_terms() - .accept_terms(&mut rng, &clock, &user, tos_uri.clone()) - .await?; - } + let post_auth_action = query + .post_auth_action + .map(serde_json::to_value) + .transpose()?; + let registration = repo + .user_registration() + .add(&mut rng, &clock, ip_address, user_agent, post_auth_action) + .await?; - let password = Zeroizing::new(form.password.into_bytes()); - let (version, hashed_password) = password_manager.hash(&mut rng, password).await?; - let user_password = repo - .user_password() - .add(&mut rng, &clock, &user, version, hashed_password, None) + let registration = repo + .user_registration() + .set_username(registration, form.username) .await?; - let user_email = repo + let registration = if let Some(tos_uri) = &site_config.tos_uri { + repo.user_registration() + .set_terms_url(registration, tos_uri.clone()) + .await? + } else { + registration + }; + + // Create a new user email authentication session + let user_email_authentication = repo .user_email() - .add(&mut rng, &clock, &user, form.email) + .add_authentication_for_registration(&mut rng, &clock, form.email, ®istration) .await?; - let next = mas_router::AccountVerifyEmail::new(user_email.id).and_maybe(query.post_auth_action); - - let session = repo - .browser_session() - .add(&mut rng, &clock, &user, user_agent) + // Schedule a job to verify the email + repo.queue_job() + .schedule_job( + &mut rng, + &clock, + SendEmailAuthenticationCodeJob::new(&user_email_authentication, locale.to_string()), + ) .await?; - repo.browser_session() - .authenticate_with_password(&mut rng, &clock, &session, &user_password) + let registration = repo + .user_registration() + .set_email_authentication(registration, &user_email_authentication) .await?; - repo.queue_job() - .schedule_job(&mut rng, &clock, ProvisionUserJob::new(&user)) + // Hash the password + let password = Zeroizing::new(form.password.into_bytes()); + let (version, hashed_password) = password_manager.hash(&mut rng, password).await?; + + // Add the password to the registration + let registration = repo + .user_registration() + .set_password(registration, hashed_password, version) .await?; repo.save().await?; - activity_tracker - .record_browser_session(&clock, &session) - .await; - - let cookie_jar = cookie_jar.set_session(&session); - Ok((cookie_jar, url_builder.redirect(&next)).into_response()) + // TODO: redirect to the next step on the registration + Ok(format!("{}", registration.id).into_response()) } async fn render( diff --git a/crates/tasks/src/email.rs b/crates/tasks/src/email.rs index fe2c2a51a..f2f343a29 100644 --- a/crates/tasks/src/email.rs +++ b/crates/tasks/src/email.rs @@ -78,6 +78,22 @@ impl RunnableJob for SendEmailAuthenticationCodeJob { None }; + // Load the registration, if any + let registration = + if let Some(registration_id) = user_email_authentication.user_registration_id { + Some( + repo.user_registration() + .lookup(registration_id) + .await + .map_err(JobError::retry)? + .ok_or(JobError::fail(anyhow::anyhow!( + "Failed to load user registration" + )))?, + ) + } else { + None + }; + // Generate a new 6-digit authentication code let range = Uniform::::from(0..1_000_000); let code = rng.sample(range); @@ -98,14 +114,17 @@ impl RunnableJob for SendEmailAuthenticationCodeJob { .email .parse() .map_err(JobError::fail)?; - let username = browser_session.as_ref().map(|s| s.user.username.clone()); + let username_from_session = browser_session.as_ref().map(|s| s.user.username.clone()); + let username_from_registration = registration.as_ref().and_then(|r| r.username.clone()); + let username = username_from_registration.or(username_from_session); let mailbox = Mailbox::new(username, address); info!("Sending email verification code to {}", mailbox); let language = self.language().parse().map_err(JobError::fail)?; - let context = EmailVerificationContext::new(code, browser_session).with_language(language); + let context = EmailVerificationContext::new(code, browser_session, registration) + .with_language(language); mailer .send_verification_email(mailbox, &context) .await diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index b8e407f92..1f0fa958f 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -23,7 +23,7 @@ use mas_data_model::{ DeviceCodeGrant, UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderTokenAuthMethod, User, UserAgent, UserEmail, UserEmailAuthenticationCode, - UserRecoverySession, + UserRecoverySession, UserRegistration, }; use mas_i18n::DataLocale; use mas_iana::jose::JsonWebSignatureAlg; @@ -878,7 +878,10 @@ impl TemplateContext for EmailRecoveryContext { /// Context used by the `emails/verification.{txt,html,subject}` templates #[derive(Serialize)] pub struct EmailVerificationContext { + #[serde(skip_serializing_if = "Option::is_none")] browser_session: Option, + #[serde(skip_serializing_if = "Option::is_none")] + user_registration: Option, authentication_code: UserEmailAuthenticationCode, } @@ -888,9 +891,11 @@ impl EmailVerificationContext { pub fn new( authentication_code: UserEmailAuthenticationCode, browser_session: Option, + user_registration: Option, ) -> Self { Self { browser_session, + user_registration, authentication_code, } } @@ -926,6 +931,7 @@ impl TemplateContext for EmailVerificationContext { Self { browser_session: Some(browser_session), + user_registration: None, authentication_code, } }) diff --git a/templates/emails/verification.html b/templates/emails/verification.html index f958e3382..58378dc27 100644 --- a/templates/emails/verification.html +++ b/templates/emails/verification.html @@ -8,6 +8,12 @@ {%- set _ = translator(lang) -%} -{{ _("mas.emails.greeting", username=browser_session.user.username | default("user")) }}
+{%- if browser_session is defined -%} + {%- set username = browser_session.user.username -%} +{%- elif user_registration is defined -%} + {%- set username = user_registration.username -%} +{%- endif -%} + +{{ _("mas.emails.greeting", username=(username|default("user"))) }}

{{ _("mas.emails.verify.body_html", code=authentication_code.code) }}
diff --git a/templates/emails/verification.txt b/templates/emails/verification.txt index f52a9ec5d..7afb408ff 100644 --- a/templates/emails/verification.txt +++ b/templates/emails/verification.txt @@ -8,6 +8,12 @@ Please see LICENSE in the repository root for full details. {%- set _ = translator(lang) -%} -{{ _("mas.emails.greeting", username=browser_session.user.username | default("user")) }} +{%- if browser_session is defined -%} + {%- set username = browser_session.user.username -%} +{%- elif user_registration is defined -%} + {%- set username = user_registration.username -%} +{%- endif -%} + +{{ _("mas.emails.greeting", username=(username|default("user"))) }} {{ _("mas.emails.verify.body_text", code=authentication_code.code) }} diff --git a/translations/en.json b/translations/en.json index 4835bf4b7..33711d4f4 100644 --- a/translations/en.json +++ b/translations/en.json @@ -219,7 +219,7 @@ "emails": { "greeting": "Hello %(username)s,", "@greeting": { - "context": "emails/verification.html:11:3-85, emails/verification.txt:11:3-85", + "context": "emails/verification.html:17:3-64, emails/verification.txt:17:3-64", "description": "Greeting at the top of emails sent to the user" }, "recovery": { @@ -251,12 +251,12 @@ "verify": { "body_html": "Your verification code to confirm this email address is: %(code)s", "@body_html": { - "context": "emails/verification.html:13:3-66", + "context": "emails/verification.html:19:3-66", "description": "The body of the email sent to verify an email address (HTML)" }, "body_text": "Your verification code to confirm this email address is: %(code)s", "@body_text": { - "context": "emails/verification.txt:13:3-66", + "context": "emails/verification.txt:19:3-66", "description": "The body of the email sent to verify an email address (text)" }, "subject": "Your email verification code is: %(code)s", @@ -327,7 +327,7 @@ }, "continue_with_provider": "Continue with %(provider)s", "@continue_with_provider": { - "context": "pages/login.html:75:15-67, pages/register/index.html:49:15-67", + "context": "pages/login.html:75:15-67, pages/register/index.html:53:15-67", "description": "Button to log in with an upstream provider" }, "description": "Please sign in to continue:", @@ -513,12 +513,12 @@ "register": { "call_to_login": "Already have an account?", "@call_to_login": { - "context": "pages/register/index.html:55:35-66, pages/register/password.html:77:33-64", + "context": "pages/register/index.html:59:35-66, pages/register/password.html:77:33-64", "description": "Displayed on the registration page to suggest to log in instead" }, "continue_with_email": "Continue with email address", "@continue_with_email": { - "context": "pages/register/index.html:40:30-67" + "context": "pages/register/index.html:44:30-67" }, "create_account": { "description": "Choose a username to continue.", @@ -682,4 +682,4 @@ } } } -} +} \ No newline at end of file