From 684964fac0db84b4854d7bec5126e211a5eaaa22 Mon Sep 17 00:00:00 2001 From: Ginger Date: Wed, 15 Jul 2026 19:59:05 -0400 Subject: [PATCH] fix: Only return create prompt if registration is enabled --- changelog.d/1994.bugfix.md | 1 + src/api/client/oauth/server_metadata.rs | 13 +++- src/service/uiaa/mod.rs | 96 +++++++++++++++++++++++-- src/web/pages/account/login.rs | 15 ++-- src/web/pages/account/register.rs | 77 +++----------------- 5 files changed, 118 insertions(+), 84 deletions(-) create mode 100644 changelog.d/1994.bugfix.md diff --git a/changelog.d/1994.bugfix.md b/changelog.d/1994.bugfix.md new file mode 100644 index 000000000..fb2b735ac --- /dev/null +++ b/changelog.d/1994.bugfix.md @@ -0,0 +1 @@ +Fixed `create` being returned as a supported prompt value regardless of if registration is enabled or not. Contributed by @ginger diff --git a/src/api/client/oauth/server_metadata.rs b/src/api/client/oauth/server_metadata.rs index eaf4d2a7a..e3714d913 100644 --- a/src/api/client/oauth/server_metadata.rs +++ b/src/api/client/oauth/server_metadata.rs @@ -37,6 +37,17 @@ pub(crate) async fn authorization_server_metadata(services: &Services) -> Value .join(super::BASE_PATH) .unwrap(); + let prompt_values_supported = if services + .uiaa + .registration_flow_status() + .await + .any_available() + { + json!(["create"]) + } else { + json!([]) + }; + json!({ "account_management_uri": endpoint_base.join(ACCOUNT_MANAGEMENT_PATH).unwrap(), "account_management_actions_supported": [ @@ -52,7 +63,7 @@ pub(crate) async fn authorization_server_metadata(services: &Services) -> Value "grant_types_supported": ["authorization_code", "refresh_token"], "issuer": services.config.get_client_domain(), "jwks_uri": endpoint_base.join(JWKS_URI_PATH).unwrap(), - "prompt_values_supported": ["create"], + "prompt_values_supported": prompt_values_supported, "registration_endpoint": endpoint_base.join(CLIENT_REGISTER_PATH).unwrap(), "response_modes_supported": ["query", "fragment"], "response_types_supported": ["code"], diff --git a/src/service/uiaa/mod.rs b/src/service/uiaa/mod.rs index 57665f5d6..f456e7aef 100644 --- a/src/service/uiaa/mod.rs +++ b/src/service/uiaa/mod.rs @@ -5,6 +5,7 @@ }; use conduwuit::{Err, Error, Result, error, utils}; +use futures::StreamExt; use lettre::Address; use ruma::{ DeviceId, UserId, @@ -25,7 +26,7 @@ use tokio::sync::Mutex; use crate::{ - Dep, config, globals, + Dep, config, firstrun, globals, oauth::{self, OAuthTicket}, registration_tokens, threepid, users, }; @@ -36,25 +37,54 @@ pub struct Service { } struct Services { - globals: Dep, - users: Dep, config: Dep, + firstrun: Dep, + globals: Dep, + oauth: Dep, registration_tokens: Dep, threepid: Dep, - oauth: Dep, + users: Dep, +} + +#[derive(Debug)] +pub enum TrustedFlowStatus { + Unavailable, + Available, +} + +#[derive(Debug)] +pub enum UntrustedFlowStatus { + Unavailable, + Available { + require_email: bool, + }, +} + +pub struct FlowStatus { + pub trusted: TrustedFlowStatus, + pub untrusted: UntrustedFlowStatus, +} + +impl FlowStatus { + #[must_use] + pub fn any_available(&self) -> bool { + matches!(self.trusted, TrustedFlowStatus::Available) + || matches!(self.untrusted, UntrustedFlowStatus::Available { .. }) + } } impl crate::Service for Service { fn build(args: crate::Args<'_>) -> Result> { Ok(Arc::new(Self { services: Services { - globals: args.depend::("globals"), - users: args.depend::("users"), config: args.depend::("config"), + firstrun: args.depend::("firstrun"), + globals: args.depend::("globals"), + oauth: args.depend::("oauth"), registration_tokens: args .depend::("registration_tokens"), threepid: args.depend::("threepid"), - oauth: args.depend::("oauth"), + users: args.depend::("users"), }, uiaa_sessions: Mutex::new(HashMap::new()), })) @@ -595,4 +625,56 @@ async fn check_stage( Ok((completed_auth_type, session_metadata)) } + + pub async fn registration_flow_status(&self) -> FlowStatus { + // Allow registration if it's enabled in the config file or if this is the first + // run (so the first user account can be created) + let allow_registration = + self.services.config.allow_registration || self.services.firstrun.is_first_run(); + + // Trusted flow is only available if any registration tokens exist + let trusted = { + if !allow_registration { + TrustedFlowStatus::Unavailable + } else if self + .services + .registration_tokens + .iterate_tokens() + .next() + .await + .is_some() + { + TrustedFlowStatus::Available + } else { + TrustedFlowStatus::Unavailable + } + }; + + // Untrusted flow is available if email is required for registration, + // or reCAPTCHA is configured, or open registration is enabled + let untrusted = { + let require_email = self + .services + .config + .smtp + .as_ref() + .is_some_and(|smtp| smtp.require_email_for_registration); + + if !allow_registration || self.services.firstrun.is_first_run() { + UntrustedFlowStatus::Unavailable + } else if self.services.config.recaptcha_private_site_key.is_some() || require_email { + UntrustedFlowStatus::Available { require_email } + } else if self + .services + .config + .yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse + { + UntrustedFlowStatus::Available { require_email: false } + } else { + UntrustedFlowStatus::Unavailable + } + }; + + FlowStatus { trusted, untrusted } + } } diff --git a/src/web/pages/account/login.rs b/src/web/pages/account/login.rs index b92e775bb..50af974ca 100644 --- a/src/web/pages/account/login.rs +++ b/src/web/pages/account/login.rs @@ -21,7 +21,6 @@ extract::{Expect, PostForm}, pages::{ GET_POST, Result, TemplateContext, - account::register::{TrustedFlowStatus, UntrustedFlowStatus, registration_flow_status}, components::UserCard, oidc::{OIDC_SESSION_ID_KEY, OidcSession, OidcSessionState}, }, @@ -101,13 +100,13 @@ async fn route_login( LoginType::Oidc { redirect_url } } else { - let (trusted_flow_status, untrusted_flow_status) = - registration_flow_status(&services).await; - - let registration_available = matches!(trusted_flow_status, TrustedFlowStatus::Available) - || matches!(untrusted_flow_status, UntrustedFlowStatus::Available { .. }); - - LoginType::Interactive { registration_available } + LoginType::Interactive { + registration_available: services + .uiaa + .registration_flow_status() + .await + .any_available(), + } }; let body = match &user_id { diff --git a/src/web/pages/account/register.rs b/src/web/pages/account/register.rs index e47a84b3d..80cf8ac5a 100644 --- a/src/web/pages/account/register.rs +++ b/src/web/pages/account/register.rs @@ -8,9 +8,12 @@ }; use conduwuit_core::{config::TermsDocument, warn}; use conduwuit_service::{ - mailer::messages, registration_tokens::ValidToken, users::HashedPassword, + mailer::messages, + registration_tokens::ValidToken, + uiaa::{FlowStatus, TrustedFlowStatus, UntrustedFlowStatus}, + users::HashedPassword, }; -use futures::{FutureExt, StreamExt}; +use futures::FutureExt; use lettre::{Address, message::Mailbox}; use ruma::{ClientSecret, OwnedClientSecret, OwnedServerName, OwnedSessionId, OwnedUserId}; use serde::{Deserialize, Serialize, de::IgnoredAny}; @@ -61,20 +64,6 @@ enum RegisterBody { }, } -#[derive(Debug)] -pub(super) enum TrustedFlowStatus { - Unavailable, - Available, -} - -#[derive(Debug)] -pub(super) enum UntrustedFlowStatus { - Unavailable, - Available { - require_email: bool, - }, -} - #[derive(Default, Deserialize, Serialize)] pub(crate) struct RegisterQuery { pub username: Option, @@ -169,7 +158,10 @@ async fn route_register( ValidationErrors::new() }; - let (trusted_flow_status, untrusted_flow_status) = registration_flow_status(&services).await; + let FlowStatus { + trusted: trusted_flow_status, + untrusted: untrusted_flow_status, + } = services.uiaa.registration_flow_status().await; if matches!(trusted_flow_status, TrustedFlowStatus::Unavailable) && matches!(untrusted_flow_status, UntrustedFlowStatus::Unavailable) @@ -540,54 +532,3 @@ async fn complete_registration( Ok(Redirect::to(&next.unwrap_or_default().target_path())) } - -pub(super) async fn registration_flow_status( - services: &crate::State, -) -> (TrustedFlowStatus, UntrustedFlowStatus) { - // Allow registration if it's enabled in the config file or if this is the first - // run (so the first user account can be created) - let allow_registration = - services.config.allow_registration || services.firstrun.is_first_run(); - - // Trusted flow is only available if any registration tokens exist - let trusted_flow_status = { - if !allow_registration { - TrustedFlowStatus::Unavailable - } else if services - .registration_tokens - .iterate_tokens() - .next() - .await - .is_some() - { - TrustedFlowStatus::Available - } else { - TrustedFlowStatus::Unavailable - } - }; - - // Untrusted flow is available if email is required for registration, - // or reCAPTCHA is configured, or open registration is enabled - let untrusted_flow_status = { - let require_email = services - .config - .smtp - .as_ref() - .is_some_and(|smtp| smtp.require_email_for_registration); - - if !allow_registration || services.firstrun.is_first_run() { - UntrustedFlowStatus::Unavailable - } else if services.config.recaptcha_private_site_key.is_some() || require_email { - UntrustedFlowStatus::Available { require_email } - } else if services - .config - .yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse - { - UntrustedFlowStatus::Available { require_email: false } - } else { - UntrustedFlowStatus::Unavailable - } - }; - - (trusted_flow_status, untrusted_flow_status) -}