feat(oidc): add auto-redirect for OIDC

add `auto_redirect_if_standalone_oidc` in `OXICLOUD_AUTH_POLICIES`
let admin decide to redirect immediately to IdP if OIDC is the only auth method enabled
This commit is contained in:
Edouard Vanbelle
2026-08-03 00:22:19 +02:00
parent b91f2fab2b
commit 5ebe2d3bae
12 changed files with 144 additions and 38 deletions
+1
View File
@@ -122,6 +122,7 @@ The verification-piggyback flow above deliberately **bypasses the `has_password`
| Token | Effect | | Token | Effect |
| --- | --- | | --- | --- |
| `permit_magic_link_for_password_users` | Allow magic-link login for accounts that also have a password. OIDC-linked users are still refused. | | `permit_magic_link_for_password_users` | Allow magic-link login for accounts that also have a password. OIDC-linked users are still refused. |
| `auto_redirect_if_standalone_oidc` | When OIDC is the ONLY working login method (no password, no magic-link — via allowlist or the OIDC-master rule), the login SPA auto-redirects to the IdP on page load instead of showing a click-to-continue SSO button. Off by default to avoid redirect loops on IdP failure and to preserve logout UX (logging out then visiting `/login` would otherwise bounce the user right back in). Silent no-op when other methods are also live. Frontend reads this via `auto_redirect_to_oidc` on `GET /api/auth/oidc/providers`. |
Unknown tokens are logged-and-skipped at startup so a typo doesn't silently zero the vector. Unknown tokens are logged-and-skipped at startup so a typo doesn't silently zero the vector.
+1 -1
View File
@@ -47,7 +47,7 @@ Most runtime variables use the `OXICLOUD_` prefix. A few build-time or allocator
| `OXICLOUD_DISABLE_REGISTRATION` | false | Disable registration of new user accounts | | `OXICLOUD_DISABLE_REGISTRATION` | false | Disable registration of new user accounts |
| `OXICLOUD_REGISTRATION_ALLOWED_EMAIL_DOMAINS` | — | Comma-separated allowlist of email domains accepted on `POST /api/auth/register` (case-insensitive, exact match on the post-`@` part). Empty = any domain is allowed. **Distinct from `OXICLOUD_EXTERNAL_EMAIL_DOMAINS`**: this one gates SELF-registration (public sign-up), the external list gates INVITATIONS (grants + magic-link to third parties). An operator can lock sign-up to their company domain while leaving invitations open. Subdomains must be listed explicitly. Rejected registrations return 403 `RegistrationDomainNotAllowed` and emit an `audit` line. Example: `mycompany.com,mycompany-eu.com`. | | `OXICLOUD_REGISTRATION_ALLOWED_EMAIL_DOMAINS` | — | Comma-separated allowlist of email domains accepted on `POST /api/auth/register` (case-insensitive, exact match on the post-`@` part). Empty = any domain is allowed. **Distinct from `OXICLOUD_EXTERNAL_EMAIL_DOMAINS`**: this one gates SELF-registration (public sign-up), the external list gates INVITATIONS (grants + magic-link to third parties). An operator can lock sign-up to their company domain while leaving invitations open. Subdomains must be listed explicitly. Rejected registrations return 403 `RegistrationDomainNotAllowed` and emit an `audit` line. Example: `mycompany.com,mycompany-eu.com`. |
| `OXICLOUD_AUTH_METHODS` | `password,magic_link` | Comma-separated allowlist of auth methods (`password`, `magic_link`, `oidc`). **Fail-fast**: unknown token → boot panic; empty allowlist → boot panic; `oidc` in list without `OXICLOUD_OIDC_ENABLED=true` → boot panic. Removing `password` disables `POST /api/auth/login` (returns 403 `PasswordLoginDisabled`) and password-based `register` (returns 403 `PasswordRegistrationDisabled`). Removing `magic_link` disables `POST /api/auth/magic-link/send` (returns 403 `MagicLinkLoginDisabled`) and the redemption path for login-purpose tokens. Setting `OXICLOUD_AUTH_METHODS=oidc` is the cleanest "SSO-only" posture. **Loose semantic (deprecation warning)**: if this list is explicitly set WITHOUT `oidc` but `OXICLOUD_OIDC_ENABLED=true`, OIDC is served regardless — a boot warning is emitted and this will become a fail-fast panic in the next major release. **Startup gate**: if `magic_link` is the only working method (no `password`, no `oidc`) AND no SMTP transport is configured (`OXICLOUD_SMTP_HOST` empty), the server refuses to start. **OIDC master rule**: when OIDC is enabled, magic-link login is hard-disabled regardless of this list (would otherwise bypass IdP-enforced MFA / step-up). Legacy alias: `OXICLOUD_OIDC_DISABLE_PASSWORD_LOGIN=true` still removes `password` from the list. | | `OXICLOUD_AUTH_METHODS` | `password,magic_link` | Comma-separated allowlist of auth methods (`password`, `magic_link`, `oidc`). **Fail-fast**: unknown token → boot panic; empty allowlist → boot panic; `oidc` in list without `OXICLOUD_OIDC_ENABLED=true` → boot panic. Removing `password` disables `POST /api/auth/login` (returns 403 `PasswordLoginDisabled`) and password-based `register` (returns 403 `PasswordRegistrationDisabled`). Removing `magic_link` disables `POST /api/auth/magic-link/send` (returns 403 `MagicLinkLoginDisabled`) and the redemption path for login-purpose tokens. Setting `OXICLOUD_AUTH_METHODS=oidc` is the cleanest "SSO-only" posture. **Loose semantic (deprecation warning)**: if this list is explicitly set WITHOUT `oidc` but `OXICLOUD_OIDC_ENABLED=true`, OIDC is served regardless — a boot warning is emitted and this will become a fail-fast panic in the next major release. **Startup gate**: if `magic_link` is the only working method (no `password`, no `oidc`) AND no SMTP transport is configured (`OXICLOUD_SMTP_HOST` empty), the server refuses to start. **OIDC master rule**: when OIDC is enabled, magic-link login is hard-disabled regardless of this list (would otherwise bypass IdP-enforced MFA / step-up). Legacy alias: `OXICLOUD_OIDC_DISABLE_PASSWORD_LOGIN=true` still removes `password` from the list. |
| `OXICLOUD_AUTH_POLICIES` | — | Comma-separated additive policy switches. Each token grants an exception or restriction to the default auth behaviour; empty (unset) = pure defaults. Recognised tokens: `permit_magic_link_for_password_users` (allow magic-link login for accounts that also have a password — off by default because magic-link would weaken the password to mailbox-strength; OIDC-linked users are still refused regardless). | | `OXICLOUD_AUTH_POLICIES` | — | Comma-separated additive policy switches. Each token grants an exception or restriction to the default auth behaviour; empty (unset) = pure defaults. Recognised tokens: `permit_magic_link_for_password_users` (allow magic-link login for accounts that also have a password — off by default because magic-link would weaken the password to mailbox-strength; OIDC-linked users are still refused regardless); `auto_redirect_if_standalone_oidc` (when OIDC is the only working login method, auto-redirect the login page to the IdP instead of showing a click-to-continue button — off by default to avoid redirect loops on IdP failure and preserve logout UX). |
| `OXICLOUD_REQUIRE_VERIFIED_EMAIL` | `false` | When `true`, `POST /api/auth/login` returns 403 `EmailNotVerified` for any account whose `email_verified_at` is NULL. Users can prove control by requesting a magic-link (whose redemption stamps `email_verified_at`), so this composes with `magic_link` in `OXICLOUD_AUTH_METHODS` to give users a self-service verification path. Admin-created (`POST /api/admin/users`) and setup-admin (`POST /api/setup`) users are auto-verified. OIDC-JIT users are also stamped verified at creation. | | `OXICLOUD_REQUIRE_VERIFIED_EMAIL` | `false` | When `true`, `POST /api/auth/login` returns 403 `EmailNotVerified` for any account whose `email_verified_at` is NULL. Users can prove control by requesting a magic-link (whose redemption stamps `email_verified_at`), so this composes with `magic_link` in `OXICLOUD_AUTH_METHODS` to give users a self-service verification path. Admin-created (`POST /api/admin/users`) and setup-admin (`POST /api/setup`) users are auto-verified. OIDC-JIT users are also stamped verified at creation. |
### Rate Limiting & Account Lockout ### Rate Limiting & Account Lockout
+13
View File
@@ -805,8 +805,21 @@ OXICLOUD_WOPI_ENABLED=false
# policy — the IdP is the security boundary and may enforce MFA we # policy — the IdP is the security boundary and may enforce MFA we
# shouldn't bypass. # shouldn't bypass.
# #
# auto_redirect_if_standalone_oidc
# When OIDC is the ONLY working login method (no password, no
# magic-link, whether via the allowlist or the OIDC-master rule),
# the login SPA auto-redirects to the OIDC authorize endpoint on
# page load instead of showing a click-to-continue SSO button.
# Off by default because auto-redirect can loop on IdP failure
# (login → IdP error → back to login → auto-redirect again) and
# makes logout-then-visit-login flows feel broken (bounces the
# user right back into the app). Silent no-op when the login page
# has more than one method available (nothing to auto-choose).
#
# Example: # Example:
#OXICLOUD_AUTH_POLICIES=permit_magic_link_for_password_users #OXICLOUD_AUTH_POLICIES=permit_magic_link_for_password_users
#OXICLOUD_AUTH_POLICIES=auto_redirect_if_standalone_oidc
#OXICLOUD_AUTH_POLICIES=permit_magic_link_for_password_users,auto_redirect_if_standalone_oidc
# Operator-level kill switch for share-notification emails to internal # Operator-level kill switch for share-notification emails to internal
+6 -7
View File
@@ -212,18 +212,17 @@
margin-top: var(--space-5); margin-top: var(--space-5);
} }
/* SSO / OIDC button */ /* SSO / OIDC button — inherits the primary .auth-button visual
(accent gradient + on-accent text), only overriding layout so an
optional provider icon can sit beside the label. The previous
text-color gradient was unreadable in dark mode because the
inherited text color also resolved to a light value. */
.auth-button-oidc { .auth-button-oidc {
background: linear-gradient(135deg, var(--color-text) 0%, var(--color-text-secondary) 100%);
box-shadow: 0 4px 12px var(--color-shadow-3xl);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: var(--space-2-5); gap: var(--space-2-5);
} text-decoration: none;
.auth-button-oidc:hover {
box-shadow: 0 6px 20px var(--color-shadow-4xl);
} }
.auth-button-sso { .auth-button-sso {
+5 -23
View File
@@ -249,19 +249,6 @@
} }
} }
// Shared by onMount step 4 and onSetup: true + navigates away iff OIDC is
// the only login method. Centralised so the guard can't drift between the
// two call sites (only the `?error=` loop-guard, checked at onMount time,
// doesn't apply post-setup — a freshly created admin can't have bounced
// off the IdP yet).
function tryAutoRedirectToIdp(): boolean {
if (oidc.enabled && oidc.password_login_enabled === false && oidc.authorize_endpoint) {
window.location.replace(oidc.authorize_endpoint);
return true;
}
return false;
}
async function onSetup(e: SubmitEvent) { async function onSetup(e: SubmitEvent) {
e.preventDefault(); e.preventDefault();
setupError = ''; setupError = '';
@@ -276,10 +263,6 @@
setupEmail = setupPassword = setupConfirm = ''; setupEmail = setupPassword = setupConfirm = '';
// Admin now exists — fold the setup affordance away and return to login. // Admin now exists — fold the setup affordance away and return to login.
setupAvailable = false; setupAvailable = false;
// OIDC-only: the login page would immediately redirect on the next
// visit anyway — skip the "you can now sign in" detour and forward
// straight to the IdP instead of leaving a dead-end local form.
if (tryAutoRedirectToIdp()) return;
setupSuccess = t('auth.admin_success', 'Administrator created. You can now sign in.'); setupSuccess = t('auth.admin_success', 'Administrator created. You can now sign in.');
setTimeout(() => { setTimeout(() => {
mode = 'login'; mode = 'login';
@@ -340,12 +323,11 @@
setupAvailable = !status.initialized; setupAvailable = !status.initialized;
if (setupAvailable) mode = 'setup'; if (setupAvailable) mode = 'setup';
// 4) Auto-redirect: when OIDC is the only auth method, skip the login page. // Auto-redirect to the IdP in standalone-OIDC posture is enforced
// Guard against loops: if the IdP returned ?error=, fall through to the UI. // server-side via the `auto_redirect_if_standalone_oidc` auth policy
if (!setupAvailable && !page.url.searchParams.has('error') && tryAutoRedirectToIdp()) { // (see interfaces/web/mod.rs::oidc_standalone_login_redirect). Keeping
return; // a client-side copy would make the policy toggle a no-op — the SPA
} // would auto-redirect regardless of what the admin configured.
booting = false; booting = false;
}); });
+8
View File
@@ -383,6 +383,14 @@ pub struct OidcProviderInfoDto {
/// straight after signup. /// straight after signup.
#[serde(default)] #[serde(default)]
pub require_verified_email: bool, pub require_verified_email: bool,
/// True iff the effective allowlist is `[Oidc]` AND the
/// `auto_redirect_if_standalone_oidc` policy is set. Frontend
/// uses this to decide whether to auto-redirect to the authorize
/// endpoint on login-page mount (true) or show a click-to-continue
/// button (false). Default false — the safe posture that avoids
/// redirect loops when the IdP is degraded.
#[serde(default)]
pub auto_redirect_to_oidc: bool,
} }
/// Claims extracted from the validated OIDC ID token /// Claims extracted from the validated OIDC ID token
@@ -9,7 +9,7 @@ use crate::application::ports::auth_ports::{
use crate::application::ports::authorization_ports::AuthorizationEngine; use crate::application::ports::authorization_ports::AuthorizationEngine;
use crate::application::ports::user_lifecycle::{DeletionMode, LogoutReason}; use crate::application::ports::user_lifecycle::{DeletionMode, LogoutReason};
use crate::application::services::user_lifecycle_service::UserLifecycleService; use crate::application::services::user_lifecycle_service::UserLifecycleService;
use crate::common::config::{AuthMethod, OidcConfig}; use crate::common::config::{AuthMethod, AuthPolicy, OidcConfig};
use crate::common::errors::{DomainError, ErrorKind}; use crate::common::errors::{DomainError, ErrorKind};
use crate::domain::entities::magic_link_token::{MagicLinkResourceKind, MagicLinkStatus}; use crate::domain::entities::magic_link_token::{MagicLinkResourceKind, MagicLinkStatus};
use crate::domain::entities::session::Session; use crate::domain::entities::session::Session;
@@ -161,6 +161,11 @@ pub struct AuthApplicationService {
/// `is_password_login_allowed()` / `is_magic_link_login_allowed()` /// `is_password_login_allowed()` / `is_magic_link_login_allowed()`
/// so callers don't have to reach for the app config. /// so callers don't have to reach for the app config.
allowed_auth_methods: Vec<AuthMethod>, allowed_auth_methods: Vec<AuthMethod>,
/// Additive auth-policy switches (mirrors `AuthConfig::auth_policies`).
/// Consulted by handlers / providers-info endpoint to compose the
/// login-page UX hints (e.g. `AutoRedirectIfStandaloneOidc`) without
/// reaching into the app config on every call.
auth_policies: Vec<AuthPolicy>,
/// Whether `POST /api/auth/login` refuses accounts whose /// Whether `POST /api/auth/login` refuses accounts whose
/// `email_verified_at IS NULL`. Mirrors /// `email_verified_at IS NULL`. Mirrors
/// `AuthConfig::require_verified_email`. /// `AuthConfig::require_verified_email`.
@@ -209,20 +214,24 @@ impl AuthApplicationService {
.time_to_live(USER_FLAGS_CACHE_TTL) .time_to_live(USER_FLAGS_CACHE_TTL)
.build(), .build(),
allowed_auth_methods: vec![AuthMethod::Password, AuthMethod::MagicLink], allowed_auth_methods: vec![AuthMethod::Password, AuthMethod::MagicLink],
auth_policies: Vec::new(),
require_verified_email: false, require_verified_email: false,
} }
} }
/// Populates the auth-method allowlist + `require_verified_email` /// Populates the auth-method allowlist + policy vector +
/// snapshot from the loaded config. Called by the DI factory. If /// `require_verified_email` snapshot from the loaded config.
/// left uncalled (test builds), defaults are permissive: both /// Called by the DI factory. If left uncalled (test builds),
/// methods enabled, verified-email not required. /// defaults are permissive: both self-service methods enabled,
/// no policies, verified-email not required.
pub fn with_auth_policy( pub fn with_auth_policy(
mut self, mut self,
allowed_methods: Vec<AuthMethod>, allowed_methods: Vec<AuthMethod>,
auth_policies: Vec<AuthPolicy>,
require_verified_email: bool, require_verified_email: bool,
) -> Self { ) -> Self {
self.allowed_auth_methods = allowed_methods; self.allowed_auth_methods = allowed_methods;
self.auth_policies = auth_policies;
self.require_verified_email = require_verified_email; self.require_verified_email = require_verified_email;
self self
} }
@@ -264,6 +273,26 @@ impl AuthApplicationService {
self.require_verified_email self.require_verified_email
} }
/// True iff the login SPA should auto-redirect to the OIDC
/// authorize endpoint on page load (SSO-only, no click needed).
///
/// Composed to be BOTH policy-set AND effectively-standalone:
/// * `AutoRedirectIfStandaloneOidc` policy is in the vector, AND
/// * OIDC is enabled AND is the only WORKING login method
/// (password + magic-link both refused by the composition of
/// the allowlist + OIDC-master rule).
///
/// When the policy is set but other methods are also live, this is
/// a silent no-op — the FE renders the multi-method chooser. If
/// the policy is NOT set, this is always false regardless.
pub fn auto_redirect_to_oidc(&self) -> bool {
self.auth_policies
.contains(&AuthPolicy::AutoRedirectIfStandaloneOidc)
&& self.oidc_enabled()
&& !self.is_password_login_allowed()
&& !self.is_magic_link_login_allowed()
}
/// Resolve a login-identifier (username OR email) to the account's /// Resolve a login-identifier (username OR email) to the account's
/// registered email address. Mirrors the `POST /api/auth/login` /// registered email address. Mirrors the `POST /api/auth/login`
/// dispatcher (`@` presence → email lookup, else → username /// dispatcher (`@` presence → email lookup, else → username
+21
View File
@@ -1493,6 +1493,24 @@ pub enum AuthPolicy {
/// Deprecated legacy alias: `OXICLOUD_MAGIC_LINK_OPEN_TO_PASSWORD_USERS=true` /// Deprecated legacy alias: `OXICLOUD_MAGIC_LINK_OPEN_TO_PASSWORD_USERS=true`
/// adds this variant to the vector with a startup warning. /// adds this variant to the vector with a startup warning.
PermitMagicLinkForPasswordUsers, PermitMagicLinkForPasswordUsers,
/// When OIDC is the ONLY auth method available (standalone SSO
/// posture — no password + no magic-link), instruct the login SPA
/// to auto-redirect to the OIDC authorize endpoint on page load
/// instead of showing a click-to-continue button.
///
/// Opt-in because:
///
/// - Auto-redirect can create loops on IdP failure (login → IdP
/// error → back to login → auto-redirect again).
/// - Logout followed by "visit login page" would bounce the user
/// right back into the app they just logged out of.
///
/// Only takes effect when the effective allowlist is `[Oidc]`
/// (or magic-link is off via the OIDC-master rule and password is
/// disabled): if any other method is live the policy is a silent
/// no-op (there's a choice to render, not a single path).
AutoRedirectIfStandaloneOidc,
} }
impl AuthPolicy { impl AuthPolicy {
@@ -1504,6 +1522,9 @@ impl AuthPolicy {
"permit_magic_link_for_password_users" | "permit-magic-link-for-password-users" => { "permit_magic_link_for_password_users" | "permit-magic-link-for-password-users" => {
Some(Self::PermitMagicLinkForPasswordUsers) Some(Self::PermitMagicLinkForPasswordUsers)
} }
"auto_redirect_if_standalone_oidc" | "auto-redirect-if-standalone-oidc" => {
Some(Self::AutoRedirectIfStandaloneOidc)
}
_ => None, _ => None,
} }
} }
+1
View File
@@ -57,6 +57,7 @@ pub async fn create_auth_services(
// rather than reaching into the app config on every call. // rather than reaching into the app config on every call.
auth_app_service = auth_app_service.with_auth_policy( auth_app_service = auth_app_service.with_auth_policy(
config.auth.allowed_auth_methods.clone(), config.auth.allowed_auth_methods.clone(),
config.auth.auth_policies.clone(),
config.auth.require_verified_email, config.auth.require_verified_email,
); );
@@ -1062,6 +1062,7 @@ pub async fn oidc_providers(
let password_login_enabled = auth_app.is_password_login_allowed(); let password_login_enabled = auth_app.is_password_login_allowed();
let magic_link_login_enabled = auth_app.is_magic_link_login_allowed(); let magic_link_login_enabled = auth_app.is_magic_link_login_allowed();
let require_verified_email = auth_app.require_verified_email(); let require_verified_email = auth_app.require_verified_email();
let auto_redirect_to_oidc = auth_app.auto_redirect_to_oidc();
if !auth_app.oidc_enabled() { if !auth_app.oidc_enabled() {
return Ok(Json(OidcProviderInfoDto { return Ok(Json(OidcProviderInfoDto {
@@ -1071,6 +1072,7 @@ pub async fn oidc_providers(
password_login_enabled, password_login_enabled,
magic_link_login_enabled, magic_link_login_enabled,
require_verified_email, require_verified_email,
auto_redirect_to_oidc: false,
})); }));
} }
@@ -1083,6 +1085,7 @@ pub async fn oidc_providers(
password_login_enabled, password_login_enabled,
magic_link_login_enabled, magic_link_login_enabled,
require_verified_email, require_verified_email,
auto_redirect_to_oidc,
})) }))
} }
+50 -1
View File
@@ -1,7 +1,10 @@
use crate::common::config::AppConfig; use crate::common::config::AppConfig;
use crate::common::di::AppState; use crate::common::di::AppState;
use axum::Router; use axum::Router;
use axum::extract::{Request, State};
use axum::http::header::{CACHE_CONTROL, HeaderValue}; use axum::http::header::{CACHE_CONTROL, HeaderValue};
use axum::middleware::Next;
use axum::response::{IntoResponse, Redirect, Response};
use axum::routing::get_service; use axum::routing::get_service;
use base64::Engine as _; use base64::Engine as _;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
@@ -41,7 +44,7 @@ pub fn resolve_static_path(config: &AppConfig) -> PathBuf {
/// Caching: content-hashed assets under `/_app/immutable` are cached forever; /// Caching: content-hashed assets under `/_app/immutable` are cached forever;
/// everything else — crucially the `index.html` shell — is `no-cache` so a deploy /// everything else — crucially the `index.html` shell — is `no-cache` so a deploy
/// can't leave a stale app pinned in browsers. /// can't leave a stale app pinned in browsers.
pub fn create_web_routes() -> Router<Arc<AppState>> { pub fn create_web_routes(app_state: Arc<AppState>) -> Router<Arc<AppState>> {
let config = AppConfig::from_env(); let config = AppConfig::from_env();
let static_path = resolve_static_path(&config); let static_path = resolve_static_path(&config);
@@ -91,6 +94,52 @@ pub fn create_web_routes() -> Router<Arc<AppState>> {
CACHE_CONTROL, CACHE_CONTROL,
HeaderValue::from_static("no-cache"), HeaderValue::from_static("no-cache"),
)) ))
// Short-circuit `GET /login` to the OIDC authorize endpoint when
// the AutoRedirectIfStandaloneOidc policy resolves. Runs BEFORE
// the SPA shell is served, so there's no form-then-redirect flash.
// The SPA carries the same predicate as belt-and-suspenders for
// deep links / browser-cache hits that skip this hop.
.layer(axum::middleware::from_fn_with_state(
app_state,
oidc_standalone_login_redirect,
))
}
/// Intercept `GET /login` and 302 to `/api/auth/oidc/authorize` when OIDC is
/// the only working method (see `AuthApplicationService::auto_redirect_to_oidc`).
///
/// Loop-guards mirror the SPA:
/// - `?error=…` — the IdP bounced us back; falling through lets the SPA render
/// the error rather than looping straight back to the failing IdP.
/// - `?oidc_code=…` — the callback landing carries the exchange code; the SPA
/// must handle it, not another authorize round-trip.
async fn oidc_standalone_login_redirect(
State(state): State<Arc<AppState>>,
req: Request,
next: Next,
) -> Response {
if req.method() == axum::http::Method::GET && req.uri().path() == "/login" {
let has_loop_guard_param = req
.uri()
.query()
.map(|q| {
q.split('&')
.any(|p| p.starts_with("error=") || p.starts_with("oidc_code="))
})
.unwrap_or(false);
let should_redirect = !has_loop_guard_param
&& state
.auth_service
.as_ref()
.map(|svc| svc.auth_application_service.auto_redirect_to_oidc())
.unwrap_or(false);
if should_redirect {
return Redirect::temporary("/api/auth/oidc/authorize").into_response();
}
}
next.run(req).await
} }
/// Build the `content-security-policy` header value served on every response. /// Build the `content-security-policy` header value served on every response.
+1 -1
View File
@@ -628,7 +628,7 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
let api_routes = create_api_routes(&app_state); let api_routes = create_api_routes(&app_state);
let public_api_routes = create_public_api_routes(&app_state); let public_api_routes = create_public_api_routes(&app_state);
let health_routes = create_health_routes(&app_state); let health_routes = create_health_routes(&app_state);
let web_routes = create_web_routes(); let web_routes = create_web_routes(app_state.clone());
let mut app; let mut app;