diff --git a/apps/api/src/app/auth/auth.controller.ts b/apps/api/src/app/auth/auth.controller.ts
index 29af3a3c2..388f1dbd3 100644
--- a/apps/api/src/app/auth/auth.controller.ts
+++ b/apps/api/src/app/auth/auth.controller.ts
@@ -111,8 +111,6 @@ export class AuthController {
StatusCodes.FORBIDDEN
);
}
-
- // Initiates the OIDC login flow
}
@Get('oidc/callback')
diff --git a/apps/api/src/app/auth/auth.module.ts b/apps/api/src/app/auth/auth.module.ts
index c31e66299..4404205ce 100644
--- a/apps/api/src/app/auth/auth.module.ts
+++ b/apps/api/src/app/auth/auth.module.ts
@@ -60,6 +60,7 @@ import { OidcStrategy } from './oidc.strategy';
const response = await fetch(
`${issuer}/.well-known/openid-configuration`
);
+
const config = (await response.json()) as {
authorization_endpoint: string;
token_endpoint: string;
@@ -67,12 +68,12 @@ import { OidcStrategy } from './oidc.strategy';
};
options = {
+ issuer,
+ scope,
authorizationURL: config.authorization_endpoint,
callbackURL: callbackUrl,
clientID: configurationService.get('OIDC_CLIENT_ID'),
clientSecret: configurationService.get('OIDC_CLIENT_SECRET'),
- issuer,
- scope,
tokenURL: config.token_endpoint,
userInfoURL: config.userinfo_endpoint
};
@@ -82,6 +83,7 @@ import { OidcStrategy } from './oidc.strategy';
}
} else {
options = {
+ scope,
authorizationURL: configurationService.get(
'OIDC_AUTHORIZATION_URL'
),
@@ -89,7 +91,6 @@ import { OidcStrategy } from './oidc.strategy';
clientID: configurationService.get('OIDC_CLIENT_ID'),
clientSecret: configurationService.get('OIDC_CLIENT_SECRET'),
issuer: configurationService.get('OIDC_ISSUER'),
- scope,
tokenURL: configurationService.get('OIDC_TOKEN_URL'),
userInfoURL: configurationService.get('OIDC_USER_INFO_URL')
};
diff --git a/apps/api/src/app/auth/interfaces/interfaces.ts b/apps/api/src/app/auth/interfaces/interfaces.ts
index 4fdcc25b5..7ddfe41d2 100644
--- a/apps/api/src/app/auth/interfaces/interfaces.ts
+++ b/apps/api/src/app/auth/interfaces/interfaces.ts
@@ -6,6 +6,25 @@ export interface AuthDeviceDialogParams {
authDevice: AuthDeviceDto;
}
+export interface OidcContext {
+ claims?: {
+ sub?: string;
+ };
+}
+
+export interface OidcIdToken {
+ sub?: string;
+}
+
+export interface OidcParams {
+ sub?: string;
+}
+
+export interface OidcProfile {
+ id?: string;
+ sub?: string;
+}
+
export interface ValidateOAuthLoginParams {
provider: Provider;
thirdPartyId: string;
diff --git a/apps/api/src/app/auth/oidc-state.store.ts b/apps/api/src/app/auth/oidc-state.store.ts
index 437846cf1..0d9bb5f0f 100644
--- a/apps/api/src/app/auth/oidc-state.store.ts
+++ b/apps/api/src/app/auth/oidc-state.store.ts
@@ -5,6 +5,8 @@ import ms from 'ms';
* This store manages OAuth2 state parameters in memory with automatic cleanup.
*/
export class OidcStateStore {
+ private readonly STATE_EXPIRY_MS = ms('10 minutes');
+
private stateMap = new Map<
string,
{
@@ -14,7 +16,6 @@ export class OidcStateStore {
timestamp: number;
}
>();
- private readonly STATE_EXPIRY_MS = ms('10 minutes');
/**
* Store request state.
@@ -26,7 +27,7 @@ export class OidcStateStore {
appState: unknown,
ctx: { maxAge?: number; nonce?: string; issued?: Date },
callback: (err: Error | null, handle?: string) => void
- ): void {
+ ) {
try {
// Generate a unique handle for this state
const handle = this.generateHandle();
@@ -59,7 +60,7 @@ export class OidcStateStore {
appState?: unknown,
ctx?: { maxAge?: number; nonce?: string; issued?: Date }
) => void
- ): void {
+ ) {
try {
const data = this.stateMap.get(handle);
@@ -85,7 +86,7 @@ export class OidcStateStore {
/**
* Clean up expired states
*/
- private cleanup(): void {
+ private cleanup() {
const now = Date.now();
const expiredKeys: string[] = [];
@@ -103,7 +104,7 @@ export class OidcStateStore {
/**
* Generate a cryptographically secure random handle
*/
- private generateHandle(): string {
+ private generateHandle() {
return (
Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15) +
diff --git a/apps/api/src/app/auth/oidc.strategy.ts b/apps/api/src/app/auth/oidc.strategy.ts
index 58fd7bd87..6ed03b5a8 100644
--- a/apps/api/src/app/auth/oidc.strategy.ts
+++ b/apps/api/src/app/auth/oidc.strategy.ts
@@ -5,27 +5,14 @@ import { Request } from 'express';
import { Strategy, type StrategyOptions } from 'passport-openidconnect';
import { AuthService } from './auth.service';
+import {
+ OidcContext,
+ OidcIdToken,
+ OidcParams,
+ OidcProfile
+} from './interfaces/interfaces';
import { OidcStateStore } from './oidc-state.store';
-interface OidcProfile {
- id?: string;
- sub?: string;
-}
-
-interface OidcContext {
- claims?: {
- sub?: string;
- };
-}
-
-interface OidcIdToken {
- sub?: string;
-}
-
-interface OidcParams {
- sub?: string;
-}
-
@Injectable()
export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
private static readonly stateStore = new OidcStateStore();
diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts
index 6f139b305..2a0546961 100644
--- a/apps/api/src/services/configuration/configuration.service.ts
+++ b/apps/api/src/services/configuration/configuration.service.ts
@@ -55,17 +55,17 @@ export class ConfigurationService {
GOOGLE_SHEETS_ID: str({ default: '' }),
GOOGLE_SHEETS_PRIVATE_KEY: str({ default: '' }),
HOST: host({ default: DEFAULT_HOST }),
- JWT_SECRET_KEY: str({}),
+ 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: 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_AUTHORIZATION_URL: str(),
+ OIDC_CALLBACK_URL: str(),
+ OIDC_CLIENT_ID: str(),
+ OIDC_CLIENT_SECRET: str(),
+ OIDC_ISSUER: str(),
OIDC_SCOPE: json({ default: ['openid'] }),
- OIDC_TOKEN_URL: str({ default: undefined }),
- OIDC_USER_INFO_URL: str({ default: undefined }),
+ OIDC_TOKEN_URL: str(),
+ OIDC_USER_INFO_URL: str(),
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 d345c4df5..cf5611ef7 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
}
diff --git a/prisma/migrations/20251103162035_add_oidc_provider/migration.sql b/prisma/migrations/20251103162035_added_oidc_to_provider/migration.sql
similarity index 100%
rename from prisma/migrations/20251103162035_add_oidc_provider/migration.sql
rename to prisma/migrations/20251103162035_added_oidc_to_provider/migration.sql