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, 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, InternalError> {
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 .map_err(InternalError::from_anyhow)?;
65
66 if registration.completed_at.is_some() {
70 let post_auth_action: Option<PostAuthAction> = registration
71 .post_auth_action
72 .map(serde_json::from_value)
73 .transpose()?;
74
75 return Ok((
76 cookie_jar,
77 OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder),
78 )
79 .into_response());
80 }
81
82 if clock.now() - registration.created_at > Duration::hours(1) {
85 return Err(InternalError::from_anyhow(anyhow::anyhow!(
86 "Registration session has expired"
87 )));
88 }
89
90 let registrations = UserRegistrationSessions::load(&cookie_jar);
92 if !registrations.contains(®istration) {
93 return Err(InternalError::from_anyhow(anyhow::anyhow!(
95 "Could not find the registration in the browser cookies"
96 )));
97 }
98
99 if repo.user().exists(®istration.username).await? {
104 return Err(InternalError::from_anyhow(anyhow::anyhow!(
107 "Username is already taken"
108 )));
109 }
110
111 if !homeserver
112 .is_localpart_available(®istration.username)
113 .await
114 .map_err(InternalError::from_anyhow)?
115 {
116 return Err(InternalError::from_anyhow(anyhow::anyhow!(
117 "Username is not available"
118 )));
119 }
120
121 let email_authentication_id = registration
124 .email_authentication_id
125 .context("No email authentication started for this registration")
126 .map_err(InternalError::from_anyhow)?;
127 let email_authentication = repo
128 .user_email()
129 .lookup_authentication(email_authentication_id)
130 .await?
131 .context("Could not load the email authentication")
132 .map_err(InternalError::from_anyhow)?;
133
134 if email_authentication.completed_at.is_none() {
136 return Ok((
137 cookie_jar,
138 url_builder.redirect(&mas_router::RegisterVerifyEmail::new(id)),
139 )
140 .into_response());
141 }
142
143 if repo
148 .user_email()
149 .count(UserEmailFilter::new().for_email(&email_authentication.email))
150 .await?
151 > 0
152 {
153 let action = registration
154 .post_auth_action
155 .map(serde_json::from_value)
156 .transpose()?;
157
158 let ctx = RegisterStepsEmailInUseContext::new(email_authentication.email, action)
159 .with_language(lang);
160
161 return Ok((
162 cookie_jar,
163 Html(templates.render_register_steps_email_in_use(&ctx)?),
164 )
165 .into_response());
166 }
167
168 if registration.display_name.is_none() {
170 return Ok((
171 cookie_jar,
172 url_builder.redirect(&mas_router::RegisterDisplayName::new(registration.id)),
173 )
174 .into_response());
175 }
176
177 let registration = repo
179 .user_registration()
180 .complete(&clock, registration)
181 .await?;
182
183 let cookie_jar = registrations
185 .consume_session(®istration)?
186 .save(cookie_jar, &clock);
187
188 let user = repo
190 .user()
191 .add(&mut rng, &clock, registration.username)
192 .await?;
193 let user_session = repo
195 .browser_session()
196 .add(&mut rng, &clock, &user, user_agent)
197 .await?;
198
199 repo.user_email()
200 .add(&mut rng, &clock, &user, email_authentication.email)
201 .await?;
202
203 if let Some(password) = registration.password {
204 let user_password = repo
205 .user_password()
206 .add(
207 &mut rng,
208 &clock,
209 &user,
210 password.version,
211 password.hashed_password,
212 None,
213 )
214 .await?;
215
216 repo.browser_session()
217 .authenticate_with_password(&mut rng, &clock, &user_session, &user_password)
218 .await?;
219
220 PASSWORD_REGISTER_COUNTER.add(1, &[]);
221 }
222
223 if let Some(terms_url) = registration.terms_url {
224 repo.user_terms()
225 .accept_terms(&mut rng, &clock, &user, terms_url)
226 .await?;
227 }
228
229 let mut job = ProvisionUserJob::new(&user);
230 if let Some(display_name) = registration.display_name {
231 job = job.set_display_name(display_name);
232 }
233 repo.queue_job().schedule_job(&mut rng, &clock, job).await?;
234
235 repo.save().await?;
236
237 activity_tracker
238 .record_browser_session(&clock, &user_session)
239 .await;
240
241 let post_auth_action: Option<PostAuthAction> = registration
242 .post_auth_action
243 .map(serde_json::from_value)
244 .transpose()?;
245
246 let cookie_jar = cookie_jar.set_session(&user_session);
248
249 return Ok((
250 cookie_jar,
251 OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder),
252 )
253 .into_response());
254}