diff --git a/auth/server/src/api/login/local.rs b/auth/server/src/api/login/local.rs index 5747e85..992ae90 100644 --- a/auth/server/src/api/login/local.rs +++ b/auth/server/src/api/login/local.rs @@ -24,7 +24,7 @@ pub async fn sign_up_local_user( let no_users_exist = auth.no_users_exist().await?; - if auth.registration_disabled() && !no_users_exist { + if auth.local_registration_disabled() && !no_users_exist { return Err( anyhow!("User registration is disabled") .status_code(StatusCode::UNAUTHORIZED), diff --git a/auth/server/src/api/login/mod.rs b/auth/server/src/api/login/mod.rs index a01b5da..de2c5d7 100644 --- a/auth/server/src/api/login/mod.rs +++ b/auth/server/src/api/login/mod.rs @@ -115,11 +115,15 @@ pub fn get_login_options( .google_config() .map(|config| config.enabled()) .unwrap_or_default(), +<<<<<<< granular-registration-control + registration_disabled: auth.local_registration_disabled(), +======= registration_disabled: auth.registration_disabled(), oidc_auto_redirect: auth .oidc_config() .map(|config| config.enabled() && config.auto_redirect) .unwrap_or_default(), +>>>>>>> main } } @@ -138,11 +142,20 @@ mod tests { use crate::AuthImpl; use mogh_auth_client::config::OidcConfig; +<<<<<<< granular-registration-control + /// Minimal AuthImpl for testing +======= /// Minimal AuthImpl for testing get_login_options +>>>>>>> main struct TestAuth { local: bool, oidc: Option, registration_disabled: bool, +<<<<<<< granular-registration-control + local_registration_disabled: Option, + oidc_registration_disabled: Option, +======= +>>>>>>> main } impl TestAuth { @@ -151,6 +164,11 @@ mod tests { local: true, oidc: None, registration_disabled: false, +<<<<<<< granular-registration-control + local_registration_disabled: None, + oidc_registration_disabled: None, +======= +>>>>>>> main } } } @@ -172,6 +190,21 @@ mod tests { self.registration_disabled } +<<<<<<< granular-registration-control + fn local_registration_disabled(&self) -> bool { + self + .local_registration_disabled + .unwrap_or_else(|| self.registration_disabled()) + } + + fn oidc_registration_disabled(&self) -> bool { + self + .oidc_registration_disabled + .unwrap_or_else(|| self.registration_disabled()) + } + +======= +>>>>>>> main fn get_user( &self, _user_id: String, @@ -194,12 +227,96 @@ mod tests { }) } +<<<<<<< granular-registration-control + fn jwt_provider( + &self, + ) -> &crate::provider::jwt::JwtProvider { + panic!("not needed for these tests") +======= fn jwt_provider(&self) -> &crate::provider::jwt::JwtProvider { panic!("not needed for login options tests") +>>>>>>> main } } #[test] +<<<<<<< granular-registration-control + fn test_default_granular_methods_delegate_to_registration_disabled() + { + // When granular overrides are None, they should + // fall back to the global registration_disabled flag. + let auth = TestAuth { + registration_disabled: true, + ..TestAuth::default_test() + }; + assert!(auth.local_registration_disabled()); + assert!(auth.oidc_registration_disabled()); + assert!(auth.github_registration_disabled()); + assert!(auth.google_registration_disabled()); + } + + #[test] + fn test_global_disabled_local_override_enabled() { + // Global registration disabled, but local override allows it + let auth = TestAuth { + registration_disabled: true, + local_registration_disabled: Some(false), + ..TestAuth::default_test() + }; + assert!(!auth.local_registration_disabled()); + assert!(auth.oidc_registration_disabled()); + } + + #[test] + fn test_global_enabled_local_override_disabled() { + // Global registration enabled, but local override blocks it + let auth = TestAuth { + registration_disabled: false, + local_registration_disabled: Some(true), + ..TestAuth::default_test() + }; + assert!(auth.local_registration_disabled()); + assert!(!auth.oidc_registration_disabled()); + } + + #[test] + fn test_disable_local_allow_oidc() { + // The #1087 use case: disable local signup, allow OIDC + let auth = TestAuth { + registration_disabled: false, + local_registration_disabled: Some(true), + oidc_registration_disabled: Some(false), + ..TestAuth::default_test() + }; + assert!(auth.local_registration_disabled()); + assert!(!auth.oidc_registration_disabled()); + } + + #[test] + fn test_registration_disabled_reflects_local_in_login_options() { + // registration_disabled in the response controls the Sign Up button, + // which is local-only. It should reflect local_registration_disabled. + let auth = TestAuth { + registration_disabled: false, + local_registration_disabled: Some(true), + oidc_registration_disabled: Some(false), + ..TestAuth::default_test() + }; + let opts = get_login_options(&auth); + assert!(opts.registration_disabled); + } + + #[test] + fn test_registration_disabled_false_when_local_allowed() { + let auth = TestAuth { + registration_disabled: true, + local_registration_disabled: Some(false), + oidc_registration_disabled: Some(true), + ..TestAuth::default_test() + }; + let opts = get_login_options(&auth); + assert!(!opts.registration_disabled); +======= fn test_oidc_auto_redirect_defaults_false() { let auth = TestAuth::default_test(); let opts = get_login_options(&auth); @@ -266,6 +383,7 @@ mod tests { }; let opts = get_login_options(&auth); assert!(!opts.oidc_auto_redirect); +>>>>>>> main } } diff --git a/auth/server/src/api/named/github.rs b/auth/server/src/api/named/github.rs index 6f0e4d9..42dcf3d 100644 --- a/auth/server/src/api/named/github.rs +++ b/auth/server/src/api/named/github.rs @@ -174,7 +174,7 @@ pub async fn github_callback( None => { let no_users_exist = auth.no_users_exist().await?; - if auth.registration_disabled() && !no_users_exist { + if auth.github_registration_disabled() && !no_users_exist { return Err( anyhow!("User registration is disabled") .status_code(StatusCode::UNAUTHORIZED), diff --git a/auth/server/src/api/named/google.rs b/auth/server/src/api/named/google.rs index d0e9157..f78bf63 100644 --- a/auth/server/src/api/named/google.rs +++ b/auth/server/src/api/named/google.rs @@ -172,7 +172,7 @@ pub async fn google_callback( None => { let no_users_exist = auth.no_users_exist().await?; - if auth.registration_disabled() && !no_users_exist { + if auth.google_registration_disabled() && !no_users_exist { return Err( anyhow!("User registration is disabled") .status_code(StatusCode::UNAUTHORIZED), diff --git a/auth/server/src/api/oidc.rs b/auth/server/src/api/oidc.rs index 74509c4..b5b2207 100644 --- a/auth/server/src/api/oidc.rs +++ b/auth/server/src/api/oidc.rs @@ -243,7 +243,7 @@ pub async fn oidc_callback( None => { let no_users_exist = auth.no_users_exist().await?; - if auth.registration_disabled() && !no_users_exist { + if auth.oidc_registration_disabled() && !no_users_exist { return Err( anyhow!("User registration is disabled") .status_code(StatusCode::UNAUTHORIZED), diff --git a/auth/server/src/lib.rs b/auth/server/src/lib.rs index 417c4ec..7f57729 100644 --- a/auth/server/src/lib.rs +++ b/auth/server/src/lib.rs @@ -79,11 +79,35 @@ pub trait AuthImpl: Send + Sync + 'static { "/auth" } - /// Disable new user registration. + /// Disable new user registration (all providers). fn registration_disabled(&self) -> bool { false } + /// Disable new user registration for local (username/password) signups only. + /// Defaults to [Self::registration_disabled]. + fn local_registration_disabled(&self) -> bool { + self.registration_disabled() + } + + /// Disable new user registration via OIDC only. + /// Defaults to [Self::registration_disabled]. + fn oidc_registration_disabled(&self) -> bool { + self.registration_disabled() + } + + /// Disable new user registration via GitHub only. + /// Defaults to [Self::registration_disabled]. + fn github_registration_disabled(&self) -> bool { + self.registration_disabled() + } + + /// Disable new user registration via Google only. + /// Defaults to [Self::registration_disabled]. + fn google_registration_disabled(&self) -> bool { + self.registration_disabled() + } + /// Provide usernames to lock credential updates for, /// such as demo users. fn locked_usernames(&self) -> &'static [String] {