Browse Source

feat: enhance OIDC strategy and state store with improved error handling and type definitions

pull/5981/head
Germán Martín 2 weeks ago
committed by Thomas Kaul
parent
commit
5bc28bebe4
  1. 16
      apps/api/src/app/auth/auth.module.ts
  2. 10
      apps/api/src/app/auth/oidc-state.store.ts
  3. 50
      apps/api/src/app/auth/oidc.strategy.ts

16
apps/api/src/app/auth/auth.module.ts

@ -88,16 +88,30 @@ import { OidcStrategy } from './oidc.strategy';
options.userInfoURL = config.userinfo_endpoint; options.userInfoURL = config.userinfo_endpoint;
} catch (error) { } catch (error) {
Logger.error(error, 'OidcStrategy'); Logger.error(error, 'OidcStrategy');
throw new Error('Failed to fetch OIDC configuration from issuer');
} }
} else { } else {
options.authorizationURL = configurationService.get( options.authorizationURL = configurationService.get(
'OIDC_AUTHORIZATION_URL' 'OIDC_AUTHORIZATION_URL'
); );
options.issuer = configurationService.get('OIDC_ISSUER');
options.tokenURL = configurationService.get('OIDC_TOKEN_URL'); options.tokenURL = configurationService.get('OIDC_TOKEN_URL');
options.userInfoURL = configurationService.get('OIDC_USER_INFO_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] inject: [AuthService, ConfigurationService]
}, },

10
apps/api/src/app/auth/oidc-state.store.ts

@ -6,8 +6,8 @@ export class OidcStateStore {
private stateMap = new Map< private stateMap = new Map<
string, string,
{ {
ctx: { maxAge?: number; nonce?: string; issued?: Date };
appState?: unknown; appState?: unknown;
ctx: { maxAge?: number; nonce?: string; issued?: Date };
meta?: unknown; meta?: unknown;
timestamp: number; timestamp: number;
} }
@ -18,9 +18,9 @@ export class OidcStateStore {
// Signature matches passport-openidconnect SessionStore // Signature matches passport-openidconnect SessionStore
public store( public store(
_req: unknown, _req: unknown,
ctx: { maxAge?: number; nonce?: string; issued?: Date },
appState: unknown,
_meta: unknown, _meta: unknown,
appState: unknown,
ctx: { maxAge?: number; nonce?: string; issued?: Date },
callback: (err: Error | null, handle?: string) => void callback: (err: Error | null, handle?: string) => void
): void { ): void {
try { try {
@ -50,8 +50,8 @@ export class OidcStateStore {
handle: string, handle: string,
callback: ( callback: (
err: Error | null, err: Error | null,
ctx?: { maxAge?: number; nonce?: string; issued?: Date }, appState?: unknown,
appState?: unknown ctx?: { maxAge?: number; nonce?: string; issued?: Date }
) => void ) => void
): void { ): void {
try { try {

50
apps/api/src/app/auth/oidc.strategy.ts

@ -8,14 +8,33 @@ import { AuthService } from './auth.service';
import { OidcStateStore } from './oidc-state.store'; import { OidcStateStore } from './oidc-state.store';
interface OidcStrategyOptions { interface OidcStrategyOptions {
authorizationURL?: string; authorizationURL: string;
callbackURL: string; callbackURL: string;
clientID: string; clientID: string;
clientSecret: string; clientSecret: string;
issuer?: string; issuer: string;
scope?: string[]; scope?: string[];
tokenURL?: string; tokenURL: string;
userInfoURL?: string; userInfoURL: string;
}
interface OidcProfile {
id?: string;
sub?: string;
}
interface OidcContext {
claims?: {
sub?: string;
};
}
interface OidcIdToken {
sub?: string;
}
interface OidcParams {
sub?: string;
} }
@Injectable() @Injectable()
@ -30,25 +49,32 @@ export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
...options, ...options,
passReqToCallback: true, passReqToCallback: true,
store: OidcStrategy.stateStore store: OidcStrategy.stateStore
// eslint-disable-next-line @typescript-eslint/no-explicit-any });
} as any);
} }
public async validate( public async validate(
_request: Request, _request: Request,
_issuer: string, issuer: string,
profile: { id?: string }, profile: OidcProfile,
context: { claims?: { sub?: string } }, context: OidcContext,
idToken: { sub?: string }, idToken: OidcIdToken,
_accessToken: string, _accessToken: string,
_refreshToken: string, _refreshToken: string,
params: { sub?: string } params: OidcParams
) { ) {
try { try {
const thirdPartyId = const thirdPartyId =
params?.sub || idToken?.sub || context?.claims?.sub || profile?.id; profile?.id ??
profile?.sub ??
idToken?.sub ??
params?.sub ??
context?.claims?.sub;
if (!thirdPartyId) { if (!thirdPartyId) {
Logger.error(
`Missing subject identifier in OIDC response from ${issuer}`,
'OidcStrategy'
);
throw new Error('Missing subject identifier in OIDC response'); throw new Error('Missing subject identifier in OIDC response');
} }

Loading…
Cancel
Save