2026-03-21 20:59:41 -04:00
|
|
|
use std::{collections::HashMap, fmt::Write};
|
2024-05-03 21:42:47 -04:00
|
|
|
|
2024-07-16 08:05:25 +00:00
|
|
|
use axum::extract::State;
|
2024-06-10 21:04:51 -04:00
|
|
|
use axum_client_ip::InsecureClientIp;
|
2024-12-15 00:05:47 -05:00
|
|
|
use conduwuit::{
|
2026-03-21 21:33:23 -04:00
|
|
|
Err, Result, debug_info, error, info,
|
|
|
|
|
utils::{self},
|
2025-03-19 20:55:14 -04:00
|
|
|
warn,
|
2024-12-15 00:05:47 -05:00
|
|
|
};
|
2025-04-04 03:30:13 +00:00
|
|
|
use conduwuit_service::Services;
|
2024-08-08 17:18:30 +00:00
|
|
|
use futures::{FutureExt, StreamExt};
|
2022-10-05 20:34:31 +02:00
|
|
|
use register::RegistrationKind;
|
2020-07-30 18:14:47 +02:00
|
|
|
use ruma::{
|
2026-03-21 21:33:23 -04:00
|
|
|
OwnedUserId, UserId,
|
2020-07-30 18:14:47 +02:00
|
|
|
api::client::{
|
2026-03-21 21:33:23 -04:00
|
|
|
account::register::{self, LoginType},
|
2026-03-21 20:59:41 -04:00
|
|
|
uiaa::{AuthFlow, AuthType},
|
2025-07-02 19:32:50 +01:00
|
|
|
},
|
2026-03-21 21:33:23 -04:00
|
|
|
events::{GlobalAccountDataEventType, room::message::RoomMessageEventContent},
|
2025-07-02 19:32:50 +01:00
|
|
|
push,
|
2020-07-30 18:14:47 +02:00
|
|
|
};
|
2026-03-21 20:59:41 -04:00
|
|
|
use serde_json::value::RawValue;
|
2020-07-30 18:14:47 +02:00
|
|
|
|
2026-03-21 20:59:41 -04:00
|
|
|
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
|
2024-07-22 07:43:51 +00:00
|
|
|
use crate::Ruma;
|
2020-07-30 18:14:47 +02:00
|
|
|
|
2022-06-18 11:13:37 +00:00
|
|
|
const RANDOM_USER_ID_LENGTH: usize = 10;
|
2020-07-30 18:14:47 +02:00
|
|
|
|
2024-02-15 20:47:12 -05:00
|
|
|
/// # `POST /_matrix/client/v3/register`
|
2020-07-31 14:40:28 +02:00
|
|
|
///
|
|
|
|
|
/// Register an account on this homeserver.
|
|
|
|
|
///
|
2024-02-15 20:47:12 -05:00
|
|
|
/// You can use [`GET
|
|
|
|
|
/// /_matrix/client/v3/register/available`](fn.get_register_available_route.
|
2021-08-31 19:14:37 +02:00
|
|
|
/// html) to check if the user id is valid and available.
|
|
|
|
|
///
|
|
|
|
|
/// - Only works if registration is enabled
|
|
|
|
|
/// - If type is guest: ignores all parameters except
|
|
|
|
|
/// initial_device_display_name
|
|
|
|
|
/// - If sender is not appservice: Requires UIAA (but we only use a dummy stage)
|
|
|
|
|
/// - If type is not guest and no username is given: Always fails after UIAA
|
|
|
|
|
/// check
|
|
|
|
|
/// - Creates a new account and populates it with default account data
|
|
|
|
|
/// - If `inhibit_login` is false: Creates a device and returns device id and
|
|
|
|
|
/// access_token
|
2024-03-22 22:44:31 -04:00
|
|
|
#[allow(clippy::doc_markdown)]
|
2026-01-04 03:04:37 +00:00
|
|
|
#[tracing::instrument(skip_all, fields(%client), name = "register", level = "info")]
|
2024-06-10 21:04:51 -04:00
|
|
|
pub(crate) async fn register_route(
|
2024-12-15 00:05:47 -05:00
|
|
|
State(services): State<crate::State>,
|
|
|
|
|
InsecureClientIp(client): InsecureClientIp,
|
|
|
|
|
body: Ruma<register::v3::Request>,
|
2024-06-10 21:04:51 -04:00
|
|
|
) -> Result<register::v3::Response> {
|
2025-03-02 23:16:30 -05:00
|
|
|
let is_guest = body.kind == RegistrationKind::Guest;
|
|
|
|
|
let emergency_mode_enabled = services.config.emergency_password.is_some();
|
|
|
|
|
|
2026-02-12 18:24:24 -05:00
|
|
|
// Allow registration if it's enabled in the config file or if this is the first
|
|
|
|
|
// run (so the first user account can be created)
|
|
|
|
|
let allow_registration =
|
|
|
|
|
services.config.allow_registration || services.firstrun.is_first_run();
|
|
|
|
|
|
|
|
|
|
if !allow_registration && body.appservice_info.is_none() {
|
2025-03-02 23:16:30 -05:00
|
|
|
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
|
|
|
|
|
| (Some(username), Some(device_display_name)) => {
|
2025-04-27 02:39:28 +00:00
|
|
|
info!(
|
|
|
|
|
%is_guest,
|
|
|
|
|
user = %username,
|
|
|
|
|
device_name = %device_display_name,
|
|
|
|
|
"Rejecting registration attempt as registration is disabled"
|
|
|
|
|
);
|
2025-03-02 23:16:30 -05:00
|
|
|
},
|
|
|
|
|
| (Some(username), _) => {
|
2025-04-27 02:39:28 +00:00
|
|
|
info!(
|
|
|
|
|
%is_guest,
|
|
|
|
|
user = %username,
|
|
|
|
|
"Rejecting registration attempt as registration is disabled"
|
|
|
|
|
);
|
2025-03-02 23:16:30 -05:00
|
|
|
},
|
|
|
|
|
| (_, Some(device_display_name)) => {
|
2025-04-27 02:39:28 +00:00
|
|
|
info!(
|
|
|
|
|
%is_guest,
|
|
|
|
|
device_name = %device_display_name,
|
|
|
|
|
"Rejecting registration attempt as registration is disabled"
|
|
|
|
|
);
|
2025-03-02 23:16:30 -05:00
|
|
|
},
|
|
|
|
|
| (None, _) => {
|
2025-04-27 02:39:28 +00:00
|
|
|
info!(
|
|
|
|
|
%is_guest,
|
|
|
|
|
"Rejecting registration attempt as registration is disabled"
|
|
|
|
|
);
|
2025-03-02 23:16:30 -05:00
|
|
|
},
|
2025-03-11 23:05:56 -04:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2026-01-06 11:51:12 -05:00
|
|
|
return Err!(Request(Forbidden(
|
|
|
|
|
"This server is not accepting registrations at this time."
|
|
|
|
|
)));
|
2025-03-02 23:16:30 -05:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2026-02-12 10:16:03 -05:00
|
|
|
if is_guest && !services.config.allow_guest_registration {
|
2024-01-24 22:29:30 -05:00
|
|
|
info!(
|
2026-02-12 10:16:03 -05:00
|
|
|
"Guest registration disabled, rejecting guest registration attempt, initial device \
|
|
|
|
|
name: \"{}\"",
|
2024-11-02 18:52:25 -04:00
|
|
|
body.initial_device_display_name.as_deref().unwrap_or("")
|
2024-01-24 22:29:30 -05:00
|
|
|
);
|
2025-03-02 23:16:30 -05:00
|
|
|
return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
|
2023-12-21 20:44:58 -05:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2023-12-21 21:16:56 -05:00
|
|
|
// forbid guests from registering if there is not a real admin user yet. give
|
|
|
|
|
// generic user error.
|
2026-03-21 20:59:41 -04:00
|
|
|
if is_guest && services.firstrun.is_first_run() {
|
2023-12-21 21:16:56 -05:00
|
|
|
warn!(
|
2024-12-15 00:05:47 -05:00
|
|
|
"Guest account attempted to register before a real admin user has been registered, \
|
|
|
|
|
rejecting registration. Guest's initial device name: \"{}\"",
|
2024-11-02 18:52:25 -04:00
|
|
|
body.initial_device_display_name.as_deref().unwrap_or("")
|
2024-03-05 19:48:54 -05:00
|
|
|
);
|
2026-01-06 11:51:12 -05:00
|
|
|
return Err!(Request(Forbidden(
|
|
|
|
|
"This server is not accepting registrations at this time."
|
|
|
|
|
)));
|
2024-03-05 19:48:54 -05:00
|
|
|
}
|
|
|
|
|
|
2026-03-21 20:59:41 -04:00
|
|
|
let user_id = determine_registration_user_id(
|
|
|
|
|
&services,
|
|
|
|
|
body.username.clone(),
|
|
|
|
|
body.appservice_info.as_ref(),
|
|
|
|
|
is_guest,
|
|
|
|
|
emergency_mode_enabled,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2024-04-16 22:39:49 -04:00
|
|
|
if body.body.login_type == Some(LoginType::ApplicationService) {
|
2026-03-21 20:59:41 -04:00
|
|
|
// For appservice logins, make sure that the user ID is in the appservice's
|
|
|
|
|
// namespace
|
|
|
|
|
|
2025-02-23 01:17:45 -05:00
|
|
|
match body.appservice_info {
|
|
|
|
|
| Some(ref info) =>
|
2025-03-02 23:16:30 -05:00
|
|
|
if !info.is_user_match(&user_id) && !emergency_mode_enabled {
|
|
|
|
|
return Err!(Request(Exclusive(
|
|
|
|
|
"Username is not in an appservice namespace."
|
|
|
|
|
)));
|
2025-02-23 01:17:45 -05:00
|
|
|
},
|
|
|
|
|
| _ => {
|
2025-03-02 23:16:30 -05:00
|
|
|
return Err!(Request(MissingToken("Missing appservice token.")));
|
2025-02-23 01:17:45 -05:00
|
|
|
},
|
2024-04-16 22:39:49 -04:00
|
|
|
}
|
2025-03-02 23:16:30 -05:00
|
|
|
} else if services.appservice.is_exclusive_user_id(&user_id).await && !emergency_mode_enabled
|
|
|
|
|
{
|
2026-03-21 20:59:41 -04:00
|
|
|
// For non-appservice logins, ban user IDs which are in an appservice's
|
|
|
|
|
// namespace (unless emergency mode is enabled)
|
2025-03-02 23:16:30 -05:00
|
|
|
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
2024-04-16 22:39:49 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-21 20:59:41 -04:00
|
|
|
// Appeservices and guests get to skip auth
|
2025-07-12 22:37:49 +01:00
|
|
|
let skip_auth = body.appservice_info.is_some() || is_guest;
|
|
|
|
|
|
2026-03-21 20:59:41 -04:00
|
|
|
let identity = if skip_auth {
|
|
|
|
|
// Appservices and guests have no identity
|
|
|
|
|
None
|
2026-02-12 18:24:24 -05:00
|
|
|
} else {
|
2026-03-21 20:59:41 -04:00
|
|
|
// Perform UIAA to determine the user's identity
|
|
|
|
|
let (flows, params) = create_registration_uiaa_session(&services).await?;
|
2025-07-12 22:37:49 +01:00
|
|
|
|
2026-03-21 20:59:41 -04:00
|
|
|
Some(
|
|
|
|
|
services
|
|
|
|
|
.uiaa
|
|
|
|
|
.authenticate(&body.auth, flows, params, None)
|
|
|
|
|
.await?,
|
|
|
|
|
)
|
|
|
|
|
};
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2024-12-15 00:05:47 -05:00
|
|
|
let password = if is_guest { None } else { body.password.as_deref() };
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2026-03-22 11:46:26 -04:00
|
|
|
// If the user registered with an email, associate it with their account.
|
|
|
|
|
// Do this _before_ creating the user to make sure that, if their email is
|
|
|
|
|
// already in use, we don't make them an account.
|
|
|
|
|
//
|
|
|
|
|
// Note that this should only rarely cause a bailout because email uniqueness is
|
|
|
|
|
// also checked by /requestToken.
|
2026-03-21 20:59:41 -04:00
|
|
|
#[allow(clippy::collapsible_if)]
|
|
|
|
|
if let Some(identity) = identity {
|
|
|
|
|
if let Some(email) = identity.email {
|
|
|
|
|
services
|
|
|
|
|
.threepid
|
2026-03-22 11:46:26 -04:00
|
|
|
.associate_localpart_email(user_id.localpart(), &email)
|
|
|
|
|
.await?;
|
2026-03-21 20:59:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-22 11:46:26 -04:00
|
|
|
// Create user
|
|
|
|
|
services.users.create(&user_id, password, None).await?;
|
|
|
|
|
|
2026-03-21 20:59:41 -04:00
|
|
|
// Set an initial display name
|
2022-06-23 06:58:34 +00:00
|
|
|
let mut displayname = user_id.localpart().to_owned();
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2026-03-21 20:59:41 -04:00
|
|
|
// Apply the new user displayname suffix, if it's set
|
2024-12-15 00:05:47 -05:00
|
|
|
if !services.globals.new_user_displayname_suffix().is_empty()
|
|
|
|
|
&& body.appservice_info.is_none()
|
|
|
|
|
{
|
2025-04-26 23:01:21 +00:00
|
|
|
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?;
|
2022-06-23 06:58:34 +00:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2024-07-16 08:05:25 +00:00
|
|
|
services
|
2024-03-25 17:05:11 -04:00
|
|
|
.users
|
2024-08-08 17:18:30 +00:00
|
|
|
.set_displayname(&user_id, Some(displayname.clone()));
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2021-08-31 19:14:37 +02:00
|
|
|
// Initial account data
|
2024-08-08 17:18:30 +00:00
|
|
|
services
|
|
|
|
|
.account_data
|
|
|
|
|
.update(
|
|
|
|
|
None,
|
|
|
|
|
&user_id,
|
|
|
|
|
GlobalAccountDataEventType::PushRules.to_string().into(),
|
|
|
|
|
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
|
|
|
|
|
content: ruma::events::push_rules::PushRulesEventContent {
|
|
|
|
|
global: push::Ruleset::server_default(&user_id),
|
|
|
|
|
},
|
2025-04-26 23:01:21 +00:00
|
|
|
})?,
|
2024-08-08 17:18:30 +00:00
|
|
|
)
|
|
|
|
|
.await?;
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2025-09-11 17:21:20 +01:00
|
|
|
// Generate new device id if the user didn't specify one
|
|
|
|
|
let no_device = body.inhibit_login
|
2025-04-03 12:20:10 -04:00
|
|
|
|| body
|
|
|
|
|
.appservice_info
|
|
|
|
|
.as_ref()
|
2025-09-11 17:21:20 +01:00
|
|
|
.is_some_and(|aps| aps.registration.device_management);
|
|
|
|
|
let (token, device) = if !no_device {
|
|
|
|
|
// Don't create a device for inhibited logins
|
|
|
|
|
let device_id = if is_guest { None } else { body.device_id.clone() }
|
|
|
|
|
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
|
|
|
|
|
|
|
|
|
// Generate new token for the device
|
|
|
|
|
let new_token = utils::random_string(TOKEN_LENGTH);
|
|
|
|
|
|
|
|
|
|
// Create device for this account
|
|
|
|
|
services
|
|
|
|
|
.users
|
|
|
|
|
.create_device(
|
|
|
|
|
&user_id,
|
|
|
|
|
&device_id,
|
|
|
|
|
&new_token,
|
|
|
|
|
body.initial_device_display_name.clone(),
|
|
|
|
|
Some(client.to_string()),
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
debug_info!(%user_id, %device_id, "User account was created");
|
|
|
|
|
(Some(new_token), Some(device_id))
|
|
|
|
|
} else {
|
|
|
|
|
(None, None)
|
|
|
|
|
};
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2024-11-02 18:52:25 -04:00
|
|
|
let device_display_name = body.initial_device_display_name.as_deref().unwrap_or("");
|
2024-09-07 10:10:46 -04:00
|
|
|
|
2023-12-21 20:46:24 -05:00
|
|
|
// log in conduit admin channel if a non-guest user registered
|
2024-04-16 22:39:49 -04:00
|
|
|
if body.appservice_info.is_none() && !is_guest {
|
2024-09-07 10:10:46 -04:00
|
|
|
if !device_display_name.is_empty() {
|
2025-04-26 23:04:58 +00:00
|
|
|
let notice = format!(
|
|
|
|
|
"New user \"{user_id}\" registered on this server from IP {client} and device \
|
|
|
|
|
display name \"{device_display_name}\""
|
2024-11-02 18:52:25 -04:00
|
|
|
);
|
2024-10-24 22:19:18 -04:00
|
|
|
|
2025-04-26 23:04:58 +00:00
|
|
|
info!("{notice}");
|
2025-01-24 07:02:56 +00:00
|
|
|
if services.server.config.admin_room_notices {
|
2025-04-26 23:04:58 +00:00
|
|
|
services.admin.notice(¬ice).await;
|
2024-10-24 22:19:18 -04:00
|
|
|
}
|
2024-09-07 10:10:46 -04:00
|
|
|
} else {
|
2025-04-26 23:04:58 +00:00
|
|
|
let notice = format!("New user \"{user_id}\" registered on this server.");
|
2024-10-24 22:19:18 -04:00
|
|
|
|
2025-04-26 23:04:58 +00:00
|
|
|
info!("{notice}");
|
2025-01-24 07:02:56 +00:00
|
|
|
if services.server.config.admin_room_notices {
|
2025-04-26 23:04:58 +00:00
|
|
|
services.admin.notice(¬ice).await;
|
2024-10-24 22:19:18 -04:00
|
|
|
}
|
2024-09-07 10:10:46 -04:00
|
|
|
}
|
2023-08-09 18:27:30 +02:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2023-12-21 20:46:24 -05:00
|
|
|
// log in conduit admin channel if a guest registered
|
2025-03-31 02:28:01 +00:00
|
|
|
if body.appservice_info.is_none() && is_guest && services.config.log_guest_registrations {
|
2025-03-02 23:16:30 -05:00
|
|
|
debug_info!("New guest user \"{user_id}\" registered on this server.");
|
2024-05-23 15:46:21 -04:00
|
|
|
|
2024-09-07 10:10:46 -04:00
|
|
|
if !device_display_name.is_empty() {
|
2025-01-24 07:02:56 +00:00
|
|
|
if services.server.config.admin_room_notices {
|
2024-10-24 22:19:18 -04:00
|
|
|
services
|
|
|
|
|
.admin
|
2025-04-26 23:04:58 +00:00
|
|
|
.notice(&format!(
|
2024-12-15 00:05:47 -05:00
|
|
|
"Guest user \"{user_id}\" with device display name \
|
|
|
|
|
\"{device_display_name}\" registered on this server from IP {client}"
|
2025-04-26 23:04:58 +00:00
|
|
|
))
|
|
|
|
|
.await;
|
2024-10-24 22:19:18 -04:00
|
|
|
}
|
2024-04-14 13:42:14 -04:00
|
|
|
} else {
|
2024-10-24 22:19:18 -04:00
|
|
|
#[allow(clippy::collapsible_else_if)]
|
2025-01-24 07:02:56 +00:00
|
|
|
if services.server.config.admin_room_notices {
|
2024-10-24 22:19:18 -04:00
|
|
|
services
|
|
|
|
|
.admin
|
2025-04-26 23:04:58 +00:00
|
|
|
.notice(&format!(
|
2024-12-15 00:05:47 -05:00
|
|
|
"Guest user \"{user_id}\" with no device display name registered on \
|
|
|
|
|
this server from IP {client}",
|
2025-04-26 23:04:58 +00:00
|
|
|
))
|
|
|
|
|
.await;
|
2024-10-24 22:19:18 -04:00
|
|
|
}
|
2024-04-14 13:42:14 -04:00
|
|
|
}
|
2023-12-21 20:46:24 -05:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2024-03-16 14:11:03 -04:00
|
|
|
if !is_guest {
|
2026-02-12 18:24:24 -05:00
|
|
|
// Make the first user to register an administrator and disable first-run mode.
|
|
|
|
|
let was_first_user = services.firstrun.empower_first_user(&user_id).await?;
|
|
|
|
|
|
|
|
|
|
// If the registering user was not the first and we're suspending users on
|
|
|
|
|
// register, suspend them.
|
|
|
|
|
if !was_first_user && services.config.suspend_on_register {
|
|
|
|
|
// Note that we can still do auto joins for suspended users
|
|
|
|
|
services
|
|
|
|
|
.users
|
|
|
|
|
.suspend_account(&user_id, &services.globals.server_user)
|
|
|
|
|
.await;
|
|
|
|
|
// And send an @room notice to the admin room, to prompt admins to review the
|
|
|
|
|
// new user and ideally unsuspend them if deemed appropriate.
|
|
|
|
|
if services.server.config.admin_room_notices {
|
2025-07-02 00:41:34 +01:00
|
|
|
services
|
2026-02-12 18:24:24 -05:00
|
|
|
.admin
|
|
|
|
|
.send_loud_message(RoomMessageEventContent::text_plain(format!(
|
|
|
|
|
"User {user_id} has been suspended as they are not the first user on \
|
|
|
|
|
this server. Please review and unsuspend them if appropriate."
|
|
|
|
|
)))
|
|
|
|
|
.await
|
|
|
|
|
.ok();
|
2024-03-16 14:11:03 -04:00
|
|
|
}
|
|
|
|
|
}
|
2020-10-05 22:19:22 +02:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2024-04-16 22:39:49 -04:00
|
|
|
if body.appservice_info.is_none()
|
2025-01-24 07:02:56 +00:00
|
|
|
&& !services.server.config.auto_join_rooms.is_empty()
|
2025-03-31 02:28:01 +00:00
|
|
|
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
|
2024-04-13 20:33:24 -04:00
|
|
|
{
|
2025-01-24 07:02:56 +00:00
|
|
|
for room in &services.server.config.auto_join_rooms {
|
2024-11-02 20:55:40 -04:00
|
|
|
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
|
2024-12-15 00:05:47 -05:00
|
|
|
error!(
|
|
|
|
|
"Failed to resolve room alias to room ID when attempting to auto join \
|
|
|
|
|
{room}, skipping"
|
|
|
|
|
);
|
2024-11-02 20:55:40 -04:00
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-16 08:05:25 +00:00
|
|
|
if !services
|
2024-03-25 17:05:11 -04:00
|
|
|
.rooms
|
|
|
|
|
.state_cache
|
2024-11-02 20:55:40 -04:00
|
|
|
.server_in_room(services.globals.server_name(), &room_id)
|
2024-08-08 17:18:30 +00:00
|
|
|
.await
|
2024-03-25 17:05:11 -04:00
|
|
|
{
|
2024-12-15 00:05:47 -05:00
|
|
|
warn!(
|
|
|
|
|
"Skipping room {room} to automatically join as we have never joined before."
|
|
|
|
|
);
|
2024-03-20 18:09:07 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-02 20:55:40 -04:00
|
|
|
if let Some(room_server_name) = room.server_name() {
|
2025-02-23 01:17:45 -05:00
|
|
|
match join_room_by_id_helper(
|
2024-07-27 07:17:07 +00:00
|
|
|
&services,
|
2024-07-07 15:02:09 -04:00
|
|
|
&user_id,
|
2024-11-02 20:55:40 -04:00
|
|
|
&room_id,
|
2024-03-20 18:09:07 -04:00
|
|
|
Some("Automatically joining this room upon registration".to_owned()),
|
2024-11-02 20:55:40 -04:00
|
|
|
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
2024-09-14 12:27:22 -04:00
|
|
|
&body.appservice_info,
|
2024-03-20 11:19:41 -04:00
|
|
|
)
|
2024-08-08 17:18:30 +00:00
|
|
|
.boxed()
|
2024-03-20 11:19:41 -04:00
|
|
|
.await
|
|
|
|
|
{
|
2025-02-23 01:17:45 -05:00
|
|
|
| Err(e) => {
|
|
|
|
|
// don't return this error so we don't fail registrations
|
|
|
|
|
error!(
|
|
|
|
|
"Failed to automatically join room {room} for user {user_id}: {e}"
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
| _ => {
|
|
|
|
|
info!("Automatically joined room {room} for user {user_id}");
|
|
|
|
|
},
|
2025-02-25 18:38:12 +00:00
|
|
|
}
|
2024-03-20 11:19:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-18 15:33:14 +01:00
|
|
|
Ok(register::v3::Response {
|
2025-09-11 17:21:20 +01:00
|
|
|
access_token: token,
|
2020-07-30 18:14:47 +02:00
|
|
|
user_id,
|
2025-09-11 17:21:20 +01:00
|
|
|
device_id: device,
|
2022-10-09 17:25:06 +02:00
|
|
|
refresh_token: None,
|
|
|
|
|
expires_in: None,
|
2022-01-22 16:58:32 +01:00
|
|
|
})
|
2020-07-30 18:14:47 +02:00
|
|
|
}
|
|
|
|
|
|
2026-03-21 20:59:41 -04:00
|
|
|
/// Determine which flows and parameters should be presented when
|
|
|
|
|
/// registering a new account.
|
|
|
|
|
async fn create_registration_uiaa_session(
|
|
|
|
|
services: &Services,
|
|
|
|
|
) -> Result<(Vec<AuthFlow>, Box<RawValue>)> {
|
|
|
|
|
let mut flows = vec![];
|
|
|
|
|
let mut params = HashMap::<String, serde_json::Value>::new();
|
|
|
|
|
|
|
|
|
|
if services.firstrun.is_first_run() {
|
|
|
|
|
// Registration token forced while in first-run mode
|
|
|
|
|
flows.push(AuthFlow::new(vec![AuthType::RegistrationToken]));
|
|
|
|
|
} else {
|
|
|
|
|
if services
|
|
|
|
|
.registration_tokens
|
|
|
|
|
.iterate_tokens()
|
|
|
|
|
.next()
|
|
|
|
|
.await
|
|
|
|
|
.is_some()
|
|
|
|
|
{
|
|
|
|
|
// Registration token flow is available
|
|
|
|
|
flows.push(AuthFlow::new(vec![AuthType::RegistrationToken]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if services.config.recaptcha_private_site_key.is_some() {
|
|
|
|
|
if let Some(pubkey) = &services.config.recaptcha_site_key {
|
|
|
|
|
// ReCaptcha flow is available
|
|
|
|
|
flows.push(AuthFlow::new(vec![AuthType::ReCaptcha]));
|
|
|
|
|
|
|
|
|
|
params.insert(
|
|
|
|
|
AuthType::ReCaptcha.as_str().to_owned(),
|
|
|
|
|
serde_json::json!({
|
|
|
|
|
"public_key": pubkey,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if flows.is_empty() {
|
|
|
|
|
// Registration is enabled, but no flows are configured. Bail out by default
|
|
|
|
|
// unless open registration was explicitly enabled.
|
|
|
|
|
if !services
|
|
|
|
|
.config
|
|
|
|
|
.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
|
|
|
|
|
{
|
|
|
|
|
return Err!(Request(Forbidden(
|
|
|
|
|
"This server is not accepting registrations at this time."
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We have open registration enabled (😧), provide a dummy flow
|
|
|
|
|
flows.push(AuthFlow { stages: vec![AuthType::Dummy] });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let params = serde_json::value::to_raw_value(¶ms).expect("params should be valid JSON");
|
|
|
|
|
|
|
|
|
|
Ok((flows, params))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn determine_registration_user_id(
|
|
|
|
|
services: &Services,
|
|
|
|
|
supplied_username: Option<String>,
|
|
|
|
|
appservice_info: Option<&service::appservice::RegistrationInfo>,
|
|
|
|
|
is_guest: bool,
|
|
|
|
|
emergency_mode_enabled: bool,
|
|
|
|
|
) -> Result<OwnedUserId> {
|
|
|
|
|
if let Some(mut supplied_username) = supplied_username
|
|
|
|
|
&& !is_guest
|
|
|
|
|
{
|
|
|
|
|
// The user gets to pick their username. Do some validation to make sure it's
|
|
|
|
|
// acceptable.
|
|
|
|
|
|
|
|
|
|
// Don't allow registration with forbidden usernames.
|
|
|
|
|
if services
|
|
|
|
|
.globals
|
|
|
|
|
.forbidden_usernames()
|
|
|
|
|
.is_match(&supplied_username)
|
|
|
|
|
&& !emergency_mode_enabled
|
|
|
|
|
{
|
|
|
|
|
return Err!(Request(Forbidden("Username is forbidden")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
|
|
|
|
let is_matrix_appservice_irc = appservice_info.is_some_and(|appservice| {
|
|
|
|
|
appservice.registration.id == "irc"
|
|
|
|
|
|| appservice.registration.id.contains("matrix-appservice-irc")
|
|
|
|
|
|| appservice.registration.id.contains("matrix_appservice_irc")
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Don't force the username lowercase if it's from matrix-appservice-irc.
|
|
|
|
|
if !is_matrix_appservice_irc {
|
|
|
|
|
supplied_username = supplied_username.to_lowercase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create and validate the user ID
|
|
|
|
|
let user_id = match UserId::parse_with_server_name(
|
|
|
|
|
&supplied_username,
|
|
|
|
|
services.globals.server_name(),
|
|
|
|
|
) {
|
|
|
|
|
| Ok(user_id) => {
|
|
|
|
|
if let Err(e) = user_id.validate_strict() {
|
|
|
|
|
// unless the username is from the broken matrix appservice IRC bridge, or
|
|
|
|
|
// we are in emergency mode, we should follow synapse's behaviour on
|
|
|
|
|
// not allowing things like spaces and UTF-8 characters in usernames
|
|
|
|
|
if !is_matrix_appservice_irc && !emergency_mode_enabled {
|
|
|
|
|
return Err!(Request(InvalidUsername(debug_warn!(
|
|
|
|
|
"Username {supplied_username} contains disallowed characters or \
|
|
|
|
|
spaces: {e}"
|
|
|
|
|
))));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't allow registration with user IDs that aren't local
|
|
|
|
|
if !services.globals.user_is_local(&user_id) {
|
|
|
|
|
return Err!(Request(InvalidUsername(
|
|
|
|
|
"Username {supplied_username} is not local to this server"
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
user_id
|
|
|
|
|
},
|
|
|
|
|
| Err(e) => {
|
|
|
|
|
return Err!(Request(InvalidUsername(debug_warn!(
|
|
|
|
|
"Username {supplied_username} is not valid: {e}"
|
|
|
|
|
))));
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if services.users.exists(&user_id).await {
|
|
|
|
|
return Err!(Request(UserInUse("User ID is not available.")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(user_id)
|
|
|
|
|
} else {
|
|
|
|
|
// The user is a guest or is lacking in creativity. Generate a username for
|
|
|
|
|
// them.
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
let user_id = UserId::parse_with_server_name(
|
|
|
|
|
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
|
|
|
|
|
services.globals.server_name(),
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
if !services.users.exists(&user_id).await {
|
|
|
|
|
break Ok(user_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|