mas_handlers/views/register/steps/
finish.rs1use std::sync::Arc;
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 ulid::Ulid;
26
27use super::super::cookie::UserRegistrationSessions;
28use crate::{BoundActivityTracker, PreferredLanguage, views::shared::OptionalPostAuthAction};
29
30#[tracing::instrument(
31 name = "handlers.views.register.steps.finish.get",
32 fields(user_registration.id = %id),
33 skip_all,
34 err,
35)]
36pub(crate) async fn get(
37 mut rng: BoxRng,
38 clock: BoxClock,
39 mut repo: BoxRepository,
40 activity_tracker: BoundActivityTracker,
41 user_agent: Option<TypedHeader<headers::UserAgent>>,
42 State(url_builder): State<UrlBuilder>,
43 State(homeserver): State<Arc<dyn HomeserverConnection>>,
44 State(templates): State<Templates>,
45 PreferredLanguage(lang): PreferredLanguage,
46 cookie_jar: CookieJar,
47 Path(id): Path<Ulid>,
48) -> Result<Response, FancyError> {
49 let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
50 let registration = repo
51 .user_registration()
52 .lookup(id)
53 .await?
54 .context("User registration not found")?;
55
56 if registration.completed_at.is_some() {
60 let post_auth_action: Option<PostAuthAction> = registration
61 .post_auth_action
62 .map(serde_json::from_value)
63 .transpose()?;
64
65 return Ok((
66 cookie_jar,
67 OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder),
68 )
69 .into_response());
70 }
71
72 if clock.now() - registration.created_at > Duration::hours(1) {
75 return Err(FancyError::from(anyhow::anyhow!(
76 "Registration session has expired"
77 )));
78 }
79
80 let registrations = UserRegistrationSessions::load(&cookie_jar);
82 if !registrations.contains(®istration) {
83 return Err(FancyError::from(anyhow::anyhow!(
85 "Could not find the registration in the browser cookies"
86 )));
87 }
88
89 if repo.user().exists(®istration.username).await? {
94 return Err(FancyError::from(anyhow::anyhow!(
97 "Username is already taken"
98 )));
99 }
100
101 if !homeserver
102 .is_localpart_available(®istration.username)
103 .await?
104 {
105 return Err(FancyError::from(anyhow::anyhow!(
106 "Username is not available"
107 )));
108 }
109
110 let email_authentication_id = registration
113 .email_authentication_id
114 .context("No email authentication started for this registration")?;
115 let email_authentication = repo
116 .user_email()
117 .lookup_authentication(email_authentication_id)
118 .await?
119 .context("Could not load the email authentication")?;
120
121 if email_authentication.completed_at.is_none() {
123 return Ok((
124 cookie_jar,
125 url_builder.redirect(&mas_router::RegisterVerifyEmail::new(id)),
126 )
127 .into_response());
128 }
129
130 if repo
135 .user_email()
136 .count(UserEmailFilter::new().for_email(&email_authentication.email))
137 .await?
138 > 0
139 {
140 let action = registration
141 .post_auth_action
142 .map(serde_json::from_value)
143 .transpose()?;
144
145 let ctx = RegisterStepsEmailInUseContext::new(email_authentication.email, action)
146 .with_language(lang);
147
148 return Ok((
149 cookie_jar,
150 Html(templates.render_register_steps_email_in_use(&ctx)?),
151 )
152 .into_response());
153 }
154
155 if registration.display_name.is_none() {
157 return Ok((
158 cookie_jar,
159 url_builder.redirect(&mas_router::RegisterDisplayName::new(registration.id)),
160 )
161 .into_response());
162 }
163
164 let registration = repo
166 .user_registration()
167 .complete(&clock, registration)
168 .await?;
169
170 let cookie_jar = registrations
172 .consume_session(®istration)?
173 .save(cookie_jar, &clock);
174
175 let user = repo
177 .user()
178 .add(&mut rng, &clock, registration.username)
179 .await?;
180 let user_session = repo
182 .browser_session()
183 .add(&mut rng, &clock, &user, user_agent)
184 .await?;
185
186 repo.user_email()
187 .add(&mut rng, &clock, &user, email_authentication.email)
188 .await?;
189
190 if let Some(password) = registration.password {
191 let user_password = repo
192 .user_password()
193 .add(
194 &mut rng,
195 &clock,
196 &user,
197 password.version,
198 password.hashed_password,
199 None,
200 )
201 .await?;
202
203 repo.browser_session()
204 .authenticate_with_password(&mut rng, &clock, &user_session, &user_password)
205 .await?;
206 }
207
208 if let Some(terms_url) = registration.terms_url {
209 repo.user_terms()
210 .accept_terms(&mut rng, &clock, &user, terms_url)
211 .await?;
212 }
213
214 let mut job = ProvisionUserJob::new(&user);
215 if let Some(display_name) = registration.display_name {
216 job = job.set_display_name(display_name);
217 }
218 repo.queue_job().schedule_job(&mut rng, &clock, job).await?;
219
220 repo.save().await?;
221
222 activity_tracker
223 .record_browser_session(&clock, &user_session)
224 .await;
225
226 let post_auth_action: Option<PostAuthAction> = registration
227 .post_auth_action
228 .map(serde_json::from_value)
229 .transpose()?;
230
231 let cookie_jar = cookie_jar.set_session(&user_session);
233
234 return Ok((
235 cookie_jar,
236 OptionalPostAuthAction::from(post_auth_action).go_next(&url_builder),
237 )
238 .into_response());
239}