import { SignJWT, jwtVerify, type JWTPayload } from 'jose'; import { serialize, parse } from 'cookie'; import { config } from 'dotenv'; // Load environment variables config(); // Environment variables - use runtime access for server-side function getEnv(key: string): string { const value = process.env[key]; if (!value) { throw new Error(`Missing environment variable: ${key}`); } return value; } const SECRET_KEY = new TextEncoder().encode(getEnv('OIDC_CLIENT_SECRET')); const SESSION_DURATION = 6 * 60 * 60; // 6 hours in seconds export interface SessionData { userId: string; authenticated: boolean; exp: number; } // Create a signed JWT session token export async function createSession(userId: string): Promise { const exp = Math.floor(Date.now() / 1000) + SESSION_DURATION; return await new SignJWT({ userId, authenticated: true, exp }) .setProtectedHeader({ alg: 'HS256' }) .setExpirationTime(exp) .sign(SECRET_KEY); } // Verify and decode a session token export async function verifySession(token: string): Promise { try { const { payload } = await jwtVerify(token, SECRET_KEY); // Validate payload structure and cast properly if ( typeof payload.userId === 'string' && typeof payload.authenticated === 'boolean' && typeof payload.exp === 'number' ) { return { userId: payload.userId, authenticated: payload.authenticated, exp: payload.exp }; } return null; } catch (error) { console.log('Session verification failed:', error); return null; } } // Get session from request cookies export function getSessionFromRequest(request: Request): string | null { const cookieHeader = request.headers.get('cookie'); if (!cookieHeader) return null; const cookies = parse(cookieHeader); return cookies.session || null; } // Create session cookie export function createSessionCookie(token: string): string { const publicBaseUrl = getEnv('PUBLIC_BASE_URL'); const isSecure = publicBaseUrl.startsWith('https://'); return serialize('session', token, { httpOnly: true, secure: isSecure, sameSite: 'lax', maxAge: SESSION_DURATION, path: '/' }); } // Clear session cookie export function clearSessionCookie(): string { const publicBaseUrl = getEnv('PUBLIC_BASE_URL'); const isSecure = publicBaseUrl.startsWith('https://'); return serialize('session', '', { httpOnly: true, secure: isSecure, sameSite: 'lax', maxAge: 0, path: '/' }); } // Generate OIDC authorization URL export function generateAuthUrl(state: string): string { const oidcEndpoint = getEnv('OIDC_ENDPOINT'); const clientId = getEnv('OIDC_CLIENT_ID'); const publicBaseUrl = getEnv('PUBLIC_BASE_URL'); const params = new URLSearchParams({ response_type: 'code', client_id: clientId, redirect_uri: `${publicBaseUrl}/auth/callback`, scope: 'openid profile email', state: state }); return `${oidcEndpoint}/apps/oidc/authorize?${params.toString()}`; } // Exchange authorization code for tokens export async function exchangeCodeForTokens(code: string): Promise { const oidcEndpoint = getEnv('OIDC_ENDPOINT'); const clientId = getEnv('OIDC_CLIENT_ID'); const clientSecret = getEnv('OIDC_CLIENT_SECRET'); const publicBaseUrl = getEnv('PUBLIC_BASE_URL'); const response = await fetch(`${oidcEndpoint}/apps/oidc/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Basic ${btoa(`${clientId}:${clientSecret}`)}` }, body: new URLSearchParams({ grant_type: 'authorization_code', code: code, redirect_uri: `${publicBaseUrl}/auth/callback` }) }); if (!response.ok) { const error = await response.text(); console.error('Token exchange failed:', error); throw new Error('Failed to exchange authorization code'); } return await response.json(); } // Get user info from OIDC provider export async function getUserInfo(accessToken: string): Promise { const oidcEndpoint = getEnv('OIDC_ENDPOINT'); const response = await fetch(`${oidcEndpoint}/apps/oidc/userinfo`, { headers: { 'Authorization': `Bearer ${accessToken}` } }); if (!response.ok) { const error = await response.text(); console.error('Userinfo request failed:', error); throw new Error('Failed to get user info'); } return await response.json(); } // Generate random state for CSRF protection export function generateState(): string { return crypto.randomUUID(); } // Log authentication events for debugging export function logAuthEvent(event: string, details?: any) { const timestamp = new Date().toISOString(); console.log(`[AUTH ${timestamp}] ${event}`, details ? JSON.stringify(details) : ''); }