|
| 1 | +// Copyright 2025 New Vector Ltd. |
| 2 | +// |
| 3 | +// SPDX-License-Identifier: AGPL-3.0-only |
| 4 | +// Please see LICENSE in the repository root for full details. |
| 5 | + |
| 6 | +use anyhow::Context as _; |
| 7 | +use axum::{ |
| 8 | + extract::{Path, State}, |
| 9 | + response::IntoResponse, |
| 10 | +}; |
| 11 | +use axum_extra::TypedHeader; |
| 12 | +use mas_axum_utils::{cookies::CookieJar, FancyError, SessionInfoExt as _}; |
| 13 | +use mas_data_model::UserAgent; |
| 14 | +use mas_router::{PostAuthAction, UrlBuilder}; |
| 15 | +use mas_storage::{ |
| 16 | + queue::{ProvisionUserJob, QueueJobRepositoryExt as _}, |
| 17 | + user::UserEmailFilter, |
| 18 | + BoxClock, BoxRepository, BoxRng, |
| 19 | +}; |
| 20 | +use ulid::Ulid; |
| 21 | + |
| 22 | +use crate::{views::shared::OptionalPostAuthAction, BoundActivityTracker}; |
| 23 | + |
| 24 | +#[tracing::instrument( |
| 25 | + name = "handlers.views.register.steps.finish.get", |
| 26 | + fields(user_registration.id = %id), |
| 27 | + skip_all, |
| 28 | + err, |
| 29 | +)] |
| 30 | +pub(crate) async fn get( |
| 31 | + mut rng: BoxRng, |
| 32 | + clock: BoxClock, |
| 33 | + mut repo: BoxRepository, |
| 34 | + activity_tracker: BoundActivityTracker, |
| 35 | + user_agent: Option<TypedHeader<headers::UserAgent>>, |
| 36 | + State(url_builder): State<UrlBuilder>, |
| 37 | + cookie_jar: CookieJar, |
| 38 | + Path(id): Path<Ulid>, |
| 39 | +) -> Result<impl IntoResponse, FancyError> { |
| 40 | + let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned())); |
| 41 | + let registration = repo |
| 42 | + .user_registration() |
| 43 | + .lookup(id) |
| 44 | + .await? |
| 45 | + .context("User registration not found")?; |
| 46 | + |
| 47 | + // If the registration is completed, we can go to the registration destination |
| 48 | + // XXX: this might not be the right thing to do? Maybe an error page would be |
| 49 | + // better? |
| 50 | + if registration.completed_at.is_some() { |
| 51 | + let post_auth_action: Option<PostAuthAction> = registration |
| 52 | + .post_auth_action |
| 53 | + .map(serde_json::from_value) |
| 54 | + .transpose()?; |
| 55 | + |
| 56 | + return Ok(( |
| 57 | + cookie_jar, |
| 58 | + OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder), |
| 59 | + )); |
| 60 | + } |
| 61 | + |
| 62 | + // Let's perform last minute checks on the registration, especially to avoid |
| 63 | + // race conditions where multiple users register with the same username or email |
| 64 | + // address |
| 65 | + |
| 66 | + if repo.user().exists(®istration.username).await? { |
| 67 | + return Err(FancyError::from(anyhow::anyhow!( |
| 68 | + "Username is already taken" |
| 69 | + ))); |
| 70 | + } |
| 71 | + |
| 72 | + // TODO: query the homeserver |
| 73 | + |
| 74 | + // For now, we require an email address on the registration, but this might |
| 75 | + // change in the future |
| 76 | + let email_authentication_id = registration |
| 77 | + .email_authentication_id |
| 78 | + .context("No email authentication started for this registration")?; |
| 79 | + let email_authentication = repo |
| 80 | + .user_email() |
| 81 | + .lookup_authentication(email_authentication_id) |
| 82 | + .await? |
| 83 | + .context("Could not load the email authentication")?; |
| 84 | + |
| 85 | + // Check that the email authentication has been completed |
| 86 | + if email_authentication.completed_at.is_none() { |
| 87 | + return Ok(( |
| 88 | + cookie_jar, |
| 89 | + url_builder.redirect(&mas_router::RegisterVerifyEmail::new(id)), |
| 90 | + )); |
| 91 | + } |
| 92 | + |
| 93 | + // Check that the email address isn't already used |
| 94 | + if repo |
| 95 | + .user_email() |
| 96 | + .count(UserEmailFilter::new().for_email(&email_authentication.email)) |
| 97 | + .await? |
| 98 | + > 0 |
| 99 | + { |
| 100 | + return Err(FancyError::from(anyhow::anyhow!( |
| 101 | + "Email address is already used" |
| 102 | + ))); |
| 103 | + } |
| 104 | + |
| 105 | + // Everuthing is good, let's complete the registration |
| 106 | + let registration = repo |
| 107 | + .user_registration() |
| 108 | + .complete(&clock, registration) |
| 109 | + .await?; |
| 110 | + |
| 111 | + // Now we can start the user creation |
| 112 | + let user = repo |
| 113 | + .user() |
| 114 | + .add(&mut rng, &clock, registration.username) |
| 115 | + .await?; |
| 116 | + // Also create a browser session which will log the user in |
| 117 | + let user_session = repo |
| 118 | + .browser_session() |
| 119 | + .add(&mut rng, &clock, &user, user_agent) |
| 120 | + .await?; |
| 121 | + |
| 122 | + repo.user_email() |
| 123 | + .add(&mut rng, &clock, &user, email_authentication.email) |
| 124 | + .await?; |
| 125 | + |
| 126 | + if let Some(password) = registration.password { |
| 127 | + let user_password = repo |
| 128 | + .user_password() |
| 129 | + .add( |
| 130 | + &mut rng, |
| 131 | + &clock, |
| 132 | + &user, |
| 133 | + password.version, |
| 134 | + password.hashed_password, |
| 135 | + None, |
| 136 | + ) |
| 137 | + .await?; |
| 138 | + |
| 139 | + repo.browser_session() |
| 140 | + .authenticate_with_password(&mut rng, &clock, &user_session, &user_password) |
| 141 | + .await?; |
| 142 | + } |
| 143 | + |
| 144 | + if let Some(terms_url) = registration.terms_url { |
| 145 | + repo.user_terms() |
| 146 | + .accept_terms(&mut rng, &clock, &user, terms_url) |
| 147 | + .await?; |
| 148 | + } |
| 149 | + |
| 150 | + let mut job = ProvisionUserJob::new(&user); |
| 151 | + if let Some(display_name) = registration.display_name { |
| 152 | + job = job.set_display_name(display_name); |
| 153 | + } |
| 154 | + repo.queue_job().schedule_job(&mut rng, &clock, job).await?; |
| 155 | + |
| 156 | + repo.save().await?; |
| 157 | + |
| 158 | + activity_tracker |
| 159 | + .record_browser_session(&clock, &user_session) |
| 160 | + .await; |
| 161 | + |
| 162 | + let post_auth_action: Option<PostAuthAction> = registration |
| 163 | + .post_auth_action |
| 164 | + .map(serde_json::from_value) |
| 165 | + .transpose()?; |
| 166 | + |
| 167 | + // Login the user with the session we just created |
| 168 | + let cookie_jar = cookie_jar.set_session(&user_session); |
| 169 | + |
| 170 | + return Ok(( |
| 171 | + cookie_jar, |
| 172 | + OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder), |
| 173 | + )); |
| 174 | +} |
0 commit comments