feat(admin): Create user route

This commit is contained in:
timedout committed 2026-10-10 23:11:37 +01:00
1 parent 1deaf99409
commit 02f4f9d88d
7 files changed
+133 -3

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
use axum::extract::State;
use conduwuit::{Err, error};
use ruma::{api::client::profile::PropagateTo, profile::ProfileFieldValue};
use ruminuwuity::admin::continuwuity::users::create::v1::{Request, Response};
use service::users::{HashedPassword, ProfileFieldChange};
use crate::router::Ruma;
/// # `POST /_continuwuity/admin/v1/users`
///
/// Creates a new user on the homeserver.
pub(crate) async fn create_user(
State(services): State<crate::State>,
body: Ruma<Request>,
) -> conduwuit::Result<Response> {
let sender_user = body.identity.expect_sender_user()?;
if !services.users.is_admin(sender_user).await {
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
}
if !services.globals.user_is_local(&body.user_id) {
return Err!(Request(InvalidParam("User ID does not belong to this server.")));
}
if services.users.status(&body.user_id).await.is_found() {
return Err!(Request(UserInUse("An account with this user ID already exists.")));
}
services
.users
.create_local_account(
&body.user_id,
body.password
.as_ref()
.and_then(|raw| HashedPassword::new(raw).ok()),
None,
None,
None,
)
.await?;
if body.locked {
services
.users
.lock_account(&body.user_id, sender_user)
.await;
}
if body.suspended {
services
.users
.suspend_account(&body.user_id, sender_user)
.await;
}
for (key, value) in body.profile.clone().unwrap_or_default() {
services
.users
.set_profile_field(
&body.user_id,
ProfileFieldChange::Set(ProfileFieldValue::new(&key, value)?),
PropagateTo::All,
)
.await
.inspect_err(|e| error!(error=?e, %key, "Failed to set profile field"))
.ok();
}
if body.admin {
services.admin.make_user_admin(&body.user_id).await?;
}
Ok(Response::default())
}
+1 -1
View File
@@ -11,7 +11,7 @@ use service::users::AccountStatus;
use crate::router::Ruma;
/// # `PUT /_continuwuity/admin/v1/users`
/// # `GET /_continuwuity/admin/v1/users`
///
/// Lists users on this homeserver
pub(crate) async fn list_users(
+1
View File
@@ -1 +1,2 @@
pub mod create;
pub mod list;
+2 -1
View File
@@ -198,7 +198,8 @@ pub fn build(router: Router<State>, state: State) -> Router<State> {
.route("/_continuwuity/server_version", get(client::continuwuity_server_version))
.ruma_route(&admin::rooms::ban::ban_room)
.ruma_route(&admin::rooms::list::list_rooms)
.ruma_route(&admin::users::list::list_users);
.ruma_route(&admin::users::list::list_users)
.ruma_route(&admin::users::create::create_user);
if config.allow_federation {
router = router
+1 -1
View File
@@ -11,7 +11,7 @@ version.workspace = true
path = "mod.rs"
[features]
default = ["client", "server"]
default = ["client", "server", "unstable-msc4484"]
client = []
server = []
@@ -0,0 +1,58 @@
pub mod v1 {
use ruma::{
OwnedUserId,
api::{OAuthClientScope, auth_scheme::AccessToken, request, response},
metadata,
profile::UserProfile,
};
metadata! {
method: POST,
rate_limited: false,
authentication: AccessToken,
history: {
1.0 => "/_continuwuity/admin/v1/users",
},
required_client_scopes: [OAuthClientScope::ServerAdministration]
}
#[request]
pub struct Request {
/// The full user ID to create. Must belong to the server.
pub user_id: OwnedUserId,
/// The password to set for the new user.
///
/// If `None`, the user will not be able to log in.
#[serde(default, skip_serializing_if = "ruma::serde::is_default")]
pub password: Option<String>,
/// The profile fields to set for the user. This can be used to set the
/// new user's display name and profile picture.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile: Option<UserProfile>,
#[serde(default, skip_serializing_if = "ruma::serde::is_default")]
pub admin: bool,
#[serde(default, skip_serializing_if = "ruma::serde::is_default")]
pub suspended: bool,
#[serde(default, skip_serializing_if = "ruma::serde::is_default")]
pub locked: bool,
}
#[derive(Default)]
#[response]
pub struct Response;
impl Request {
#[must_use]
pub fn new(user_id: OwnedUserId) -> Self {
Self {
user_id,
password: None,
profile: None,
admin: false,
suspended: false,
locked: false,
}
}
}
}
@@ -1 +1,2 @@
pub mod create;
pub mod list;