diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index cf1548d6..6588e5bd 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -320,6 +320,7 @@ where PasswordManager: FromRef, MetadataCache: FromRef, SiteConfig: FromRef, + BoxHomeserverConnection: FromRef, BoxClock: FromRequestParts, BoxRng: FromRequestParts, Policy: FromRequestParts, diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index 0902fba1..b03c02f5 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -26,6 +26,7 @@ use mas_axum_utils::{ }; use mas_data_model::{User, UserAgent}; use mas_jose::jwt::Jwt; +use mas_matrix::BoxHomeserverConnection; use mas_policy::Policy; use mas_router::UrlBuilder; use mas_storage::{ @@ -94,6 +95,9 @@ pub(crate) enum RouteError { #[error("Invalid form action")] InvalidFormAction, + #[error("Homeserver connection error")] + HomeserverConnection(#[source] anyhow::Error), + #[error(transparent)] Internal(Box), } @@ -196,6 +200,7 @@ pub(crate) async fn get( PreferredLanguage(locale): PreferredLanguage, State(templates): State, State(url_builder): State, + State(homeserver): State, cookie_jar: CookieJar, user_agent: Option>, Path(link_id): Path, @@ -406,10 +411,17 @@ pub(crate) async fn get( // form, but this lead to poor UX. This is why we do // it ahead of time here. let maybe_existing_user = repo.user().find_by_username(&localpart).await?; - if let Some(existing_user) = maybe_existing_user { - // The mapper returned a username which already exists, but isn't linked - // to this upstream user. - warn!(username = %localpart, user_id = %existing_user.id, "Localpart template returned an existing username"); + let is_available = homeserver + .is_localpart_available(&localpart) + .await + .map_err(RouteError::HomeserverConnection)?; + + if maybe_existing_user.is_some() || !is_available { + if let Some(existing_user) = maybe_existing_user { + // The mapper returned a username which already exists, but isn't + // linked to this upstream user. + warn!(username = %localpart, user_id = %existing_user.id, "Localpart template returned an existing username"); + } // TODO: translate let ctx = ErrorContext::new() @@ -476,6 +488,7 @@ pub(crate) async fn post( mut policy: Policy, PreferredLanguage(locale): PreferredLanguage, State(templates): State, + State(homeserver): State, State(url_builder): State, State(site_config): State, Path(link_id): Path, @@ -682,7 +695,14 @@ pub(crate) async fn post( // Check if there is an existing user let existing_user = repo.user().find_by_username(&username).await?; - if let Some(_existing_user) = existing_user { + + // Ask the homeserver to make sure the username is valid + let is_available = homeserver + .is_localpart_available(&username) + .await + .map_err(RouteError::HomeserverConnection)?; + + if existing_user.is_some() || !is_available { // If there is an existing user, we can't create a new one // with the same username, show an error diff --git a/crates/handlers/src/views/register.rs b/crates/handlers/src/views/register.rs index 3edad71c..1cd66c41 100644 --- a/crates/handlers/src/views/register.rs +++ b/crates/handlers/src/views/register.rs @@ -28,6 +28,7 @@ use mas_axum_utils::{ }; use mas_data_model::UserAgent; use mas_i18n::DataLocale; +use mas_matrix::BoxHomeserverConnection; use mas_policy::Policy; use mas_router::UrlBuilder; use mas_storage::{ @@ -111,6 +112,7 @@ pub(crate) async fn post( State(templates): State, State(url_builder): State, State(site_config): State, + State(homeserver): State, mut policy: Policy, mut repo: BoxRepository, activity_tracker: BoundActivityTracker, @@ -135,6 +137,16 @@ pub(crate) async fn post( if form.username.is_empty() { state.add_error_on_field(RegisterFormField::Username, FieldError::Required); } else if repo.user().exists(&form.username).await? { + // The user already exists in the database + state.add_error_on_field(RegisterFormField::Username, FieldError::Exists); + } else if !homeserver.is_localpart_available(&form.username).await? { + // The user already exists on the homeserver + // XXX: we may want to return different errors like "this username is reserved" + tracing::warn!( + username = &form.username, + "User tried to register with a reserved username" + ); + state.add_error_on_field(RegisterFormField::Username, FieldError::Exists); }