diff --git a/.env.example b/.env.example index 2f02616..9f7c539 100644 --- a/.env.example +++ b/.env.example @@ -71,4 +71,9 @@ OIDC_REDIRECT_URI= # Space-separated scopes # Default is "openid profile email" OIDC_SCOPES= +# Automatically redirect to OIDC provider instead of showing login page +# When enabled, users will be immediately redirected to the OIDC provider +# instead of seeing the login screen (only works when password auth is disabled) +# Default is FALSE +OIDC_AUTO_REDIRECT= #endregion \ No newline at end of file diff --git a/backend/api/auth_api.py b/backend/api/auth_api.py index 8dab64a..8086fcf 100644 --- a/backend/api/auth_api.py +++ b/backend/api/auth_api.py @@ -91,6 +91,16 @@ def is_password_set() -> bool: return AUTH_PASSWORD_PROVIDER.is_password_set() +@router.get( + path="/oidc_auto_redirect", + description="Check if OIDC auto redirect is enabled", + response_model=bool, +) +async def oidc_auto_redirect() -> bool: + from backend.config import Config + return Config.OIDC_AUTO_REDIRECT + + @router.get( path="/{provider}/login", description="Login with provider" ) diff --git a/backend/config.py b/backend/config.py index fdaff73..209338d 100644 --- a/backend/config.py +++ b/backend/config.py @@ -25,6 +25,7 @@ class Config: OIDC_CLIENT_SECRET: ClassVar[str] OIDC_REDIRECT_URI: ClassVar[str] OIDC_SCOPES: ClassVar[str] + OIDC_AUTO_REDIRECT: ClassVar[bool] @classmethod def load(cls): @@ -63,6 +64,7 @@ def load(cls): cls.OIDC_CLIENT_SECRET = os.getenv("OIDC_CLIENT_SECRET", "") cls.OIDC_REDIRECT_URI = os.getenv("OIDC_REDIRECT_URI", "") cls.OIDC_SCOPES = os.getenv("OIDC_SCOPES", "openid profile email") + cls.OIDC_AUTO_REDIRECT = os.getenv("OIDC_AUTO_REDIRECT", "false").lower() == "true" Config.load() diff --git a/frontend/src/app/entities/auth/auth-api.service.ts b/frontend/src/app/entities/auth/auth-api.service.ts index e9839c3..32e779e 100644 --- a/frontend/src/app/entities/auth/auth-api.service.ts +++ b/frontend/src/app/entities/auth/auth-api.service.ts @@ -57,4 +57,12 @@ export class AuthApiService extends BaseApiService<'/auth'> { isPasswordSet(): Observable { return this.httpClient.get(`${this.basePath}/is_password_set`); } + + /** + * Check if OIDC auto redirect is enabled + * @returns + */ + isOidcAutoRedirectEnabled(): Observable { + return this.httpClient.get(`${this.basePath}/oidc_auto_redirect`); + } } diff --git a/frontend/src/app/features/auth-page/auth-page.ts b/frontend/src/app/features/auth-page/auth-page.ts index 6ab4039..6b5d5d6 100644 --- a/frontend/src/app/features/auth-page/auth-page.ts +++ b/frontend/src/app/features/auth-page/auth-page.ts @@ -23,6 +23,11 @@ export class AuthPage { private readonly router = inject(Router); private readonly toastService = inject(ToastService); + constructor() { + // Check for OIDC auto-redirect when component initializes + this.checkOidcAutoRedirect(); + } + public readonly isLoading = signal(false); public readonly isPasswordSet = resource({ defaultValue: false, @@ -65,6 +70,20 @@ export class AuthPage { ), }); + public readonly isOidcAutoRedirectEnabled = resource({ + defaultValue: false, + loader: () => + firstValueFrom( + this.authApiService.isOidcAutoRedirectEnabled().pipe( + retry(1), + catchError((error) => { + this.toastService.error(error); + return throwError(() => error); + }), + ), + ), + }); + onSubmitNewPassword($event: ISetPasswordBody): void { this.isLoading.set(true); this.authApiService @@ -99,4 +118,18 @@ export class AuthPage { onOidcLogin(): void { this.authApiService.initiateLogin('oidc'); } + + private checkOidcAutoRedirect(): void { + // Wait for resources to load, then check if auto-redirect should happen + setTimeout(() => { + const oidcEnabled = this.isOidcEnabled.value(); + const passwordEnabled = this.isPasswordEnabled.value(); + const autoRedirectEnabled = this.isOidcAutoRedirectEnabled.value(); + + // Auto-redirect if OIDC auto-redirect is enabled, OIDC is enabled, and password auth is disabled + if (autoRedirectEnabled && oidcEnabled && !passwordEnabled) { + this.authApiService.initiateLogin('oidc'); + } + }, 100); + } }