mas_handlers/views/register/steps/
finish.rs1use std::sync::{Arc, LazyLock};
7
8use anyhow::Context as _;
9use axum::{
10 extract::{Path, State},
11 response::{Html, IntoResponse, Response},
12};
13use axum_extra::TypedHeader;
14use chrono::Duration;
15use mas_axum_utils::{FancyError, SessionInfoExt as _, cookies::CookieJar};
16use mas_matrix::HomeserverConnection;
17use mas_router::{PostAuthAction, UrlBuilder};
18use mas_storage::{
19 BoxClock, BoxRepository, BoxRng,
20 queue::{ProvisionUserJob, QueueJobRepositoryExt as _},
21 user::UserEmailFilter,
22};
23use mas_templates::{RegisterStepsEmailInUseContext, TemplateContext as _, Templates};
24use opentelemetry::metrics::Counter;
25use ulid::Ulid;
26
27use super::super::cookie::UserRegistrationSessions;
28use crate::{
29 BoundActivityTracker, METER, PreferredLanguage, views::shared::OptionalPostAuthAction,
30};
31
32static PASSWORD_REGISTER_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
33 METER
34 .u64_counter("mas.user.password_registration")
35 .with_description("Number of password registrations")
36 .with_unit("{registration}")
37 .build()
38});
39
40#[tracing::instrument(
41 name = "handlers.views.register.steps.finish.get",
42 fields(user_registration.id = %id),
43 skip_all,
44)]
45pub(crate) async fn get(
46 mut rng: BoxRng,
47 clock: BoxClock,
48 mut repo: BoxRepository,
49 activity_tracker: BoundActivityTracker,
50 user_agent: Option<TypedHeader<headers::UserAgent>>,
51 State(url_builder): State<UrlBuilder>,
52 State(homeserver): State<Arc<dyn HomeserverConnection>>,
53 State(templates): State<Templates>,
54 PreferredLanguage(lang): PreferredLanguage,
55 cookie_jar: CookieJar,
56 Path(id): Path<Ulid>,
57) -> Result<Response, FancyError> {
58 let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
59 let registration = repo
60 .user_registration()
61 .lookup(id)
62 .await?
63 .context("User registration not found")?;
64
65 if registration.completed_at.is_some() {
69 let post_auth_action: Option<PostAuthAction> = registration
70 .post_auth_action
71 .map(serde_json::from_value)
72 .transpose()?;
73
74 return Ok((
75 cookie_jar,
76 OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder),
77 )
78 .into_response());
79 }
80
81 if clock.now() - registration.created_at > Duration::hours(1) {
84 return Err(FancyError::from(anyhow::anyhow!(
85 "Registration session has expired"
86 )));
87 }
88
89 let registrations = UserRegistrationSessions::load(&cookie_jar);
91 if !registrations.contains(®istration) {
92 return Err(FancyError::from(anyhow::anyhow!(
94 "Could not find the registration in the browser cookies"
95 )));
96 }
97
98 if repo.user().exists(®istration.username).await? {
103 return Err(FancyError::from(anyhow::anyhow!(
106 "Username is already taken"
107 )));
108 }
109
110 if !homeserver
111 .is_localpart_available(®istration.username)
112 .await?
113 {
114 return Err(FancyError::from(anyhow::anyhow!(
115 "Username is not available"
116 )));
117 }
118
119 let email_authentication_id = registration
122 .email_authentication_id
123 .context("No email authentication started for this registration")?;
124 let email_authentication = repo
125 .user_email()
126 .lookup_authentication(email_authentication_id)
127 .await?
128 .context("Could not load the email authentication")?;
129
130 if email_authentication.completed_at.is_none() {
132 return Ok((
133 cookie_jar,
134 url_builder.redirect(&mas_router::RegisterVerifyEmail::new(id)),
135 )
136 .into_response());
137 }
138
139 if repo
144 .user_email()
145 .count(UserEmailFilter::new().for_email(&email_authentication.email))
146 .await?
147 > 0
148 {
149 let action = registration
150 .post_auth_action
151 .map(serde_json::from_value)
152 .transpose()?;
153
154 let ctx = RegisterStepsEmailInUseContext::new(email_authentication.email, action)
155 .with_language(lang);
156
157 return Ok((
158 cookie_jar,
159 Html(templates.render_register_steps_email_in_use(&ctx)?),
160 )
161 .into_response());
162 }
163
164 if registration.display_name.is_none() {
166 return Ok((
167 cookie_jar,
168 url_builder.redirect(&mas_router::RegisterDisplayName::new(registration.id)),
169 )
170 .into_response());
171 }
172
173 let registration = repo
175 .user_registration()
176 .complete(&clock, registration)
177 .await?;
178
179 let cookie_jar = registrations
181 .consume_session(®istration)?
182 .save(cookie_jar, &clock);
183
184 let user = repo
186 .user()
187 .add(&mut rng, &clock, registration.username)
188 .await?;
189 let user_session = repo
191 .browser_session()
192 .add(&mut rng, &clock, &user, user_agent)
193 .await?;
194
195 repo.user_email()
196 .add(&mut rng, &clock, &user, email_authentication.email)
197 .await?;
198
199 if let Some(password) = registration.password {
200 let user_password = repo
201 .user_password()
202 .add(
203 &mut rng,
204 &clock,
205 &user,
206 password.version,
207 password.hashed_password,
208 None,
209 )
210 .await?;
211
212 repo.browser_session()
213 .authenticate_with_password(&mut rng, &clock, &user_session, &user_password)
214 .await?;
215
216 PASSWORD_REGISTER_COUNTER.add(1, &[]);
217 }
218
219 if let Some(terms_url) = registration.terms_url {
220 repo.user_terms()
221 .accept_terms(&mut rng, &clock, &user, terms_url)
222 .await?;
223 }
224
225 let mut job = ProvisionUserJob::new(&user);
226 if let Some(display_name) = registration.display_name {
227 job = job.set_display_name(display_name);
228 }
229 repo.queue_job().schedule_job(&mut rng, &clock, job).await?;
230
231 repo.save().await?;
232
233 activity_tracker
234 .record_browser_session(&clock, &user_session)
235 .await;
236
237 let post_auth_action: Option<PostAuthAction> = registration
238 .post_auth_action
239 .map(serde_json::from_value)
240 .transpose()?;
241
242 let cookie_jar = cookie_jar.set_session(&user_session);
244
245 return Ok((
246 cookie_jar,
247 OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder),
248 )
249 .into_response());
250}