diff --git a/CHANGELOG.md b/CHANGELOG.md index ca0d05c14..8f44c21fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgraded `papaparse` from version `5.5.3` to `5.7.0` - Upgraded `zone.js` from version `0.16.2` to `0.16.3` +### Fixed + +- Hardened the authentication with _OpenID Connect_ (`OIDC`) (experimental) + ## 3.69.0 - 2026-09-07 ### Changed diff --git a/apps/api/src/app/auth/oidc-state.store.ts b/apps/api/src/app/auth/oidc-state.store.ts index aebd99892..0131d184d 100644 --- a/apps/api/src/app/auth/oidc-state.store.ts +++ b/apps/api/src/app/auth/oidc-state.store.ts @@ -1,5 +1,6 @@ import type { Request } from 'express'; import ms from 'ms'; +import { randomBytes } from 'node:crypto'; import type { SessionStore, SessionStoreCallback, @@ -66,13 +67,20 @@ export class OidcStateStore implements SessionStore { const data = this.stateMap.get(handle); if (!data) { - return callback(null, undefined, undefined); + return callback( + new Error('Invalid OIDC state parameter'), + undefined, + undefined + ); } if (Date.now() - data.timestamp > this.STATE_EXPIRY_MS) { - // State has expired this.stateMap.delete(handle); - return callback(null, undefined, undefined); + return callback( + new Error('OIDC state has expired, please try again'), + undefined, + undefined + ); } // Remove state after verification (one-time use) @@ -106,10 +114,6 @@ export class OidcStateStore implements SessionStore { * Generate a cryptographically secure random handle */ private generateHandle() { - return ( - Math.random().toString(36).substring(2, 15) + - Math.random().toString(36).substring(2, 15) + - Date.now().toString(36) - ); + return randomBytes(32).toString('hex'); } } diff --git a/apps/api/src/guards/oauth-callback.guard.ts b/apps/api/src/guards/oauth-callback.guard.ts index cedbccde1..a89d77daa 100644 --- a/apps/api/src/guards/oauth-callback.guard.ts +++ b/apps/api/src/guards/oauth-callback.guard.ts @@ -5,11 +5,19 @@ export function OAuthCallbackGuard(strategy: string): Type { class OAuthCallbackGuardMixin extends AuthGuard(strategy) { private readonly logger = new Logger(OAuthCallbackGuard.name); - public override handleRequest(error: Error, user: any) { + public override handleRequest( + error: Error | null, + user: any, + info?: { message?: string } + ) { if (error) { this.logger.error( `Authentication with the ${strategy} strategy has failed: ${error.message}` ); + } else if (!user && info?.message) { + this.logger.warn( + `Authentication with the ${strategy} strategy was rejected: ${info.message}` + ); } // Do not throw, the callback handler redirects to the login page instead