diff --git a/src/api/client/account/register.rs b/src/api/client/account/register.rs index 1aafe6280..5f8ffaeea 100644 --- a/src/api/client/account/register.rs +++ b/src/api/client/account/register.rs @@ -99,7 +99,7 @@ pub(crate) async fn register_route( .users .create_local_account(&user_id, password, identity.email) .await; - + services.users.join_auto_join_rooms(&user_id).await; user_id }; diff --git a/src/api/client/admin/site/users/create.rs b/src/api/client/admin/site/users/create.rs new file mode 100644 index 000000000..422821235 --- /dev/null +++ b/src/api/client/admin/site/users/create.rs @@ -0,0 +1,119 @@ +use axum::extract::State; +use conduwuit::{ + Err, err, error, info, + utils::{IterStream, stream::BroadbandExt}, + warn, +}; +use futures::{FutureExt, StreamExt}; +use ruma::UserId; +use ruminuwuity::admin::continuwuity::users; +use service::users::HashedPassword; + +use crate::router::Ruma; + +/// # `POST /_continuwuity/admin/v1/users/create` +/// +/// Creates a new user. +pub(crate) async fn create_user_route( + State(services): State, + body: Ruma, +) -> conduwuit::Result { + let sender_user = body.sender_user(); + + if !services.users.is_admin(sender_user).await { + return Err!(Request(Forbidden("Only server administrators can use this endpoint"))); + } + let user_id = + &UserId::parse_with_server_name(&body.localpart, services.globals.server_name())?; + if services.users.is_active_local(user_id).await { + return Err!(Conflict("A user with this username already exists")); + } + + services + .users + .create_local_account( + user_id, + HashedPassword::new(&body.password)?, + body.email + .clone() + .map(lettre::Address::try_from) + .transpose() + .map_err(|e| err!(Request(BadJson("Invalid email address: {e}"))))?, + ) + .await; + if body.suspended { + services.users.suspend_account(user_id, sender_user).await; + } + if body.locked { + services.users.lock_account(user_id, sender_user).await; + } + if body.login_disabled { + services.users.disable_login(user_id); + } + if let Some(ref value) = body.display_name { + services.users.set_profile_key( + user_id, + "displayname", + Some(serde_json::to_value(value)?), + ); + } + if let Some(ref value) = body.avatar_url { + services + .users + .set_profile_key(user_id, "avatar_url", Some(serde_json::to_value(value)?)); + } + if body.admin { + services + .admin + .make_user_admin(user_id) + .await + .inspect_err(|e| error!("failed to make new user {user_id} an admin: {e}")) + .ok(); + } + if !body.skip_auto_join { + services.users.join_auto_join_rooms(user_id).await; + } + + body.auto_join_rooms + .clone() + .into_iter() + .stream() + .broad_filter_map(|room| async move { + services + .rooms + .alias + .resolve_with_servers(&room, None) + .await + .inspect_err(|e| { + warn!( + "Failed to resolve room alias to room ID when attempting to auto join \ + {room}: {e}" + ); + }) + .ok() + }) + .for_each_concurrent(None, |(room_id, servers)| async move { + match services + .rooms + .membership + .join_room( + user_id, + &room_id, + Some("Automatically joining this room upon registration".to_owned()), + servers.as_ref(), + ) + .boxed() + .await + { + | Err(e) => { + warn!("Failed to automatically join {user_id} to {room_id}: {e}"); + }, + | _ => { + info!("Automatically joined room {user_id} to {room_id}"); + }, + } + }) + .await; + + Ok(users::create::v1::Response::new(user_id.to_owned())) +} diff --git a/src/api/client/admin/site/users/mod.rs b/src/api/client/admin/site/users/mod.rs index 7e67b3889..a37668086 100644 --- a/src/api/client/admin/site/users/mod.rs +++ b/src/api/client/admin/site/users/mod.rs @@ -1,3 +1,5 @@ +mod create; mod list; -pub(crate) use list::list_users_route; +pub(crate) use create::*; +pub(crate) use list::*; diff --git a/src/api/router.rs b/src/api/router.rs index c95254543..a5787b55e 100644 --- a/src/api/router.rs +++ b/src/api/router.rs @@ -283,6 +283,7 @@ pub fn build(router: Router, state: State) -> Router { { router = router .ruma_route(&admin_api::users::list_users_route) + .ruma_route(&admin_api::users::create_user_route) .ruma_route(&admin_api::rooms::ban_room) .ruma_route(&admin_api::rooms::legacy_list_rooms_route) .ruma_route(&admin_api::rooms::list_rooms_route); diff --git a/src/ruminuwuity/admin/continuwuity/users/create.rs b/src/ruminuwuity/admin/continuwuity/users/create.rs index d02557cc6..1b8c61c6d 100644 --- a/src/ruminuwuity/admin/continuwuity/users/create.rs +++ b/src/ruminuwuity/admin/continuwuity/users/create.rs @@ -23,6 +23,10 @@ pub mod v1 { /// The user's desired password. Cannot be blank. pub password: String, + /// The user's email address, if any. + #[serde(default, skip_serializing_if = "ruma::serde::is_default")] + pub email: Option, + /// The display name to set upon creation. #[serde(default, skip_serializing_if = "ruma::serde::is_default")] pub display_name: Option, @@ -80,6 +84,7 @@ pub mod v1 { Self { localpart, password, + email: None, display_name: None, avatar_url: None, suspended: false, diff --git a/src/service/users/mod.rs b/src/service/users/mod.rs index c93b9e82a..78fb3e771 100644 --- a/src/service/users/mod.rs +++ b/src/service/users/mod.rs @@ -229,6 +229,9 @@ impl Service { } /// Create a new account for a local human or bot user. + /// + /// Does not automatically join the user to auto join rooms. Use + /// `join_auto_join_rooms` for that. pub async fn create_local_account( &self, user_id: &UserId, @@ -303,8 +306,11 @@ impl Service { .ok(); } } + info!("Created new user account for {user_id}"); + } - // Autojoin the user to the configured autojoin rooms + /// Autojoin the user to the configured autojoin rooms + pub async fn join_auto_join_rooms(&self, user_id: &UserId) { for room in &self.services.config.auto_join_rooms { let Ok(room_id) = self.services.alias.resolve(room).await else { error!( @@ -320,9 +326,7 @@ impl Service { .server_in_room(self.services.globals.server_name(), &room_id) .await { - warn!( - "Skipping room {room} to automatically join as we have never joined before." - ); + warn!("Skipping auto-room {room} as we have never joined before."); continue; } @@ -354,8 +358,6 @@ impl Service { } } } - - info!("Created new user account for {user_id}"); } pub async fn determine_registration_user_id( diff --git a/src/web/pages/account/register.rs b/src/web/pages/account/register.rs index fbd200392..227de5cef 100644 --- a/src/web/pages/account/register.rs +++ b/src/web/pages/account/register.rs @@ -519,6 +519,7 @@ async fn complete_registration( .registration_tokens .mark_token_as_used(registration_token); } + services.users.join_auto_join_rooms(&user_id).await; let user_session = UserSession { user_id, last_login: SystemTime::now() };