1use std::fmt::Write;
2
3use axum::extract::State;
4use ruma::{
5 UserId,
6 api::client::{
7 account::{
8 check_registration_token_validity, get_username_availability,
9 register::{self, LoginType, RegistrationKind},
10 },
11 uiaa::{AuthFlow, AuthType, UiaaInfo},
12 },
13};
14use tuwunel_core::{Err, Error, Result, debug_info, debug_warn, info, utils};
15use tuwunel_service::users::{Register, device::generate_refresh_token};
16
17use super::SESSION_ID_LENGTH;
18use crate::{ClientIp, Ruma};
19
20const RANDOM_USER_ID_LENGTH: usize = 10;
21
22#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
34pub(crate) async fn get_register_available_route(
35 State(services): State<crate::State>,
36 ClientIp(client): ClientIp,
37 body: Ruma<get_username_availability::v3::Request>,
38) -> Result<get_username_availability::v3::Response> {
39 let is_matrix_appservice_irc = body
41 .appservice_info
42 .as_ref()
43 .is_some_and(|appservice| {
44 let id = &appservice.registration.id;
45 id == "irc"
46 || id.contains("matrix-appservice-irc")
47 || id.contains("matrix_appservice_irc")
48 });
49
50 if services
51 .config
52 .forbidden_usernames
53 .is_match(&body.username)
54 {
55 return Err!(Request(Forbidden("Username is forbidden")));
56 }
57
58 let body_username = if is_matrix_appservice_irc {
60 body.username.clone()
61 } else {
62 body.username.to_lowercase()
63 };
64
65 let user_id =
67 match UserId::parse_with_server_name(&body_username, services.globals.server_name()) {
68 | Ok(user_id) => {
69 if let Err(e) = user_id.validate_strict() {
70 if !is_matrix_appservice_irc {
74 return Err!(Request(InvalidUsername(debug_warn!(
75 "Username {body_username} contains disallowed characters or spaces: \
76 {e}"
77 ))));
78 }
79 }
80
81 user_id
82 },
83 | Err(e) => {
84 return Err!(Request(InvalidUsername(debug_warn!(
85 "Username {body_username} is not valid: {e}"
86 ))));
87 },
88 };
89
90 if services.users.exists(&user_id).await {
92 return Err!(Request(UserInUse("User ID is not available.")));
93 }
94
95 if let Some(ref info) = body.appservice_info
96 && !info.is_user_match(&user_id)
97 {
98 return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
99 }
100
101 if services
102 .appservice
103 .is_exclusive_user_id(&user_id)
104 .await
105 {
106 return Err!(Request(Exclusive("Username is reserved by an appservice.")));
107 }
108
109 Ok(get_username_availability::v3::Response { available: true })
110}
111
112#[expect(clippy::doc_markdown)]
130#[tracing::instrument(skip_all, fields(%client), name = "register")]
131pub(crate) async fn register_route(
132 State(services): State<crate::State>,
133 ClientIp(client): ClientIp,
134 body: Ruma<register::v3::Request>,
135) -> Result<register::v3::Response> {
136 let is_guest = body.kind == RegistrationKind::Guest;
137 let emergency_mode_enabled = services.config.emergency_password.is_some();
138
139 let user = body.username.as_deref().unwrap_or("");
140 let device_name = body
141 .initial_device_display_name
142 .as_deref()
143 .unwrap_or("");
144
145 if !services.config.allow_registration && body.appservice_info.is_none() {
146 info!(
147 %is_guest,
148 %user,
149 %device_name,
150 "Rejecting registration attempt as registration is disabled"
151 );
152
153 return Err!(Request(Forbidden("Registration has been disabled.")));
154 }
155
156 if is_guest && !services.config.allow_guest_registration {
157 debug_warn!(
158 %device_name,
159 "Guest registration disabled, rejecting guest registration attempt"
160 );
161
162 return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
163 }
164
165 let user_id = match (body.username.as_ref(), is_guest) {
166 | (Some(username), false) => {
167 let is_matrix_appservice_irc =
169 body.appservice_info
170 .as_ref()
171 .is_some_and(|appservice| {
172 appservice.registration.id == "irc"
173 || appservice
174 .registration
175 .id
176 .contains("matrix-appservice-irc")
177 || appservice
178 .registration
179 .id
180 .contains("matrix_appservice_irc")
181 });
182
183 if services
184 .config
185 .forbidden_usernames
186 .is_match(username)
187 && !emergency_mode_enabled
188 {
189 return Err!(Request(Forbidden("Username is forbidden")));
190 }
191
192 let body_username = if is_matrix_appservice_irc {
194 username.clone()
195 } else {
196 username.to_lowercase()
197 };
198
199 let proposed_user_id = match UserId::parse_with_server_name(
200 &body_username,
201 services.globals.server_name(),
202 ) {
203 | Ok(user_id) => {
204 if let Err(e) = user_id.validate_strict() {
205 if !is_matrix_appservice_irc && !emergency_mode_enabled {
209 return Err!(Request(InvalidUsername(debug_warn!(
210 "Username {body_username} contains disallowed characters or \
211 spaces: {e}"
212 ))));
213 }
214 }
215
216 user_id
217 },
218 | Err(e) => {
219 return Err!(Request(InvalidUsername(debug_warn!(
220 "Username {body_username} is not valid: {e}"
221 ))));
222 },
223 };
224
225 if services.users.exists(&proposed_user_id).await {
226 return Err!(Request(UserInUse("User ID is not available.")));
227 }
228
229 proposed_user_id
230 },
231 | _ => loop {
232 let proposed_user_id = UserId::parse_with_server_name(
233 utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
234 services.globals.server_name(),
235 )?;
236
237 if !services.users.exists(&proposed_user_id).await {
238 break proposed_user_id;
239 }
240 },
241 };
242
243 if body.body.login_type == Some(LoginType::ApplicationService) {
244 match body.appservice_info {
245 | Some(ref info) =>
246 if !info.is_user_match(&user_id) && !emergency_mode_enabled {
247 return Err!(Request(Exclusive(
248 "Username is not in an appservice namespace."
249 )));
250 },
251 | _ => {
252 return Err!(Request(MissingToken("Missing appservice token.")));
253 },
254 }
255 } else if services
256 .appservice
257 .is_exclusive_user_id(&user_id)
258 .await && !emergency_mode_enabled
259 {
260 return Err!(Request(Exclusive("Username is reserved by an appservice.")));
261 }
262
263 let mut uiaainfo;
265 let skip_auth = if services.registration_tokens.is_enabled().await && !is_guest {
266 uiaainfo = UiaaInfo {
268 flows: vec![AuthFlow {
269 stages: vec![AuthType::RegistrationToken],
270 }],
271 completed: Vec::new(),
272 params: Default::default(),
273 session: None,
274 auth_error: None,
275 };
276
277 body.appservice_info.is_some()
278 } else {
279 uiaainfo = UiaaInfo {
281 flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
282 completed: Vec::new(),
283 params: Default::default(),
284 session: None,
285 auth_error: None,
286 };
287
288 body.appservice_info.is_some() || is_guest
289 };
290
291 if !skip_auth {
292 match &body.auth {
293 | Some(auth) => {
294 let (worked, uiaainfo) = services
295 .uiaa
296 .try_auth(
297 &UserId::parse_with_server_name("", services.globals.server_name())?,
298 "".into(),
299 auth,
300 &uiaainfo,
301 )
302 .await?;
303 if !worked {
304 return Err(Error::Uiaa(uiaainfo));
305 }
306 },
308 | _ => match body.json_body {
309 | Some(ref json) => {
310 uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
311 services.uiaa.create(
312 &UserId::parse_with_server_name("", services.globals.server_name())?,
313 "".into(),
314 &uiaainfo,
315 json,
316 );
317 return Err(Error::Uiaa(uiaainfo));
318 },
319 | _ => {
320 return Err!(Request(NotJson("JSON body is not valid")));
321 },
322 },
323 }
324 }
325
326 let password = if is_guest { None } else { body.password.as_deref() };
327
328 services
329 .users
330 .full_register(Register {
331 user_id: Some(&user_id),
332 password,
333 is_appservice: body.appservice_info.is_some(),
334 is_guest,
335 grant_first_user_admin: true,
336 ..Default::default()
337 })
338 .await?;
339
340 if (!is_guest && body.inhibit_login)
341 || body
342 .appservice_info
343 .as_ref()
344 .is_some_and(|appservice| appservice.registration.device_management)
345 {
346 return Ok(register::v3::Response {
347 user_id,
348 device_id: None,
349 access_token: None,
350 refresh_token: None,
351 expires_in: None,
352 });
353 }
354
355 let device_id = if is_guest { None } else { body.device_id.as_deref() };
356
357 let (access_token, expires_in) = services
359 .users
360 .generate_access_token(body.refresh_token);
361
362 let refresh_token = expires_in.is_some().then(generate_refresh_token);
364
365 let device_id = services
367 .users
368 .create_device(
369 &user_id,
370 device_id,
371 (Some(&access_token), expires_in),
372 refresh_token.as_deref(),
373 body.initial_device_display_name.as_deref(),
374 Some(client),
375 )
376 .await?;
377
378 debug_info!(%user_id, %device_id, "User account was created");
379
380 if body.appservice_info.is_none() && (!is_guest || services.config.log_guest_registrations) {
381 let mut notice = String::from(if is_guest { "New guest user" } else { "New user" });
382
383 write!(notice, " \"{user_id}\" registered on this server from IP {client}")?;
384
385 if let Some(device_name) = body.initial_device_display_name.as_deref() {
386 write!(notice, " with device name {device_name}")?;
387 }
388
389 if !is_guest {
390 info!("{notice}");
391 } else {
392 debug_info!("{notice}");
393 }
394
395 if services.server.config.admin_room_notices {
396 services.admin.notice(¬ice).await;
397 }
398 }
399
400 Ok(register::v3::Response {
401 user_id,
402 device_id: Some(device_id),
403 access_token: Some(access_token),
404 refresh_token,
405 expires_in,
406 })
407}
408
409pub(crate) async fn check_registration_token_validity(
416 State(services): State<crate::State>,
417 body: Ruma<check_registration_token_validity::v1::Request>,
418) -> Result<check_registration_token_validity::v1::Response> {
419 if !services.registration_tokens.is_enabled().await {
420 return Err!(Request(Forbidden("Server does not allow token registration")));
421 }
422
423 let valid = services
424 .registration_tokens
425 .is_token_valid(&body.token)
426 .await
427 .is_ok();
428
429 Ok(check_registration_token_validity::v1::Response { valid })
430}