File size: 2,417 Bytes
720c6ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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<string, unknown>;
  isValid: boolean;
  error?: string;
};

export async function verifyZeroTrustToken(request: IncomingMessage, runId?: string): Promise<AuthContext> {
  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<string, unknown>;
    
    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) 
    };
  }
}