refactor: Remove support for guest user registration

This commit is contained in:
Ginger
2026-05-05 09:09:38 -04:00
parent 7436e2f4e1
commit 8c2cf67783
19 changed files with 152 additions and 356 deletions
+5 -14
View File
@@ -24,7 +24,7 @@ use ruma::{
power_levels::RoomPowerLevelsEventContent,
},
};
use service::{mailer::messages, uiaa::Identity};
use service::{mailer::messages, uiaa::Identity, users::HashedPassword};
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
use crate::Ruma;
@@ -150,8 +150,7 @@ pub(crate) async fn change_password_route(
services
.users
.set_password(&sender_user, Some(&body.new_password))
.await?;
.set_password(&sender_user, Some(HashedPassword::new(&body.new_password)?));
if body.logout_devices {
// Logout all devices except the current one
@@ -239,19 +238,11 @@ pub(crate) async fn request_password_change_token_via_email_route(
///
/// Note: Also works for Application Services
pub(crate) async fn whoami_route(
State(services): State<crate::State>,
State(_): State<crate::State>,
body: Ruma<whoami::v3::Request>,
) -> Result<whoami::v3::Response> {
let is_guest = services
.users
.is_deactivated(body.sender_user())
.await
.map_err(|_| {
err!(Request(Forbidden("Application service has not registered this user.")))
})? && body.appservice_info.is_none();
Ok(assign!(whoami::v3::Response::new(body.sender_user().to_owned(), is_guest), {
device_id: body.sender_device.clone(),
Ok(assign!(whoami::v3::Response::new(body.sender_user().to_owned(), false), {
device_id: body.sender_device,
}))
}
+43 -139
View File
@@ -10,7 +10,6 @@ use conduwuit::{
use conduwuit_service::Services;
use futures::{FutureExt, StreamExt};
use lettre::{Address, message::Mailbox};
use register::RegistrationKind;
use ruma::{
OwnedUserId, UserId,
api::client::{
@@ -28,7 +27,7 @@ use ruma::{
push,
};
use serde_json::value::RawValue;
use service::mailer::messages;
use service::{mailer::messages, users::HashedPassword};
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
use crate::Ruma;
@@ -42,16 +41,6 @@ const RANDOM_USER_ID_LENGTH: usize = 10;
/// You can use [`GET
/// /_matrix/client/v3/register/available`](fn.get_register_available_route.
/// 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
#[allow(clippy::doc_markdown)]
#[tracing::instrument(skip_all, fields(%client), name = "register", level = "info")]
pub(crate) async fn register_route(
@@ -59,7 +48,6 @@ pub(crate) async fn register_route(
ClientIp(client): ClientIp,
body: Ruma<register::v3::Request>,
) -> Result<register::v3::Response> {
let is_guest = body.kind == RegistrationKind::Guest;
let emergency_mode_enabled = services.config.emergency_password.is_some();
// Allow registration if it's enabled in the config file or if this is the first
@@ -68,69 +56,19 @@ pub(crate) async fn register_route(
services.config.allow_registration || services.firstrun.is_first_run();
if !allow_registration && body.appservice_info.is_none() {
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
| (Some(username), Some(device_display_name)) => {
info!(
%is_guest,
user = %username,
device_name = %device_display_name,
"Rejecting registration attempt as registration is disabled"
);
},
| (Some(username), _) => {
info!(
%is_guest,
user = %username,
"Rejecting registration attempt as registration is disabled"
);
},
| (_, Some(device_display_name)) => {
info!(
%is_guest,
device_name = %device_display_name,
"Rejecting registration attempt as registration is disabled"
);
},
| (None, _) => {
info!(
%is_guest,
"Rejecting registration attempt as registration is disabled"
);
},
}
return Err!(Request(Forbidden(
"This server is not accepting registrations at this time."
)));
}
if is_guest && !services.config.allow_guest_registration {
info!(
"Guest registration disabled, rejecting guest registration attempt, initial device \
name: \"{}\"",
body.initial_device_display_name.as_deref().unwrap_or("")
?body.username,
?body.initial_device_display_name,
"Rejecting registration attempt as registration is disabled"
);
return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
}
// forbid guests from registering if there is not a real admin user yet. give
// generic user error.
if is_guest && services.firstrun.is_first_run() {
warn!(
"Guest account attempted to register before a real admin user has been registered, \
rejecting registration. Guest's initial device name: \"{}\"",
body.initial_device_display_name.as_deref().unwrap_or("")
);
return Err!(Request(Forbidden(
"This server is not accepting registrations at this time."
)));
}
// Appeservices and guests get to skip auth
let skip_auth = body.appservice_info.is_some() || is_guest;
let identity = if skip_auth {
// Appservices and guests have no identity
let identity = if body.appservice_info.is_some() {
// Appservices can skip auth
None
} else {
// Perform UIAA to determine the user's identity
@@ -157,13 +95,9 @@ pub(crate) async fn register_route(
}
});
let user_id = determine_registration_user_id(
&services,
supplied_username,
is_guest,
emergency_mode_enabled,
)
.await?;
let user_id =
determine_registration_user_id(&services, supplied_username, emergency_mode_enabled)
.await?;
if body.body.login_type == Some(LoginType::ApplicationService) {
// For appservice logins, make sure that the user ID is in the appservice's
@@ -187,7 +121,13 @@ pub(crate) async fn register_route(
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
}
let password = if is_guest { None } else { body.password.as_deref() };
let password = if body.appservice_info.is_some() {
None
} else if let Some(password) = body.password.as_deref() {
Some(HashedPassword::new(password)?)
} else {
return Err!(Request(InvalidParam("A password must be provided")));
};
// Create user
services.users.create(&user_id, password).await?;
@@ -222,7 +162,9 @@ pub(crate) async fn register_route(
// Generate new device id if the user didn't specify one
let (token, device) = if !body.inhibit_login {
let device_id = if is_guest { None } else { body.device_id.clone() }
let device_id = body
.device_id
.clone()
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
// Generate new token for the device
@@ -263,8 +205,7 @@ pub(crate) async fn register_route(
let device_display_name = body.initial_device_display_name.as_deref().unwrap_or("");
// log in conduit admin channel if a non-guest user registered
if body.appservice_info.is_none() && !is_guest {
if body.appservice_info.is_none() {
if !device_display_name.is_empty() {
let notice = format!(
"New user \"{user_id}\" registered on this server from IP {client} and device \
@@ -285,65 +226,32 @@ pub(crate) async fn register_route(
}
}
// log in conduit admin channel if a guest registered
if body.appservice_info.is_none() && is_guest && services.config.log_guest_registrations {
debug_info!("New guest user \"{user_id}\" registered on this server.");
// 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 !device_display_name.is_empty() {
if services.server.config.admin_room_notices {
services
.admin
.notice(&format!(
"Guest user \"{user_id}\" with device display name \
\"{device_display_name}\" registered on this server from IP {client}"
))
.await;
}
} else {
#[allow(clippy::collapsible_else_if)]
if services.server.config.admin_room_notices {
services
.admin
.notice(&format!(
"Guest user \"{user_id}\" with no device display name registered on \
this server from IP {client}",
))
.await;
}
}
}
if !is_guest {
// 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
// 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 {
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 {
services
.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();
}
.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();
}
}
if body.appservice_info.is_none()
&& !services.server.config.auto_join_rooms.is_empty()
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
{
if body.appservice_info.is_none() && !services.server.config.auto_join_rooms.is_empty() {
for room in &services.server.config.auto_join_rooms {
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
error!(
@@ -372,7 +280,6 @@ pub(crate) async fn register_route(
&room_id,
Some("Automatically joining this room upon registration".to_owned()),
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
&body.appservice_info,
)
.boxed()
.await
@@ -511,12 +418,9 @@ async fn create_registration_uiaa_session(
async fn determine_registration_user_id(
services: &Services,
supplied_username: Option<String>,
is_guest: bool,
emergency_mode_enabled: bool,
) -> Result<OwnedUserId> {
if let Some(supplied_username) = supplied_username
&& !is_guest
{
if let Some(supplied_username) = supplied_username {
// The user gets to pick their username. Do some validation to make sure it's
// acceptable.
@@ -569,7 +473,7 @@ async fn determine_registration_user_id(
Ok(user_id)
} else {
// The user is a guest or didn't specify a username. Generate a username for
// The user didn't specify a username. Generate a username for
// them.
loop {
-10
View File
@@ -122,16 +122,6 @@ pub(crate) async fn set_room_visibility_route(
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
if services
.users
.is_deactivated(sender_user)
.await
.unwrap_or(false)
&& body.appservice_info.is_none()
{
return Err!(Request(Forbidden("Guests cannot publish to room directories")));
}
if !user_can_publish_room(&services, sender_user, &body.room_id).await? {
return Err!(Request(Forbidden("User is not allowed to publish this room")));
}
+7 -34
View File
@@ -39,7 +39,6 @@ use ruma::{
};
use service::{
Services,
appservice::RegistrationInfo,
rooms::{
state::RoomMutexGuard,
state_compressor::{CompressedState, HashSetCompressStateEvent},
@@ -112,16 +111,9 @@ pub(crate) async fn join_room_by_id_route(
shuffle(&mut servers);
let servers = deprioritize(servers, &services.config.deprioritize_joins_through_servers);
join_room_by_id_helper(
&services,
sender_user,
&body.room_id,
body.reason.clone(),
&servers,
&body.appservice_info,
)
.boxed()
.await
join_room_by_id_helper(&services, sender_user, &body.room_id, body.reason.clone(), &servers)
.boxed()
.await
}
/// # `POST /_matrix/client/r0/join/{roomIdOrAlias}`
@@ -140,7 +132,6 @@ pub(crate) async fn join_room_by_id_or_alias_route(
body: Ruma<join_room_by_id_or_alias::v3::Request>,
) -> Result<join_room_by_id_or_alias::v3::Response> {
let sender_user = body.sender_user();
let appservice_info = &body.appservice_info;
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
@@ -235,16 +226,10 @@ pub(crate) async fn join_room_by_id_or_alias_route(
};
let servers = deprioritize(servers, &services.config.deprioritize_joins_through_servers);
let join_room_response = join_room_by_id_helper(
&services,
sender_user,
&room_id,
body.reason.clone(),
&servers,
appservice_info,
)
.boxed()
.await?;
let join_room_response =
join_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers)
.boxed()
.await?;
Ok(join_room_by_id_or_alias::v3::Response::new(join_room_response.room_id))
}
@@ -255,21 +240,9 @@ pub async fn join_room_by_id_helper(
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
appservice_info: &Option<RegistrationInfo>,
) -> Result<join_room_by_id::v3::Response> {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let user_is_guest = services
.users
.is_deactivated(sender_user)
.await
.unwrap_or(false)
&& appservice_info.is_none();
if user_is_guest && !services.rooms.state_accessor.guest_can_join(room_id).await {
return Err!(Request(Forbidden("Guests are not allowed to join this room")));
}
if services
.rooms
.state_cache
+2 -9
View File
@@ -238,15 +238,8 @@ async fn knock_room_by_id_helper(
// join_room_by_id_helper We need to release the lock here and let
// join_room_by_id_helper acquire it again
drop(state_lock);
match join_room_by_id_helper(
services,
sender_user,
room_id,
reason.clone(),
servers,
&None,
)
.await
match join_room_by_id_helper(services, sender_user, room_id, reason.clone(), servers)
.await
{
| Ok(_) => return Ok(knock_room::v3::Response::new(room_id.to_owned())),
| Err(e) => {
+3 -43
View File
@@ -4,10 +4,9 @@ use axum::extract::State;
use axum_client_ip::ClientIp;
use conduwuit::{
Err, Result, debug, err, info,
utils::{self, ReadyExt, hash, stream::BroadbandExt},
utils::{self, ReadyExt, stream::BroadbandExt},
warn,
};
use conduwuit_core::debug_error;
use conduwuit_service::Services;
use futures::StreamExt;
use lettre::Address;
@@ -54,37 +53,6 @@ pub(crate) async fn get_login_types_route(
]))
}
/// Authenticates the given user by its ID and its password.
///
/// Returns the user ID if successful, and an error otherwise.
#[tracing::instrument(skip_all, fields(%user_id), name = "password", level = "debug")]
pub(crate) async fn password_login(
services: &Services,
user_id: &UserId,
lowercased_user_id: &UserId,
password: &str,
) -> Result<OwnedUserId> {
let (hash, user_id) = match services.users.password_hash(user_id).await {
| Ok(hash) => (hash, user_id),
| Err(_) => services
.users
.password_hash(lowercased_user_id)
.await
.map(|hash| (hash, lowercased_user_id))
.map_err(|_| err!(Request(Forbidden("Invalid identifier or password."))))?,
};
if hash.is_empty() {
return Err!(Request(UserDeactivated("The user has been deactivated")));
}
hash::verify_password(password, &hash)
.inspect_err(|e| debug_error!("{e}"))
.map_err(|_| err!(Request(Forbidden("Invalid identifier or password."))))?;
Ok(user_id.to_owned())
}
pub(crate) async fn handle_login(
services: &Services,
identifier: Option<&UserIdentifier>,
@@ -115,15 +83,7 @@ pub(crate) async fn handle_login(
UserId::parse_with_server_name(user_id_or_localpart, &services.config.server_name)
.map_err(|_| err!(Request(InvalidUsername("User ID is malformed"))))?;
let lowercased_user_id = UserId::parse_with_server_name(
user_id.localpart().to_lowercase(),
&services.config.server_name,
)
.unwrap();
if !services.globals.user_is_local(&user_id)
|| !services.globals.user_is_local(&lowercased_user_id)
{
if !services.globals.user_is_local(&user_id) {
return Err!(Request(InvalidParam("User ID does not belong to this homeserver")));
}
@@ -136,7 +96,7 @@ pub(crate) async fn handle_login(
return Err!(Request(Forbidden("This account is not permitted to log in.")));
}
password_login(services, &user_id, &lowercased_user_id, password).await
services.users.check_password(&user_id, password).await
}
/// # `POST /_matrix/client/v3/login`
+1 -1
View File
@@ -80,7 +80,7 @@ pub(crate) async fn conduwuit_server_version() -> Result<impl IntoResponse> {
///
/// conduwuit-specific API to return the amount of users registered on this
/// homeserver. Endpoint is disabled if federation is disabled for privacy. This
/// only includes active users (not deactivated, no guests, etc)
/// only includes active users (not deactivated, etc)
pub(crate) async fn conduwuit_local_user_count(
State(services): State<crate::State>,
) -> Result<impl IntoResponse> {