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_data_model::UserAgent;
17use mas_matrix::HomeserverConnection;
18use mas_router::{PostAuthAction, UrlBuilder};
19use mas_storage::{
20 BoxClock, BoxRepository, BoxRng,
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 err,
46)]
47pub(crate) async fn get(
48 mut rng: BoxRng,
49 clock: BoxClock,
50 mut repo: BoxRepository,
51 activity_tracker: BoundActivityTracker,
52 user_agent: Option<TypedHeader<headers::UserAgent>>,
53 State(url_builder): State<UrlBuilder>,
54 State(homeserver): State<Arc<dyn HomeserverConnection>>,
55 State(templates): State<Templates>,
56 PreferredLanguage(lang): PreferredLanguage,
57 cookie_jar: CookieJar,
58 Path(id): Path<Ulid>,
59) -> Result<Response, FancyError> {
60 let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
61 let registration = repo
62 .user_registration()
63 .lookup(id)
64 .await?
65 .context("User registration not found")?;
66
67 if registration.completed_at.is_some() {
71 let post_auth_action: Option<PostAuthAction> = registration
72 .post_auth_action
73 .map(serde_json::from_value)
74 .transpose()?;
75
76 return Ok((
77 cookie_jar,
78 OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder),
79 )
80 .into_response());
81 }
82
83 if clock.now() - registration.created_at > Duration::hours(1) {
86 return Err(FancyError::from(anyhow::anyhow!(
87 "Registration session has expired"
88 )));
89 }
90
91 let registrations = UserRegistrationSessions::load(&cookie_jar);
93 if !registrations.contains(®istration) {
94 return Err(FancyError::from(anyhow::anyhow!(
96 "Could not find the registration in the browser cookies"
97 )));
98 }
99
100 if repo.user().exists(®istration.username).await? {
105 return Err(FancyError::from(anyhow::anyhow!(
108 "Username is already taken"
109 )));
110 }
111
112 if !homeserver
113 .is_localpart_available(®istration.username)
114 .await?
115 {
116 return Err(FancyError::from(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 let email_authentication = repo
127 .user_email()
128 .lookup_authentication(email_authentication_id)
129 .await?
130 .context("Could not load the email authentication")?;
131
132 if email_authentication.completed_at.is_none() {
134 return Ok((
135 cookie_jar,
136 url_builder.redirect(&mas_router::RegisterVerifyEmail::new(id)),
137 )
138 .into_response());
139 }
140
141 if repo
146 .user_email()
147 .count(UserEmailFilter::new().for_email(&email_authentication.email))
148 .await?
149 > 0
150 {
151 let action = registration
152 .post_auth_action
153 .map(serde_json::from_value)
154 .transpose()?;
155
156 let ctx = RegisterStepsEmailInUseContext::new(email_authentication.email, action)
157 .with_language(lang);
158
159 return Ok((
160 cookie_jar,
161 Html(templates.render_register_steps_email_in_use(&ctx)?),
162 )
163 .into_response());
164 }
165
166 if registration.display_name.is_none() {
168 return Ok((
169 cookie_jar,
170 url_builder.redirect(&mas_router::RegisterDisplayName::new(registration.id)),
171 )
172 .into_response());
173 }
174
175 let registration = repo
177 .user_registration()
178 .complete(&clock, registration)
179 .await?;
180
181 let cookie_jar = registrations
183 .consume_session(®istration)?
184 .save(cookie_jar, &clock);
185
186 let user = repo
188 .user()
189 .add(&mut rng, &clock, registration.username)
190 .await?;
191 let user_session = repo
193 .browser_session()
194 .add(&mut rng, &clock, &user, user_agent)
195 .await?;
196
197 repo.user_email()
198 .add(&mut rng, &clock, &user, email_authentication.email)
199 .await?;
200
201 if let Some(password) = registration.password {
202 let user_password = repo
203 .user_password()
204 .add(
205 &mut rng,
206 &clock,
207 &user,
208 password.version,
209 password.hashed_password,
210 None,
211 )
212 .await?;
213
214 repo.browser_session()
215 .authenticate_with_password(&mut rng, &clock, &user_session, &user_password)
216 .await?;
217
218 PASSWORD_REGISTER_COUNTER.add(1, &[]);
219 }
220
221 if let Some(terms_url) = registration.terms_url {
222 repo.user_terms()
223 .accept_terms(&mut rng, &clock, &user, terms_url)
224 .await?;
225 }
226
227 let mut job = ProvisionUserJob::new(&user);
228 if let Some(display_name) = registration.display_name {
229 job = job.set_display_name(display_name);
230 }
231 repo.queue_job().schedule_job(&mut rng, &clock, job).await?;
232
233 repo.save().await?;
234
235 activity_tracker
236 .record_browser_session(&clock, &user_session)
237 .await;
238
239 let post_auth_action: Option<PostAuthAction> = registration
240 .post_auth_action
241 .map(serde_json::from_value)
242 .transpose()?;
243
244 let cookie_jar = cookie_jar.set_session(&user_session);
246
247 return Ok((
248 cookie_jar,
249 OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder),
250 )
251 .into_response());
252}