diff --git a/crates/graphql/src/mutations/user.rs b/crates/graphql/src/mutations/user.rs index b469cad4..c38037c8 100644 --- a/crates/graphql/src/mutations/user.rs +++ b/crates/graphql/src/mutations/user.rs @@ -18,7 +18,7 @@ use mas_storage::{ job::{DeactivateUserJob, JobRepositoryExt, ProvisionUserJob}, user::UserRepository, }; -use tracing::info; +use tracing::{info, warn}; use crate::{ model::{NodeType, User}, @@ -36,6 +36,13 @@ pub struct UserMutations { struct AddUserInput { /// The username of the user to add. username: String, + + /// Skip checking with the homeserver whether the username is valid. + /// + /// Use this with caution! The main reason to use this, is when a user used + /// by an application service needs to exist in MAS to craft special + /// tokens (like with admin access) for them + skip_homeserver_check: Option, } /// The status of the `addUser` mutation. @@ -47,6 +54,9 @@ enum AddUserStatus { /// The user already exists. Exists, + /// The username is reserved. + Reserved, + /// The username is invalid. Invalid, } @@ -56,6 +66,7 @@ enum AddUserStatus { enum AddUserPayload { Added(mas_data_model::User), Exists(mas_data_model::User), + Reserved, Invalid, } @@ -66,6 +77,7 @@ impl AddUserPayload { match self { Self::Added(_) => AddUserStatus::Added, Self::Exists(_) => AddUserStatus::Exists, + Self::Reserved => AddUserStatus::Reserved, Self::Invalid => AddUserStatus::Invalid, } } @@ -74,7 +86,7 @@ impl AddUserPayload { async fn user(&self) -> Option { match self { Self::Added(user) | Self::Exists(user) => Some(User(user.clone())), - Self::Invalid => None, + Self::Invalid | Self::Reserved => None, } } } @@ -245,6 +257,21 @@ impl UserMutations { return Ok(AddUserPayload::Invalid); } + // Ask the homeserver if the username is available + let homeserver_available = state + .homeserver_connection() + .is_localpart_available(&input.username) + .await?; + + if !homeserver_available { + if !input.skip_homeserver_check.unwrap_or(false) { + return Ok(AddUserPayload::Reserved); + } + + // If we skipped the check, we still want to shout about it + warn!("Skipped homeserver check for username {}", input.username); + } + let user = repo.user().add(&mut rng, &clock, input.username).await?; repo.job() diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 54de662b..46a8de6e 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -72,6 +72,14 @@ input AddUserInput { The username of the user to add. """ username: String! + """ + Skip checking with the homeserver whether the username is valid. + + Use this with caution! The main reason to use this, is when a user used + by an application service needs to exist in MAS to craft special + tokens (like with admin access) for them + """ + skipHomeserverCheck: Boolean } """ @@ -101,6 +109,10 @@ enum AddUserStatus { """ EXISTS """ + The username is reserved. + """ + RESERVED + """ The username is invalid. """ INVALID diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index 8fde3d9c..f9a1421a 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -76,6 +76,14 @@ export enum AddEmailStatus { /** The input for the `addUser` mutation. */ export type AddUserInput = { + /** + * Skip checking with the homeserver whether the username is valid. + * + * Use this with caution! The main reason to use this, is when a user used + * by an application service needs to exist in MAS to craft special + * tokens (like with admin access) for them + */ + skipHomeserverCheck?: InputMaybe; /** The username of the user to add. */ username: Scalars["String"]["input"]; }; @@ -97,6 +105,8 @@ export enum AddUserStatus { Exists = "EXISTS", /** The username is invalid. */ Invalid = "INVALID", + /** The username is reserved. */ + Reserved = "RESERVED", } /** The input for the `allowUserCrossSigningReset` mutation. */