import { verify } from 'jsonwebtoken'; import { resolveTemplateAgentConfig } from '../../metadata.js'; import { createTemplateLogger } from './SentinelLogger.js'; import type { IncomingMessage } from 'node:http'; // We fetch the public key (or use a shared secret if configured) from SIN-Authenticator's OIDC jwks endpoint or env const OIDC_PUBLIC_KEY = process.env.SIN_OIDC_PUBLIC_KEY || process.env.SIN_SHARED_SECRET || ''; export type AuthContext = { subject: string; roles: string[]; claims: Record; isValid: boolean; error?: string; }; export async function verifyZeroTrustToken(request: IncomingMessage, runId?: string): Promise { const logger = createTemplateLogger(runId); const authHeader = request.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { await logger.failed('auth.zero_trust', 'auth.token.missing', new Error('Missing or malformed Authorization header')); return { subject: 'anonymous', roles: [], claims: {}, isValid: false, error: 'missing_token' }; } const token = authHeader.split(' ')[1]; if (!OIDC_PUBLIC_KEY) { // If the fleet runs in a mode without strict OIDC enforcement configured yet, // we log a warning but might fail closed depending on fleet policy. // For Epic #195, we want to enforce zero-trust strictly when keys are present. await logger.log({ surface: 'auth.zero_trust', event: 'auth.keys.missing', level: 'warn', status: 'failed', details: { note: 'No SIN_OIDC_PUBLIC_KEY or SIN_SHARED_SECRET configured for verification.' } }); return { subject: 'anonymous', roles: [], claims: {}, isValid: false, error: 'missing_keys' }; } try { const decoded = verify(token, OIDC_PUBLIC_KEY, { algorithms: ['RS256', 'HS256'] }) as Record; await logger.succeeded('auth.zero_trust', 'auth.token.verified', { subject: decoded.sub }); return { subject: String(decoded.sub || 'unknown'), roles: Array.isArray(decoded.roles) ? decoded.roles.map(String) : [], claims: decoded, isValid: true }; } catch (error) { await logger.failed('auth.zero_trust', 'auth.token.rejected', error); return { subject: 'anonymous', roles: [], claims: {}, isValid: false, error: error instanceof Error ? error.message : String(error) }; } }