// engine/auth/token-validator.ts — Validate JWT/opaque tokens (SPEC-161)
// Supports: RFC 7662 token introspection (opaque tokens) and JWKS (JWT).
// Never logs token values.
const BEARER_PREFIX = 'Bearer ';
/**
 * Extract the raw token from an Authorization header value.
 * Returns null if the header is missing or not a Bearer token.
 */
export function extractBearerToken(authorizationHeader) {
    if (!authorizationHeader) {
        return null;
    }
    if (!authorizationHeader.startsWith(BEARER_PREFIX)) {
        return null;
    }
    const token = authorizationHeader.slice(BEARER_PREFIX.length).trim();
    return token.length > 0 ? token : null;
}
/**
 * Validate an access token against the configured OAuth provider.
 * Prefers JWKS validation (offline) when jwksUrl is configured,
 * falls back to introspection endpoint (online) when introspectionUrl is set.
 *
 * IMPORTANT: The token string is NEVER logged or included in error messages.
 */
export async function validateToken(token, config) {
    if (config.jwksUrl) {
        return validateJwtWithJwks(token, config.jwksUrl);
    }
    if (config.introspectionUrl) {
        return validateViaIntrospection(token, config.introspectionUrl, config.clientId);
    }
    return {
        valid: false,
        scopes: [],
        reason: 'No validation endpoint configured (set jwksUrl or introspectionUrl in auth config)',
    };
}
/**
 * Reject JWKS-based JWT validation with a clear explanation.
 *
 * WHY this always returns invalid: verifying a JWT signature requires
 * fetching the JWKS endpoint and validating against the provider's public key.
 * That requires a crypto library (e.g. jose, jsonwebtoken) which cannot be
 * added as a runtime dependency (project constraint: only MCP SDK, glob, zod).
 * Accepting a JWT after only decoding the payload — without signature
 * verification — would be a critical security vulnerability: any attacker
 * could forge arbitrary claims by crafting their own base64-encoded payload.
 *
 * Use introspectionUrl for opaque tokens (RFC 7662), or configure a token
 * validation webhook that performs signature verification externally.
 */
function validateJwtWithJwks(_token, _jwksUrl) {
    return {
        valid: false,
        scopes: [],
        reason: 'JWT signature verification requires a JWKS client library. ' +
            'Configure a token validation webhook or use API key authentication instead.',
    };
}
/**
 * Validate a token via RFC 7662 token introspection endpoint.
 * Sends the token to the provider's introspection URL and checks active=true.
 */
async function validateViaIntrospection(token, introspectionUrl, clientId) {
    let response;
    try {
        response = await fetch(introspectionUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                Accept: 'application/json',
            },
            body: new URLSearchParams({ token, client_id: clientId }).toString(),
            signal: AbortSignal.timeout(10_000),
        });
    }
    catch {
        return { valid: false, scopes: [], reason: 'Introspection endpoint unreachable' };
    }
    if (!response.ok) {
        return {
            valid: false,
            scopes: [],
            reason: `Introspection endpoint returned HTTP ${String(response.status)}`,
        };
    }
    let data;
    try {
        data = (await response.json());
    }
    catch {
        return { valid: false, scopes: [], reason: 'Introspection response is not valid JSON' };
    }
    if (!data.active) {
        return { valid: false, scopes: [], reason: 'Token is inactive or revoked' };
    }
    const scopes = extractScopesFromPayload(data);
    const subject = typeof data.sub === 'string' ? data.sub : undefined;
    const expiresAt = typeof data.exp === 'number' ? data.exp : undefined;
    return { valid: true, subject, scopes, expiresAt };
}
/**
 * Extract OAuth scopes from a token payload object.
 * Handles both "scope" (space-separated string) and "scp" (array) claim formats.
 */
function extractScopesFromPayload(payload) {
    // RFC 8693 — "scope" as space-separated string
    if (typeof payload.scope === 'string') {
        return payload.scope.split(' ').filter(Boolean);
    }
    // Common alternative — "scp" as array
    if (Array.isArray(payload.scp)) {
        return payload.scp.filter((s) => typeof s === 'string');
    }
    // Some providers use "scope" as an array
    if (Array.isArray(payload.scope)) {
        return payload.scope.filter((s) => typeof s === 'string');
    }
    return [];
}
//# sourceMappingURL=token-validator.js.map