From 5bc176df716e34f2446108ea736e6e4882165f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADn?= Date: Sun, 23 Nov 2025 17:16:08 +0100 Subject: [PATCH] feat: enhance OIDC strategy and state store with improved error handling and type definitions --- apps/api/src/app/auth/auth.module.ts | 16 +++++++- apps/api/src/app/auth/oidc-state.store.ts | 10 ++--- apps/api/src/app/auth/oidc.strategy.ts | 50 +++++++++++++++++------ 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/apps/api/src/app/auth/auth.module.ts b/apps/api/src/app/auth/auth.module.ts index 00494ccdb..9c535f947 100644 --- a/apps/api/src/app/auth/auth.module.ts +++ b/apps/api/src/app/auth/auth.module.ts @@ -88,16 +88,30 @@ import { OidcStrategy } from './oidc.strategy'; options.userInfoURL = config.userinfo_endpoint; } catch (error) { Logger.error(error, 'OidcStrategy'); + throw new Error('Failed to fetch OIDC configuration from issuer'); } } else { options.authorizationURL = configurationService.get( 'OIDC_AUTHORIZATION_URL' ); + options.issuer = configurationService.get('OIDC_ISSUER'); options.tokenURL = configurationService.get('OIDC_TOKEN_URL'); options.userInfoURL = configurationService.get('OIDC_USER_INFO_URL'); } - return new OidcStrategy(authService, options); + return new OidcStrategy( + authService, + options as { + authorizationURL: string; + callbackURL: string; + clientID: string; + clientSecret: string; + issuer: string; + scope: string[]; + tokenURL: string; + userInfoURL: string; + } + ); }, inject: [AuthService, ConfigurationService] }, diff --git a/apps/api/src/app/auth/oidc-state.store.ts b/apps/api/src/app/auth/oidc-state.store.ts index 5c9ef2ace..d1b578e2b 100644 --- a/apps/api/src/app/auth/oidc-state.store.ts +++ b/apps/api/src/app/auth/oidc-state.store.ts @@ -6,8 +6,8 @@ export class OidcStateStore { private stateMap = new Map< string, { - ctx: { maxAge?: number; nonce?: string; issued?: Date }; appState?: unknown; + ctx: { maxAge?: number; nonce?: string; issued?: Date }; meta?: unknown; timestamp: number; } @@ -18,9 +18,9 @@ export class OidcStateStore { // Signature matches passport-openidconnect SessionStore public store( _req: unknown, - ctx: { maxAge?: number; nonce?: string; issued?: Date }, - appState: unknown, _meta: unknown, + appState: unknown, + ctx: { maxAge?: number; nonce?: string; issued?: Date }, callback: (err: Error | null, handle?: string) => void ): void { try { @@ -50,8 +50,8 @@ export class OidcStateStore { handle: string, callback: ( err: Error | null, - ctx?: { maxAge?: number; nonce?: string; issued?: Date }, - appState?: unknown + appState?: unknown, + ctx?: { maxAge?: number; nonce?: string; issued?: Date } ) => void ): void { try { diff --git a/apps/api/src/app/auth/oidc.strategy.ts b/apps/api/src/app/auth/oidc.strategy.ts index 3224daed9..2ac1e0473 100644 --- a/apps/api/src/app/auth/oidc.strategy.ts +++ b/apps/api/src/app/auth/oidc.strategy.ts @@ -8,14 +8,33 @@ import { AuthService } from './auth.service'; import { OidcStateStore } from './oidc-state.store'; interface OidcStrategyOptions { - authorizationURL?: string; + authorizationURL: string; callbackURL: string; clientID: string; clientSecret: string; - issuer?: string; + issuer: string; scope?: string[]; - tokenURL?: string; - userInfoURL?: string; + tokenURL: string; + userInfoURL: string; +} + +interface OidcProfile { + id?: string; + sub?: string; +} + +interface OidcContext { + claims?: { + sub?: string; + }; +} + +interface OidcIdToken { + sub?: string; +} + +interface OidcParams { + sub?: string; } @Injectable() @@ -30,25 +49,32 @@ export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') { ...options, passReqToCallback: true, store: OidcStrategy.stateStore - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any); + }); } public async validate( _request: Request, - _issuer: string, - profile: { id?: string }, - context: { claims?: { sub?: string } }, - idToken: { sub?: string }, + issuer: string, + profile: OidcProfile, + context: OidcContext, + idToken: OidcIdToken, _accessToken: string, _refreshToken: string, - params: { sub?: string } + params: OidcParams ) { try { const thirdPartyId = - params?.sub || idToken?.sub || context?.claims?.sub || profile?.id; + profile?.id ?? + profile?.sub ?? + idToken?.sub ?? + params?.sub ?? + context?.claims?.sub; if (!thirdPartyId) { + Logger.error( + `Missing subject identifier in OIDC response from ${issuer}`, + 'OidcStrategy' + ); throw new Error('Missing subject identifier in OIDC response'); }