3 Commits

Author SHA1 Message Date
859420c5d6 fix 2026-02-18 20:15:11 +01:00
8b45fffd6e update 2026-02-18 20:09:38 +01:00
01502122b2 Revert "update"
This reverts commit 5c86afd640.
2026-02-18 20:05:32 +01:00
7 changed files with 46 additions and 41 deletions

View File

@@ -78,6 +78,7 @@ 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,6 +1,6 @@
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;
@@ -10,20 +10,21 @@ export async function initializeOIDC(): Promise<void> {
oidcClient = new issuer.Client({ oidcClient = new issuer.Client({
client_id: config.oidc.clientId, client_id: config.oidc.clientId,
response_types: ["code"], redirect_uris: [config.oidc.redirectUri],
token_endpoint_auth_method: "none", // PKCE flow - no client secret response_types: ['code'],
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;
} }
@@ -47,32 +48,34 @@ export function getAuthorizationUrl(session: AuthSession): string {
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(undefined, params, { const tokenSet = await client.callback(
config.oidc.redirectUri,
params,
{
code_verifier: session.codeVerifier, code_verifier: session.codeVerifier,
state: session.state, state: session.state,
nonce: session.nonce, nonce: session.nonce,
}); }
);
return tokenSet; return tokenSet;
} }
export async function getUserInfo( export async function getUserInfo(tokenSet: TokenSet): Promise<AuthenticatedUser> {
tokenSet: TokenSet,
): Promise<AuthenticatedUser> {
const client = getOIDCClient(); const client = getOIDCClient();
const claims = tokenSet.claims(); const claims = tokenSet.claims();
@@ -87,17 +90,12 @@ export async function getUserInfo(
} }
const id = String(claims.sub); const id = String(claims.sub);
const username = String( const username = String(userInfo.preferred_username || claims.preferred_username || claims.name || id);
userInfo.preferred_username || const email = String(userInfo.email || claims.email || '');
claims.preferred_username || const fullName = String(userInfo.name || claims.name || '') || null;
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 {

View File

@@ -14,6 +14,9 @@ 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

@@ -42,7 +42,7 @@ async function main() {
saveUninitialized: false, saveUninitialized: false,
name: "sessionId", name: "sessionId",
cookie: { cookie: {
secure: config.nodeEnv === "production", secure: false,
httpOnly: true, httpOnly: true,
maxAge: config.session.maxAge, maxAge: config.session.maxAge,
sameSite: "lax", sameSite: "lax",

View File

@@ -23,6 +23,7 @@ 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

@@ -2,6 +2,6 @@ apiVersion: v2
name: timetracker name: timetracker
description: A Helm chart for the TimeTracker application description: A Helm chart for the TimeTracker application
type: application type: application
version: 1.0.4 version: 1.0.5
appVersion: "1.0.0" appVersion: "1.0.0"
dependencies: [] dependencies: []

View File

@@ -54,6 +54,8 @@ 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: {{ (index .Values.ingress.hosts 0).host | printf "https://%s/api/auth/callback" | 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