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::{InternalError, RecordAsRequester, SessionInfoExt as _, cookies::CookieJar};
16use mas_data_model::{BoxClock, BoxRng, SiteConfig};
17use mas_matrix::HomeserverConnection;
18use mas_router::{PostAuthAction, UrlBuilder};
19use mas_storage::{
20 BoxRepository,
21 queue::{ProvisionUserJob, QueueJobRepositoryExt as _},
22 user::UserEmailFilter,
23};
24use mas_templates::{RegisterStepsEmailInUseContext, TemplateContext as _, Templates};
25use opentelemetry::metrics::Counter;
26use ulid::Ulid;
27
28use super::super::cookie::UserRegistrationSessions;
29use crate::{
30 BoundActivityTracker, METER, PreferredLanguage, views::shared::OptionalPostAuthAction,
31};
32
33static PASSWORD_REGISTER_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
34 METER
35 .u64_counter("mas.user.password_registration")
36 .with_description("Number of password registrations")
37 .with_unit("{registration}")
38 .build()
39});
40
41#[tracing::instrument(
42 name = "handlers.views.register.steps.finish.get",
43 fields(user_registration.id = %id),
44 skip_all,
45)]
46pub(crate) async fn get(
47 mut rng: BoxRng,
48 clock: BoxClock,
49 mut repo: BoxRepository,
50 activity_tracker: BoundActivityTracker,
51 user_agent: Option<TypedHeader<headers::UserAgent>>,
52 State(url_builder): State<UrlBuilder>,
53 State(homeserver): State<Arc<dyn HomeserverConnection>>,
54 State(templates): State<Templates>,
55 State(site_config): State<SiteConfig>,
56 PreferredLanguage(lang): PreferredLanguage,
57 cookie_jar: CookieJar,
58 Path(id): Path<Ulid>,
59) -> Result<Response, InternalError> {
60 let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
61 let registration = repo
62 .user_registration()
63 .lookup(id)
64 .await?
65 .context("User registration not found")
66 .map_err(InternalError::from_anyhow)?;
67
68 if registration.completed_at.is_some() {
72 let post_auth_action: Option<PostAuthAction> = registration
73 .post_auth_action
74 .map(serde_json::from_value)
75 .transpose()?;
76
77 return Ok((
78 cookie_jar,
79 OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder),
80 )
81 .into_response());
82 }
83
84 if clock.now() - registration.created_at > Duration::hours(1) {
87 return Err(InternalError::from_anyhow(anyhow::anyhow!(
88 "Registration session has expired"
89 )));
90 }
91
92 let registrations = UserRegistrationSessions::load(&cookie_jar);
94 if !registrations.contains(®istration) {
95 return Err(InternalError::from_anyhow(anyhow::anyhow!(
97 "Could not find the registration in the browser cookies"
98 )));
99 }
100
101 if repo.user().exists(®istration.username).await? {
106 return Err(InternalError::from_anyhow(anyhow::anyhow!(
109 "Username is already taken"
110 )));
111 }
112
113 if !homeserver
114 .is_localpart_available(®istration.username)
115 .await
116 .map_err(InternalError::from_anyhow)?
117 {
118 return Err(InternalError::from_anyhow(anyhow::anyhow!(
119 "Username is not available"
120 )));
121 }
122
123 let token_required = if let Some(session_id) =
124 registration.upstream_oauth_authorization_session_id
125 {
126 let session = repo
127 .upstream_oauth_session()
128 .lookup(session_id)
129 .await?
130 .context("Could not load the upstream OAuth authorization session")
131 .map_err(InternalError::from_anyhow)?;
132
133 let provider = repo
134 .upstream_oauth_provider()
135 .lookup(session.provider_id)
136 .await?
137 .context("Could not load the upstream OAuth provider")
138 .map_err(InternalError::from_anyhow)?;
139
140 provider.registration_token_required || site_config.registration_token_required
141 } else {
142 site_config.password_registration_token_required || site_config.registration_token_required
143 };
144
145 let registration_token = if token_required {
146 if let Some(registration_token_id) = registration.user_registration_token_id {
147 let registration_token = repo
148 .user_registration_token()
149 .lookup(registration_token_id)
150 .await?
151 .context("Could not load the registration token")
152 .map_err(InternalError::from_anyhow)?;
153
154 if !registration_token.is_valid(clock.now()) {
155 return Err(InternalError::from_anyhow(anyhow::anyhow!(
158 "Registration token used is no longer valid"
159 )));
160 }
161
162 Some(registration_token)
163 } else {
164 return Ok((
166 cookie_jar,
167 url_builder.redirect(&mas_router::RegisterToken::new(registration.id)),
168 )
169 .into_response());
170 }
171 } else {
172 None
173 };
174
175 let email_authentication =
179 if let Some(email_authentication_id) = registration.email_authentication_id {
180 let email_authentication = repo
181 .user_email()
182 .lookup_authentication(email_authentication_id)
183 .await?
184 .context("Could not load the email authentication")
185 .map_err(InternalError::from_anyhow)?;
186
187 if email_authentication.completed_at.is_none() {
189 return Ok((
190 cookie_jar,
191 url_builder.redirect(&mas_router::RegisterVerifyEmail::new(id)),
192 )
193 .into_response());
194 }
195
196 if repo
201 .user_email()
202 .count(UserEmailFilter::new().for_email(&email_authentication.email))
203 .await?
204 > 0
205 {
206 let action = registration
207 .post_auth_action
208 .map(serde_json::from_value)
209 .transpose()?;
210
211 let ctx = RegisterStepsEmailInUseContext::new(email_authentication.email, action)
212 .with_language(lang);
213
214 return Ok((
215 cookie_jar,
216 Html(templates.render_register_steps_email_in_use(&ctx)?),
217 )
218 .into_response());
219 }
220
221 Some(email_authentication)
222 } else {
223 None
224 };
225
226 let upstream_oauth = if let Some(upstream_oauth_authorization_session_id) =
229 registration.upstream_oauth_authorization_session_id
230 {
231 let upstream_oauth_authorization_session = repo
232 .upstream_oauth_session()
233 .lookup(upstream_oauth_authorization_session_id)
234 .await?
235 .context("Could not load the upstream OAuth authorization session")
236 .map_err(InternalError::from_anyhow)?;
237
238 let link_id = upstream_oauth_authorization_session
239 .link_id()
240 .context("Authorization session has no upstream link associated with it")
243 .map_err(InternalError::from_anyhow)?;
244
245 if upstream_oauth_authorization_session.is_consumed() {
246 return Err(InternalError::from_anyhow(anyhow::anyhow!(
253 "The upstream authorization session was already used. Try registering again"
254 )));
255 }
256
257 let upstream_oauth_link = repo
258 .upstream_oauth_link()
259 .lookup(link_id)
260 .await?
261 .context("Could not load the upstream OAuth link")
262 .map_err(InternalError::from_anyhow)?;
263
264 if upstream_oauth_link.user_id.is_some() {
265 return Err(InternalError::from_anyhow(anyhow::anyhow!(
269 "The upstream identity was already linked to a user. Try logging in again"
270 )));
271 }
272
273 Some((upstream_oauth_authorization_session, upstream_oauth_link))
274 } else {
275 None
276 };
277
278 if registration.display_name.is_none() {
280 return Ok((
281 cookie_jar,
282 url_builder.redirect(&mas_router::RegisterDisplayName::new(registration.id)),
283 )
284 .into_response());
285 }
286
287 let registration = repo
289 .user_registration()
290 .complete(&clock, registration)
291 .await?;
292
293 if let Some(registration_token) = registration_token {
295 repo.user_registration_token()
296 .use_token(&clock, registration_token)
297 .await?;
298 }
299
300 let cookie_jar = registrations
302 .consume_session(®istration)?
303 .save(cookie_jar, &clock);
304
305 let user = repo
307 .user()
308 .add(&mut rng, &clock, registration.username)
309 .await?;
310 user.maybe_record_as_requester();
313 let user_session = repo
315 .browser_session()
316 .add(&mut rng, &clock, &user, user_agent)
317 .await?;
318
319 if let Some(email_authentication) = email_authentication {
320 repo.user_email()
321 .add(&mut rng, &clock, &user, email_authentication.email)
322 .await?;
323 }
324
325 if let Some(password) = registration.password {
326 let user_password = repo
327 .user_password()
328 .add(
329 &mut rng,
330 &clock,
331 &user,
332 password.version,
333 password.hashed_password,
334 None,
335 )
336 .await?;
337
338 repo.browser_session()
339 .authenticate_with_password(&mut rng, &clock, &user_session, &user_password)
340 .await?;
341
342 PASSWORD_REGISTER_COUNTER.add(1, &[]);
343 }
344
345 if let Some((upstream_session, upstream_link)) = upstream_oauth {
346 let upstream_session = repo
347 .upstream_oauth_session()
348 .consume(&clock, upstream_session, &user_session)
349 .await?;
350
351 repo.upstream_oauth_link()
352 .associate_to_user(&upstream_link, &user)
353 .await?;
354
355 repo.browser_session()
356 .authenticate_with_upstream(&mut rng, &clock, &user_session, &upstream_session)
357 .await?;
358 }
359
360 if let Some(terms_url) = registration.terms_url {
361 repo.user_terms()
362 .accept_terms(&mut rng, &clock, &user, terms_url)
363 .await?;
364 }
365
366 let mut job = ProvisionUserJob::new(&user);
367 if let Some(display_name) = registration.display_name {
368 job = job.set_display_name(display_name);
369 }
370 repo.queue_job().schedule_job(&mut rng, &clock, job).await?;
371
372 repo.save().await?;
373
374 activity_tracker
375 .record_browser_session(&clock, &user_session)
376 .await;
377
378 let post_auth_action: Option<PostAuthAction> = registration
379 .post_auth_action
380 .map(serde_json::from_value)
381 .transpose()?;
382
383 let cookie_jar = cookie_jar.set_session(&user_session);
385
386 return Ok((
387 cookie_jar,
388 OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder),
389 )
390 .into_response());
391}