import { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; import jwksClient from 'jwks-rsa'; const OIDC_ISSUER = process.env.OIDC_ISSUER || 'http://localhost:9000/application/o/kaboot/'; const OIDC_JWKS_URI = process.env.OIDC_JWKS_URI || 'http://localhost:9000/application/o/kaboot/jwks/'; const OIDC_INTERNAL_JWKS_URI = process.env.OIDC_INTERNAL_JWKS_URI || OIDC_JWKS_URI; const client = jwksClient({ jwksUri: OIDC_INTERNAL_JWKS_URI, cache: true, cacheMaxAge: 600000, rateLimit: true, jwksRequestsPerMinute: 10, }); function getSigningKey(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback): void { if (!header.kid) { callback(new Error('No kid in token header')); return; } client.getSigningKey(header.kid, (err, key) => { if (err) { callback(err); return; } const signingKey = key?.getPublicKey(); callback(null, signingKey); }); } export interface AuthenticatedUser { sub: string; preferred_username: string; email?: string; name?: string; } export interface AuthenticatedRequest extends Request { user?: AuthenticatedUser; } export function requireAuth( req: AuthenticatedRequest, res: Response, next: NextFunction ): void { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { res.status(401).json({ error: 'Missing or invalid authorization header' }); return; } const token = authHeader.slice(7); jwt.verify( token, getSigningKey, { issuer: OIDC_ISSUER, algorithms: ['RS256'], }, (err, decoded) => { if (err) { console.error('Token verification failed:', err.message); res.status(401).json({ error: 'Invalid token', details: err.message }); return; } const payload = decoded as jwt.JwtPayload; req.user = { sub: payload.sub!, preferred_username: payload.preferred_username || payload.sub!, email: payload.email, name: payload.name, }; next(); } ); }