diff --git a/apps/api/src/app/auth/auth.controller.ts b/apps/api/src/app/auth/auth.controller.ts index cd51e2954..29af3a3c2 100644 --- a/apps/api/src/app/auth/auth.controller.ts +++ b/apps/api/src/app/auth/auth.controller.ts @@ -84,7 +84,6 @@ export class AuthController { @Req() request: Request, @Res() response: Response ) { - // Handles the Google OAuth2 callback const jwt: string = (request.user as any).jwt; if (jwt) { @@ -120,7 +119,6 @@ export class AuthController { @UseGuards(AuthGuard('oidc')) @Version(VERSION_NEUTRAL) public oidcLoginCallback(@Req() request: Request, @Res() response: Response) { - // Handles the OIDC callback const jwt: string = (request.user as any).jwt; if (jwt) { diff --git a/apps/api/src/app/auth/auth.module.ts b/apps/api/src/app/auth/auth.module.ts index 60a3a64d1..c31e66299 100644 --- a/apps/api/src/app/auth/auth.module.ts +++ b/apps/api/src/app/auth/auth.module.ts @@ -40,6 +40,7 @@ import { OidcStrategy } from './oidc.strategy'; GoogleStrategy, JwtStrategy, { + inject: [AuthService, ConfigurationService], provide: OidcStrategy, useFactory: async ( authService: AuthService, @@ -95,8 +96,7 @@ import { OidcStrategy } from './oidc.strategy'; } return new OidcStrategy(authService, options); - }, - inject: [AuthService, ConfigurationService] + } }, WebAuthService ] diff --git a/apps/api/src/app/auth/oidc-state.store.ts b/apps/api/src/app/auth/oidc-state.store.ts index d1b578e2b..437846cf1 100644 --- a/apps/api/src/app/auth/oidc-state.store.ts +++ b/apps/api/src/app/auth/oidc-state.store.ts @@ -1,3 +1,5 @@ +import ms from 'ms'; + /** * Custom state store for OIDC authentication that doesn't rely on express-session. * This store manages OAuth2 state parameters in memory with automatic cleanup. @@ -7,15 +9,17 @@ export class OidcStateStore { string, { appState?: unknown; - ctx: { maxAge?: number; nonce?: string; issued?: Date }; + ctx: { issued?: Date; maxAge?: number; nonce?: string }; meta?: unknown; timestamp: number; } >(); - private readonly STATE_EXPIRY_MS = 10 * 60 * 1000; // 10 minutes + private readonly STATE_EXPIRY_MS = ms('10 minutes'); - // Store request state. - // Signature matches passport-openidconnect SessionStore + /** + * Store request state. + * Signature matches passport-openidconnect SessionStore + */ public store( _req: unknown, _meta: unknown, @@ -43,8 +47,10 @@ export class OidcStateStore { } } - // Verify request state. - // Signature matches passport-openidconnect SessionStore + /** + * Verify request state. + * Signature matches passport-openidconnect SessionStore + */ public verify( _req: unknown, handle: string, @@ -76,16 +82,9 @@ export class OidcStateStore { } } - // Generate a cryptographically secure random handle - private generateHandle(): string { - return ( - Math.random().toString(36).substring(2, 15) + - Math.random().toString(36).substring(2, 15) + - Date.now().toString(36) - ); - } - - // Clean up expired states + /** + * Clean up expired states + */ private cleanup(): void { const now = Date.now(); const expiredKeys: string[] = []; @@ -100,4 +99,15 @@ export class OidcStateStore { this.stateMap.delete(key); } } + + /** + * Generate a cryptographically secure random handle + */ + private generateHandle(): string { + return ( + Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15) + + Date.now().toString(36) + ); + } } diff --git a/apps/api/src/app/auth/oidc.strategy.ts b/apps/api/src/app/auth/oidc.strategy.ts index 8366c58bc..58fd7bd87 100644 --- a/apps/api/src/app/auth/oidc.strategy.ts +++ b/apps/api/src/app/auth/oidc.strategy.ts @@ -59,6 +59,11 @@ export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') { params?.sub ?? context?.claims?.sub; + const jwt = await this.authService.validateOAuthLogin({ + provider: Provider.OIDC, + thirdPartyId + }); + if (!thirdPartyId) { Logger.error( `Missing subject identifier in OIDC response from ${issuer}`, @@ -67,11 +72,6 @@ export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') { throw new Error('Missing subject identifier in OIDC response'); } - const jwt = await this.authService.validateOAuthLogin({ - provider: Provider.OIDC, - thirdPartyId - }); - return { jwt }; } catch (error) { Logger.error(error, 'OidcStrategy'); diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index 59de60354..6f139b305 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -58,14 +58,14 @@ export class ConfigurationService { JWT_SECRET_KEY: str({}), MAX_ACTIVITIES_TO_IMPORT: num({ default: Number.MAX_SAFE_INTEGER }), MAX_CHART_ITEMS: num({ default: 365 }), - OIDC_AUTHORIZATION_URL: str({ default: '' }), - OIDC_CALLBACK_URL: str({ default: '' }), - OIDC_CLIENT_ID: str({ default: '' }), - OIDC_CLIENT_SECRET: str({ default: '' }), - OIDC_ISSUER: str({ default: '' }), + OIDC_AUTHORIZATION_URL: str({ default: undefined }), + OIDC_CALLBACK_URL: str({ default: undefined }), + OIDC_CLIENT_ID: str({ default: undefined }), + OIDC_CLIENT_SECRET: str({ default: undefined }), + OIDC_ISSUER: str({ default: undefined }), OIDC_SCOPE: json({ default: ['openid'] }), - OIDC_TOKEN_URL: str({ default: '' }), - OIDC_USER_INFO_URL: str({ default: '' }), + OIDC_TOKEN_URL: str({ default: undefined }), + OIDC_USER_INFO_URL: str({ default: undefined }), PORT: port({ default: DEFAULT_PORT }), PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY: num({ default: DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html index 6570ab3d6..d345c4df5 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -41,7 +41,7 @@ class="mr-2" src="../assets/icons/google.svg" style="height: 1rem" - />Sign in with GoogleSign in with Google } @@ -52,7 +52,7 @@ class="px-4 rounded-pill" href="../api/auth/oidc" mat-stroked-button - >Sign in with OIDCSign in with OIDC } diff --git a/package-lock.json b/package-lock.json index c6f2e4155..3f6513901 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,7 +83,7 @@ "passport-google-oauth20": "2.0.0", "passport-headerapikey": "1.2.2", "passport-jwt": "4.0.1", - "passport-openidconnect": "^0.1.2", + "passport-openidconnect": "0.1.2", "reflect-metadata": "0.2.2", "rxjs": "7.8.1", "stripe": "18.5.0", @@ -130,7 +130,7 @@ "@types/node": "22.15.17", "@types/papaparse": "5.3.7", "@types/passport-google-oauth20": "2.0.16", - "@types/passport-openidconnect": "^0.1.3", + "@types/passport-openidconnect": "0.1.3", "@typescript-eslint/eslint-plugin": "8.43.0", "@typescript-eslint/parser": "8.43.0", "eslint": "9.35.0", diff --git a/package.json b/package.json index 9fecefc05..221aa7b31 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "passport-google-oauth20": "2.0.0", "passport-headerapikey": "1.2.2", "passport-jwt": "4.0.1", - "passport-openidconnect": "^0.1.2", + "passport-openidconnect": "0.1.2", "reflect-metadata": "0.2.2", "rxjs": "7.8.1", "stripe": "18.5.0", @@ -174,7 +174,7 @@ "@types/node": "22.15.17", "@types/papaparse": "5.3.7", "@types/passport-google-oauth20": "2.0.16", - "@types/passport-openidconnect": "^0.1.3", + "@types/passport-openidconnect": "0.1.3", "@typescript-eslint/eslint-plugin": "8.43.0", "@typescript-eslint/parser": "8.43.0", "eslint": "9.35.0",