1use std::{fmt::Write, net::IpAddr};
2
3use axum::extract::State;
4use ruma::{
5 DeviceId, OwnedUserId, UserId,
6 api::client::{
7 account::register::{self, LoginType, RegistrationKind},
8 uiaa::{AuthFlow, AuthType, UiaaInfo},
9 },
10 thirdparty::Medium,
11};
12use serde_json::{json, value::to_raw_value};
13use tuwunel_core::{Err, Error, Result, debug_info, debug_warn, info, utils, warn};
14use tuwunel_service::{
15 threepid::Association,
16 users::{Register, device::generate_refresh_token},
17};
18
19use super::{SESSION_ID_LENGTH, is_matrix_appservice_irc};
20use crate::{
21 ClientIp, Ruma,
22 utils::{bind_new_account_email, check_email_domain},
23};
24
25const RANDOM_USER_ID_LENGTH: usize = 10;
26
27#[expect(clippy::doc_markdown)]
45#[tracing::instrument(skip_all, fields(%client), name = "register")]
46pub(crate) async fn register_route(
47 State(services): State<crate::State>,
48 ClientIp(client): ClientIp,
49 body: Ruma<register::v3::Request>,
50) -> Result<register::v3::Response> {
51 let is_guest = body.kind == RegistrationKind::Guest;
52 let emergency_mode_enabled = services
53 .config
54 .authentication
55 .emergency_password
56 .is_some();
57
58 gate_registration_allowed(services, &body, is_guest)?;
59
60 if !body.inhibit_login
63 && body
64 .appservice_info
65 .as_ref()
66 .is_some_and(|appservice| appservice.registration.device_management)
67 {
68 return Err!(Request(AppserviceLoginUnsupported(
69 "Appservice has MSC4190 device management enabled; inhibit_login must be true."
70 )));
71 }
72
73 let user_id =
74 resolve_registration_user_id(services, &body, is_guest, emergency_mode_enabled).await?;
75
76 check_appservice_namespace(services, &body, &user_id, emergency_mode_enabled).await?;
77
78 let device_id = body
79 .device_id
80 .as_deref()
81 .filter(|_| !is_guest && !body.inhibit_login);
82
83 services
85 .users
86 .check_device_id(&user_id, device_id)
87 .await?;
88
89 let email_association = enforce_uiaa(services, &body, is_guest).await?;
90
91 if let Some(ref association) = email_association {
92 check_email_domain(&services, &association.address)?;
93 }
94
95 let password = if is_guest { None } else { body.password.as_deref() };
96
97 services
98 .users
99 .full_register(Register {
100 user_id: Some(&user_id),
101 password,
102 is_appservice: body.appservice_info.is_some(),
103 is_guest,
104 grant_first_user_admin: true,
105 ..Default::default()
106 })
107 .await?;
108
109 bind_registration_email(services, &user_id, email_association.as_ref()).await;
110
111 record_accepted_terms(services, &user_id, &body, is_guest).await?;
112
113 if (!is_guest && body.inhibit_login)
114 || body
115 .appservice_info
116 .as_ref()
117 .is_some_and(|appservice| appservice.registration.device_management)
118 {
119 return Ok(register::v3::Response {
120 user_id,
121 device_id: None,
122 access_token: None,
123 refresh_token: None,
124 expires_in: None,
125 });
126 }
127
128 let (access_token, expires_in) = services
130 .users
131 .generate_access_token(body.refresh_token);
132
133 let refresh_token = expires_in.is_some().then(generate_refresh_token);
135
136 let device_id = services
138 .users
139 .create_device(
140 &user_id,
141 device_id,
142 (Some(&access_token), expires_in),
143 refresh_token.as_deref(),
144 body.initial_device_display_name.as_deref(),
145 Some(client),
146 )
147 .await?;
148
149 debug_info!(%user_id, %device_id, "User account was created");
150
151 if body.appservice_info.is_none()
152 && (!is_guest || services.config.admin.room.log_guest_registrations)
153 {
154 announce_new_user(services, &user_id, &body, is_guest, &client).await?;
155 }
156
157 Ok(register::v3::Response {
158 user_id,
159 device_id: Some(device_id),
160 access_token: Some(access_token),
161 refresh_token,
162 expires_in,
163 })
164}
165
166fn gate_registration_allowed(
167 services: crate::State,
168 body: &Ruma<register::v3::Request>,
169 is_guest: bool,
170) -> Result {
171 let user = body.username.as_deref().unwrap_or("");
172 let device_name = body
173 .initial_device_display_name
174 .as_deref()
175 .unwrap_or("");
176
177 if !services.config.registration.enable && body.appservice_info.is_none() {
178 info!(
179 %is_guest,
180 %user,
181 %device_name,
182 "Rejecting registration attempt as registration is disabled"
183 );
184
185 return Err!(Request(Forbidden("Registration has been disabled.")));
186 }
187
188 if is_guest && !services.config.registration.guests.enable {
189 debug_warn!(
190 %device_name,
191 "Guest registration disabled, rejecting guest registration attempt"
192 );
193
194 return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
195 }
196
197 if !is_guest
198 && body.appservice_info.is_none()
199 && !services.config.allows_password_registration()
200 {
201 debug_warn!(
202 %user,
203 %device_name,
204 "Rejecting registration attempt as password login is disabled"
205 );
206
207 return Err!(Request(Forbidden("Password registration has been disabled.")));
208 }
209
210 Ok(())
211}
212
213async fn resolve_registration_user_id(
214 services: crate::State,
215 body: &Ruma<register::v3::Request>,
216 is_guest: bool,
217 emergency_mode_enabled: bool,
218) -> Result<OwnedUserId> {
219 let (Some(username), false) = (body.username.as_ref(), is_guest) else {
220 loop {
221 let proposed_user_id = UserId::parse_with_server_name(
222 utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
223 services.globals.server_name(),
224 )?;
225
226 if !services.users.exists(&proposed_user_id).await {
227 return Ok(proposed_user_id);
228 }
229 }
230 };
231
232 let is_irc = is_matrix_appservice_irc(body.appservice_info.as_ref());
233
234 if services
235 .config
236 .registration
237 .forbidden_usernames
238 .is_match(username)
239 && !emergency_mode_enabled
240 {
241 return Err!(Request(Forbidden("Username is forbidden")));
242 }
243
244 let body_username = if is_irc {
246 username.clone()
247 } else {
248 username.to_lowercase()
249 };
250
251 let proposed_user_id =
252 match UserId::parse_with_server_name(&body_username, services.globals.server_name()) {
253 | Ok(user_id) => {
254 if let Err(e) = user_id.validate_strict() {
255 if !is_irc && !emergency_mode_enabled {
259 return Err!(Request(InvalidUsername(debug_warn!(
260 "Username {body_username} contains disallowed characters or spaces: \
261 {e}"
262 ))));
263 }
264 }
265
266 user_id
267 },
268 | Err(e) => {
269 return Err!(Request(InvalidUsername(debug_warn!(
270 "Username {body_username} is not valid: {e}"
271 ))));
272 },
273 };
274
275 if services.users.exists(&proposed_user_id).await {
276 return Err!(Request(UserInUse("User ID is not available.")));
277 }
278
279 services
280 .users
281 .check_creation(&proposed_user_id)
282 .await?;
283
284 Ok(proposed_user_id)
285}
286
287async fn check_appservice_namespace(
288 services: crate::State,
289 body: &Ruma<register::v3::Request>,
290 user_id: &UserId,
291 emergency_mode_enabled: bool,
292) -> Result {
293 if body.body.login_type == Some(LoginType::ApplicationService) {
294 match body.appservice_info {
295 | Some(ref info) =>
296 if !info.is_user_match(user_id) && !emergency_mode_enabled {
297 return Err!(Request(Exclusive(
298 "Username is not in an appservice namespace."
299 )));
300 },
301 | _ => {
302 return Err!(Request(MissingToken("Missing appservice token.")));
303 },
304 }
305 } else if services
306 .appservice
307 .is_exclusive_user_id(user_id)
308 .await
309 && !emergency_mode_enabled
310 {
311 return Err!(Request(Exclusive("Username is reserved by an appservice.")));
312 }
313
314 Ok(())
315}
316
317async fn enforce_uiaa(
318 services: crate::State,
319 body: &Ruma<register::v3::Request>,
320 is_guest: bool,
321) -> Result<Option<Association>> {
322 if body.appservice_info.is_some() || is_guest {
323 return Ok(None);
324 }
325
326 let token_required = services.registration_tokens.is_enabled().await;
327 let terms = services.config.login_terms_params();
328
329 let smtp = &services.config.smtp;
330 let email_required = smtp.connection_uri.is_some()
331 && (smtp.require_email_for_registration
332 || (token_required && smtp.require_email_for_token_registration));
333
334 let stages: Vec<AuthType> = [
335 token_required.then_some(AuthType::RegistrationToken),
336 email_required.then_some(AuthType::EmailIdentity),
337 terms.is_some().then_some(AuthType::Terms),
338 ]
339 .into_iter()
340 .flatten()
341 .collect();
342
343 let stages = if stages.is_empty() {
345 vec![AuthType::Dummy]
346 } else {
347 stages
348 };
349
350 let params = terms
351 .as_ref()
352 .map(|terms| to_raw_value(&json!({ "m.login.terms": terms })))
353 .transpose()?;
354
355 let mut uiaainfo = UiaaInfo {
356 flows: vec![AuthFlow { stages }],
357 completed: Vec::new(),
358 params,
359 session: None,
360 auth_error: None,
361 };
362
363 let server_user = UserId::parse_with_server_name("", services.globals.server_name())?;
364 let server_device: &DeviceId = "".into();
365
366 match &body.auth {
367 | Some(auth) => {
368 let (worked, uiaainfo) = match email_required {
369 | true =>
370 services
371 .uiaa
372 .try_auth_registration(
373 &server_user,
374 server_device,
375 auth,
376 &uiaainfo,
377 body.rate_limit_ip.ok(),
378 )
379 .await?,
380 | false =>
381 services
382 .uiaa
383 .try_auth(
384 &server_user,
385 server_device,
386 auth,
387 &uiaainfo,
388 body.rate_limit_ip.ok(),
389 )
390 .await?,
391 };
392
393 if !worked {
394 return Err(Error::Uiaa(uiaainfo));
395 }
396
397 let session = uiaainfo.session.expect("session is always set");
398 let claim = (server_user, server_device.to_owned(), session.into());
399
400 let association = match email_required {
401 | false => None,
402 | true => {
403 let association = match services.threepid.redeem_claim(&claim).await {
404 | Ok(association) => association,
405 | Err(error)
406 if error.is_not_found() || matches!(&error, Error::Request(..)) =>
407 {
408 return Err!(Request(Forbidden("Invalid email identity proof.")));
409 },
410 | Err(error) => return Err(error),
411 };
412
413 if association.medium != Medium::Email {
414 return Err!(Request(Forbidden("Invalid email identity proof.")));
415 }
416
417 Some(association)
418 },
419 };
420
421 services
422 .uiaa
423 .update_uiaa_session(&claim.0, &claim.1, &claim.2, None);
424
425 Ok(association)
426 },
427 | _ => match body.json_body {
428 | None => Err!(Request(NotJson("JSON body is not valid"))),
429 | Some(ref json) => {
430 uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
431 services
432 .uiaa
433 .create(&server_user, server_device, &uiaainfo, json);
434
435 Err(Error::Uiaa(uiaainfo))
436 },
437 },
438 }
439}
440
441async fn record_accepted_terms(
442 services: crate::State,
443 user_id: &UserId,
444 body: &Ruma<register::v3::Request>,
445 is_guest: bool,
446) -> Result {
447 if is_guest || body.appservice_info.is_some() {
448 return Ok(());
449 }
450
451 let accepted: Vec<String> = services
452 .config
453 .registration_terms
454 .values()
455 .flat_map(|policy| policy.translations.values())
456 .map(|translation| translation.url.to_string())
457 .collect();
458
459 if accepted.is_empty() {
460 return Ok(());
461 }
462
463 let event_type = "m.accepted_terms";
464 let event = json!({
465 "type": event_type,
466 "content": { "accepted": accepted },
467 });
468
469 services
470 .account_data
471 .update(None, user_id, event_type.into(), &event)
472 .await
473}
474
475async fn bind_registration_email(
480 services: crate::State,
481 user_id: &UserId,
482 association: Option<&Association>,
483) {
484 if !services.sendmail.is_enabled() {
485 return;
486 }
487
488 let Some(association) = association else {
489 return;
490 };
491
492 if let Err(error) = bind_new_account_email(&services, user_id, &association.address).await {
493 warn!(%user_id, %error, "Skipping registration email binding");
494 }
495}
496
497async fn announce_new_user(
498 services: crate::State,
499 user_id: &UserId,
500 body: &Ruma<register::v3::Request>,
501 is_guest: bool,
502 client: &IpAddr,
503) -> Result {
504 let mut notice = String::from(if is_guest { "New guest user" } else { "New user" });
505
506 write!(notice, " \"{user_id}\" registered on this server from IP {client}")?;
507
508 if let Some(device_name) = body.initial_device_display_name.as_deref() {
509 write!(notice, " with device name {device_name}")?;
510 }
511
512 if is_guest {
513 debug_info!("{notice}");
514 } else {
515 info!("{notice}");
516 }
517
518 services.admin.notify(¬ice).await;
519
520 Ok(())
521}