This commit is contained in:
2026-02-18 19:19:42 +01:00
parent 9b783037ff
commit 5c86afd640
6 changed files with 39 additions and 45 deletions

View File

@@ -78,7 +78,6 @@ DATABASE_URL="postgresql://user:password@localhost:5432/timetracker"
# OIDC Configuration # OIDC Configuration
OIDC_ISSUER_URL="https://your-oidc-provider.com" OIDC_ISSUER_URL="https://your-oidc-provider.com"
OIDC_CLIENT_ID="your-client-id" OIDC_CLIENT_ID="your-client-id"
OIDC_REDIRECT_URI="http://localhost:3001/auth/callback"
# Session # Session
SESSION_SECRET="your-secure-session-secret-min-32-chars" SESSION_SECRET="your-secure-session-secret-min-32-chars"

View File

@@ -1,30 +1,29 @@
import { Issuer, generators, Client, TokenSet } from 'openid-client'; import { Issuer, generators, Client, TokenSet } from "openid-client";
import { config } from '../config'; import { config } from "../config";
import type { AuthenticatedUser } from '../types'; import type { AuthenticatedUser } from "../types";
let oidcClient: Client | null = null; let oidcClient: Client | null = null;
export async function initializeOIDC(): Promise<void> { export async function initializeOIDC(): Promise<void> {
try { try {
const issuer = await Issuer.discover(config.oidc.issuerUrl); const issuer = await Issuer.discover(config.oidc.issuerUrl);
oidcClient = new issuer.Client({ oidcClient = new issuer.Client({
client_id: config.oidc.clientId, client_id: config.oidc.clientId,
redirect_uris: [config.oidc.redirectUri], response_types: ["code"],
response_types: ['code'], token_endpoint_auth_method: "none", // PKCE flow - no client secret
token_endpoint_auth_method: 'none', // PKCE flow - no client secret
}); });
console.log('OIDC client initialized'); console.log("OIDC client initialized");
} catch (error) { } catch (error) {
console.error('Failed to initialize OIDC client:', error); console.error("Failed to initialize OIDC client:", error);
throw error; throw error;
} }
} }
export function getOIDCClient(): Client { export function getOIDCClient(): Client {
if (!oidcClient) { if (!oidcClient) {
throw new Error('OIDC client not initialized'); throw new Error("OIDC client not initialized");
} }
return oidcClient; return oidcClient;
} }
@@ -46,40 +45,38 @@ export function createAuthSession(): AuthSession {
export function getAuthorizationUrl(session: AuthSession): string { export function getAuthorizationUrl(session: AuthSession): string {
const client = getOIDCClient(); const client = getOIDCClient();
const codeChallenge = generators.codeChallenge(session.codeVerifier); const codeChallenge = generators.codeChallenge(session.codeVerifier);
return client.authorizationUrl({ return client.authorizationUrl({
scope: 'openid profile email', scope: "openid profile email",
state: session.state, state: session.state,
nonce: session.nonce, nonce: session.nonce,
code_challenge: codeChallenge, code_challenge: codeChallenge,
code_challenge_method: 'S256', code_challenge_method: "S256",
}); });
} }
export async function handleCallback( export async function handleCallback(
params: Record<string, string>, params: Record<string, string>,
session: AuthSession session: AuthSession,
): Promise<TokenSet> { ): Promise<TokenSet> {
const client = getOIDCClient(); const client = getOIDCClient();
const tokenSet = await client.callback( const tokenSet = await client.callback(undefined, params, {
config.oidc.redirectUri, code_verifier: session.codeVerifier,
params, state: session.state,
{ nonce: session.nonce,
code_verifier: session.codeVerifier, });
state: session.state,
nonce: session.nonce,
}
);
return tokenSet; return tokenSet;
} }
export async function getUserInfo(tokenSet: TokenSet): Promise<AuthenticatedUser> { export async function getUserInfo(
tokenSet: TokenSet,
): Promise<AuthenticatedUser> {
const client = getOIDCClient(); const client = getOIDCClient();
const claims = tokenSet.claims(); const claims = tokenSet.claims();
// Try to get more detailed userinfo if available // Try to get more detailed userinfo if available
let userInfo: Record<string, unknown> = {}; let userInfo: Record<string, unknown> = {};
try { try {
@@ -88,16 +85,21 @@ export async function getUserInfo(tokenSet: TokenSet): Promise<AuthenticatedUser
// Some providers don't support userinfo endpoint // Some providers don't support userinfo endpoint
// We'll use the claims from the ID token // We'll use the claims from the ID token
} }
const id = String(claims.sub); const id = String(claims.sub);
const username = String(userInfo.preferred_username || claims.preferred_username || claims.name || id); const username = String(
const email = String(userInfo.email || claims.email || ''); userInfo.preferred_username ||
const fullName = String(userInfo.name || claims.name || '') || null; claims.preferred_username ||
claims.name ||
id,
);
const email = String(userInfo.email || claims.email || "");
const fullName = String(userInfo.name || claims.name || "") || null;
if (!email) { if (!email) {
throw new Error('Email not provided by OIDC provider'); throw new Error("Email not provided by OIDC provider");
} }
return { return {
id, id,
username, username,
@@ -114,4 +116,4 @@ export async function verifyToken(tokenSet: TokenSet): Promise<boolean> {
} catch { } catch {
return false; return false;
} }
} }

View File

@@ -14,9 +14,6 @@ export const config = {
oidc: { oidc: {
issuerUrl: process.env.OIDC_ISSUER_URL || "", issuerUrl: process.env.OIDC_ISSUER_URL || "",
clientId: process.env.OIDC_CLIENT_ID || "", clientId: process.env.OIDC_CLIENT_ID || "",
redirectUri:
process.env.OIDC_REDIRECT_URI ||
"http://localhost:3001/api/auth/callback",
}, },
session: { session: {

View File

@@ -23,7 +23,6 @@ services:
DATABASE_URL: "postgresql://timetracker:timetracker_password@db:5432/timetracker" DATABASE_URL: "postgresql://timetracker:timetracker_password@db:5432/timetracker"
OIDC_ISSUER_URL: ${OIDC_ISSUER_URL} OIDC_ISSUER_URL: ${OIDC_ISSUER_URL}
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID} OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
OIDC_REDIRECT_URI: "${API_URL}/auth/callback"
SESSION_SECRET: ${SESSION_SECRET} SESSION_SECRET: ${SESSION_SECRET}
PORT: 3001 PORT: 3001
NODE_ENV: development NODE_ENV: development

View File

@@ -54,8 +54,6 @@ spec:
value: {{ .Values.backend.oidc.issuerUrl | quote }} value: {{ .Values.backend.oidc.issuerUrl | quote }}
- name: OIDC_CLIENT_ID - name: OIDC_CLIENT_ID
value: {{ .Values.backend.oidc.clientId | quote }} value: {{ .Values.backend.oidc.clientId | quote }}
- name: OIDC_REDIRECT_URI
value: {{ .Values.backend.oidc.redirectUri | quote }}
- name: SESSION_SECRET - name: SESSION_SECRET
value: {{ .Values.backend.session.secret | quote }} value: {{ .Values.backend.session.secret | quote }}
- name: APP_URL - name: APP_URL

View File

@@ -41,7 +41,6 @@ backend:
oidc: oidc:
issuerUrl: "" issuerUrl: ""
clientId: "" clientId: ""
redirectUri: ""
# Session configuration # Session configuration
session: session: