// Zero-Trust Authentication Middleware import { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; import { sovereignIdentityFabric } from '@/sovereign/identity/sovereign-identity-fabric.service'; import { hsmService } from '@/integration/hsm/hsm.service'; import { DbisError, ErrorCode, JwtPayload } from '@/shared/types'; import { logger } from '@/infrastructure/monitoring/logger'; import { getEnv } from '@/shared/config/env-validator'; export interface AuthenticatedRequest extends Request { sovereignBankId?: string; employeeId?: string; email?: string; name?: string; roleName?: string; permissions?: string[]; sessionType?: 'portal' | 'service'; portalSurface?: 'admin' | 'member' | 'core'; identityType?: string; apiRole?: string; } function isPortalSession(payload: JwtPayload): boolean { return payload.sessionType === 'portal' || payload.identityType === 'WEB_PORTAL'; } /** * Extract Sovereign Identity Token (SIT) from Authorization header */ export function extractSitToken(req: Request): string | null { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('SOV-TOKEN ')) { return null; } return authHeader.substring(10); } /** * Verify request signature (X-SOV-SIGNATURE) */ export async function verifyRequestSignature( req: Request, sovereignBankId: string, identityType: string ): Promise { const signature = req.headers['x-sov-signature'] as string; const timestamp = req.headers['x-sov-timestamp'] as string; const nonce = req.headers['x-sov-nonce'] as string; if (!signature || !timestamp || !nonce) { logger.warn('Missing request signature headers', { sovereignBankId, identityType, hasSignature: !!signature, hasTimestamp: !!timestamp, hasNonce: !!nonce, }); return false; } // Validate timestamp (prevent replay attacks) const requestTime = parseInt(timestamp, 10); const currentTime = Date.now(); const timeDiff = Math.abs(currentTime - requestTime); const maxTimeDiff = 5 * 60 * 1000; // 5 minutes if (isNaN(requestTime) || timeDiff > maxTimeDiff) { logger.warn('Invalid or expired request timestamp', { sovereignBankId, timestamp, timeDiff: `${timeDiff}ms`, }); return false; } // Create payload for signing (must match client-side signing) const payload = JSON.stringify({ method: req.method, path: req.path, body: req.body, timestamp, nonce, }); try { // Get sovereign identity to retrieve HSM key ID const identity = await sovereignIdentityFabric.getIdentity(sovereignBankId, identityType); if (!identity || !identity.hsmKeyId) { logger.error('Sovereign identity not found or missing HSM key', { sovereignBankId, identityType, }); return false; } // Verify signature using HSM const isValid = await hsmService.verify(payload, signature, identity.hsmKeyId); if (!isValid) { logger.warn('Invalid request signature', { sovereignBankId, identityType, keyId: identity.hsmKeyId, }); } return isValid; } catch (error) { logger.error('Error verifying request signature', { sovereignBankId, identityType, error: error instanceof Error ? error.message : 'Unknown error', }); return false; } } /** * Zero-Trust Authentication Middleware * Enforces authentication and authorization for all requests */ export async function zeroTrustAuthMiddleware( req: AuthenticatedRequest, res: Response, next: NextFunction ): Promise { try { // Extract SIT token const token = extractSitToken(req); if (!token) { throw new DbisError( ErrorCode.UNAUTHORIZED, 'Missing or invalid Sovereign Identity Token' ); } // Verify JWT token const jwtSecret = getEnv('JWT_SECRET'); if (!jwtSecret) { logger.error('JWT_SECRET environment variable is not set'); throw new DbisError( ErrorCode.INTERNAL_ERROR, 'Server configuration error: JWT secret not configured' ); } let decoded: JwtPayload; try { decoded = jwt.verify(token, jwtSecret) as JwtPayload; } catch (error) { logger.warn('JWT verification failed', { error: error instanceof Error ? error.message : 'Unknown error', }); throw new DbisError(ErrorCode.UNAUTHORIZED, 'Invalid or expired token'); } // Extract claims shared by service and portal sessions req.sovereignBankId = decoded.sovereignBankId; req.employeeId = typeof decoded.employeeId === 'string' ? decoded.employeeId : undefined; req.email = typeof decoded.email === 'string' ? decoded.email : undefined; req.name = typeof decoded.name === 'string' ? decoded.name : undefined; req.roleName = typeof decoded.roleName === 'string' ? decoded.roleName : undefined; req.permissions = Array.isArray(decoded.permissions) ? decoded.permissions.filter((permission): permission is string => typeof permission === 'string') : undefined; req.sessionType = decoded.sessionType === 'portal' ? 'portal' : 'service'; req.portalSurface = decoded.portalSurface === 'admin' || decoded.portalSurface === 'member' || decoded.portalSurface === 'core' ? decoded.portalSurface : undefined; req.identityType = decoded.identityType; req.apiRole = decoded.apiRole; if (isPortalSession(decoded)) { if (!req.employeeId && !req.email) { throw new DbisError(ErrorCode.UNAUTHORIZED, 'Invalid portal token payload'); } } else { if (!req.sovereignBankId) { throw new DbisError(ErrorCode.UNAUTHORIZED, 'Invalid token payload'); } // Verify request signature const signatureValid = await verifyRequestSignature( req, req.sovereignBankId, req.identityType || '' ); if (!signatureValid) { throw new DbisError(ErrorCode.UNAUTHORIZED, 'Invalid request signature'); } } // Check token expiration if (decoded.exp && decoded.exp < Date.now() / 1000) { throw new DbisError(ErrorCode.UNAUTHORIZED, 'Token expired'); } next(); } catch (error) { if (error instanceof DbisError) { res.status(401).json({ success: false, error: { code: error.code, message: error.message, }, timestamp: new Date(), }); } else { res.status(500).json({ success: false, error: { code: ErrorCode.INTERNAL_ERROR, message: 'Authentication error', }, timestamp: new Date(), }); } } } /** * Optional authentication middleware (for public endpoints) */ export function optionalAuthMiddleware( req: AuthenticatedRequest, res: Response, next: NextFunction ): void { const token = extractSitToken(req); if (token) { try { const jwtSecret = getEnv('JWT_SECRET'); if (jwtSecret) { const decoded = jwt.verify(token, jwtSecret) as JwtPayload; req.sovereignBankId = decoded.sovereignBankId; req.employeeId = typeof decoded.employeeId === 'string' ? decoded.employeeId : undefined; req.email = typeof decoded.email === 'string' ? decoded.email : undefined; req.name = typeof decoded.name === 'string' ? decoded.name : undefined; req.roleName = typeof decoded.roleName === 'string' ? decoded.roleName : undefined; req.permissions = Array.isArray(decoded.permissions) ? decoded.permissions.filter((permission): permission is string => typeof permission === 'string') : undefined; req.sessionType = decoded.sessionType === 'portal' ? 'portal' : 'service'; req.portalSurface = decoded.portalSurface === 'admin' || decoded.portalSurface === 'member' || decoded.portalSurface === 'core' ? decoded.portalSurface : undefined; req.identityType = decoded.identityType; req.apiRole = decoded.apiRole; } } catch (error) { // Ignore auth errors for optional auth logger.debug('Optional auth failed (ignored)', { error: error instanceof Error ? error.message : 'Unknown error', }); } } next(); }