"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.JWT = exports.ParsedJwt = void 0;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-server-foundation/type-metadata-runtime"));
const node_crypto_1 = require("node:crypto");
const fast_jwt_1 = require("fast-jwt");
const app_1 = require("../app");
const headers_1 = require("../http/headers");
const __tsf_metadata_type = _a.createCompactMetadataRegistryV1("[1,[{\"kind\":2},{\"kind\":6},{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":14,\"type\":{\"kind\":6}}]},{\"kind\":7},{\"kind\":2,\"typeName\":\"T\"},{\"kind\":18,\"typeName\":\"Record\",\"utilityType\":\"Record\",\"typeArguments\":[{\"kind\":6},{\"kind\":2}],\"index\":{\"kind\":2},\"types\":[]}]]", []);
const JWT_ALGORITHMS = new Set([
    'none',
    'HS256',
    'HS384',
    'HS512',
    'ES256',
    'ES384',
    'ES512',
    'RS256',
    'RS384',
    'RS512',
    'PS256',
    'PS384',
    'PS512',
    'EdDSA'
]);
const REQUIRED_JWT_CLAIMS = ['iss', 'sub', 'exp', 'iat'];
const TokenErrorClasses = {
    decode: new Set([fast_jwt_1.TokenError.codes.invalidPayload, fast_jwt_1.TokenError.codes.malformed, fast_jwt_1.TokenError.codes.invalidType]),
    verify: new Set([
        fast_jwt_1.TokenError.codes.verifyError,
        fast_jwt_1.TokenError.codes.invalidSignature,
        fast_jwt_1.TokenError.codes.invalidKey,
        fast_jwt_1.TokenError.codes.missingKey,
        fast_jwt_1.TokenError.codes.keyFetchingError,
        fast_jwt_1.TokenError.codes.missingSignature
    ]),
    payload: new Set([
        fast_jwt_1.TokenError.codes.invalidAlgorithm,
        fast_jwt_1.TokenError.codes.invalidClaimType,
        fast_jwt_1.TokenError.codes.invalidClaimValue,
        fast_jwt_1.TokenError.codes.missingRequiredClaim,
        fast_jwt_1.TokenError.codes.invalidCritHeader
    ]),
    expiry: new Set([fast_jwt_1.TokenError.codes.expired, fast_jwt_1.TokenError.codes.inactive])
};
const decoder = (0, fast_jwt_1.createDecoder)();
const completeDecoder = (0, fast_jwt_1.createDecoder)({ complete: true });
let _ParsedJwtMetadata_1;
class ParsedJwt {
    isValid = true;
    id;
    issuer;
    audience;
    subject;
    issuedAtMs;
    expiresAtMs;
    payload;
    rawPayload;
    get issuedAt() {
        return new Date(this.issuedAtMs);
    }
    get expiresAt() {
        return new Date(this.expiresAtMs);
    }
    static __tsfType = _ParsedJwtMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"ParsedJwt\",\"typeName\":\"ParsedJwt\",\"classType\":{\"$tsf\":0},\"properties\":[{\"name\":\"isValid\",\"type\":{\"$tsfType\":0},\"optional\":false},{\"name\":\"id\",\"type\":{\"$tsfType\":1},\"optional\":true},{\"name\":\"issuer\",\"type\":{\"$tsfType\":1},\"optional\":false},{\"name\":\"audience\",\"type\":{\"$tsfType\":2},\"optional\":true},{\"name\":\"subject\",\"type\":{\"$tsfType\":1},\"optional\":false},{\"name\":\"issuedAtMs\",\"type\":{\"$tsfType\":3},\"optional\":false},{\"name\":\"expiresAtMs\",\"type\":{\"$tsfType\":3},\"optional\":false},{\"name\":\"payload\",\"type\":{\"$tsfType\":4},\"optional\":false},{\"name\":\"rawPayload\",\"type\":{\"$tsfType\":5},\"optional\":false}],\"methods\":[],\"hasConstructor\":false,\"constructorParameters\":[]}]", [() => ParsedJwt], __tsf_metadata_type);
}
exports.ParsedJwt = ParsedJwt;
_ParsedJwtMetadata_1.classType = ParsedJwt;
ParsedJwt.__tsfType = _ParsedJwtMetadata_1;
let _JWTMetadata_1;
class JWT {
    static async generate(options) {
        const config = (0, app_1.getAppConfig)();
        const now = Math.floor(Date.now() / 1000);
        const payload = {
            ...options.payload,
            ...(options.id ? { jti: options.id } : {}),
            iss: options.issuer ?? config.AUTH_JWT_ISSUER ?? 'app',
            aud: options.audience,
            sub: options.subject,
            iat: now,
            exp: getExpirationTs(options, config, now)
        };
        return signJwt(payload, config);
    }
    static async generateCookie(options, response, cookieOptions) {
        const token = await this.generate(options);
        appendSetCookie(response, `${getCookieName()}=${token}; ${buildCookieAttributes(cookieOptions)}`);
    }
    static async clearCookie(response, cookieOptions) {
        appendSetCookie(response, `${getCookieName()}=invalid; Expires=Thu, 01 Jan 1970 00:00:00 GMT; ${buildCookieAttributes(cookieOptions)}`);
    }
    static createVerifier(options) {
        const algorithm = resolveExplicitJwtAlgorithm(options.algorithm);
        if (options.algorithm !== undefined && algorithm === undefined) {
            return async () => ({ isValid: false, isDecodable: false });
        }
        let verifier;
        try {
            verifier = createJwtVerifier(options.key, {
                issuer: options.issuer,
                audience: options.audience,
                algorithm
            });
        }
        catch (err) {
            return async () => formatJwtError(err);
        }
        return token => verifyWithFastJwt(token, verifier, {
            issuer: options.issuer,
            audience: options.audience
        });
    }
    static async verify(token, key, options = {}) {
        const algorithm = resolveExplicitJwtAlgorithm(options.algorithm);
        if (options.algorithm !== undefined && algorithm === undefined) {
            return { isValid: false, isDecodable: false };
        }
        const config = (0, app_1.getAppConfig)();
        const issuer = options.issuer ?? config.AUTH_JWT_ISSUER;
        try {
            const verifier = createJwtVerifier(key ?? getJwtVerificationKey(config), {
                issuer,
                audience: options.audience,
                algorithm
            });
            return verifyWithFastJwt(token, verifier, { issuer, audience: options.audience });
        }
        catch (err) {
            return formatJwtError(err);
        }
    }
    static async decode(token) {
        try {
            return formatPayload(decoder(token));
        }
        catch (err) {
            return formatJwtError(err);
        }
    }
    static async process(token) {
        return (0, app_1.getAppConfig)().AUTH_JWT_ENABLE_VERIFY ? this.verify(token) : this.decode(token);
    }
    static async processWithRequest(request) {
        const authorization = (0, headers_1.getRequestHeader)(request, 'authorization');
        if (typeof authorization === 'string') {
            const match = /^bearer\s+(\S+)$/i.exec(authorization.trim());
            if (match)
                return this.process(match[1]);
        }
        const cookie = (0, headers_1.getRequestHeader)(request, 'cookie');
        if (typeof cookie === 'string') {
            const token = readCookie(cookie, getCookieName());
            if (token)
                return this.process(token);
        }
        return null;
    }
    static __tsfType = _JWTMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"JWT\",\"typeName\":\"JWT\",\"classType\":{\"$tsf\":0},\"properties\":[],\"methods\":[],\"hasConstructor\":false,\"constructorParameters\":[]}]", [() => JWT], __tsf_metadata_type);
}
exports.JWT = JWT;
_JWTMetadata_1.classType = JWT;
JWT.__tsfType = _JWTMetadata_1;
function signJwt(payload, config) {
    const algorithm = resolveSigningJwtAlgorithm(config);
    const signer = (0, fast_jwt_1.createSigner)({
        algorithm,
        key: getJwtSigningKey(config, algorithm)
    });
    return signer(payload);
}
async function verifyWithFastJwt(token, verifier, options) {
    try {
        const decoded = completeDecoder(token);
        if (!isSupportedType(decoded.header)) {
            return { isValid: false, isDecodable: true, isSignatureValid: true, isPayloadValid: false };
        }
        return validatePayload(verifier(token), options);
    }
    catch (err) {
        return formatJwtError(err);
    }
}
function createJwtVerifier(key, options) {
    return (0, fast_jwt_1.createVerifier)({
        cache: true,
        allowedIss: options.issuer,
        allowedAud: options.audience,
        algorithms: options.algorithm === undefined ? undefined : [options.algorithm],
        key: normalizeVerificationKey(key, options.algorithm),
        requiredClaims: REQUIRED_JWT_CLAIMS
    });
}
function isSupportedType(header) {
    return header.typ === undefined || header.typ === 'JWT';
}
function validatePayload(payload, options) {
    if (typeof payload.iss !== 'string' || typeof payload.sub !== 'string' || payload.sub.length === 0) {
        return { isValid: false, isDecodable: true, isSignatureValid: true, isPayloadValid: false };
    }
    if (options.issuer !== undefined && payload.iss !== options.issuer) {
        return { isValid: false, isDecodable: true, isSignatureValid: true, isPayloadValid: false };
    }
    if (!isAudienceClaim(payload.aud)) {
        return { isValid: false, isDecodable: true, isSignatureValid: true, isPayloadValid: false };
    }
    if (options.audience !== undefined && !isAudienceAccepted(payload.aud, options.audience)) {
        return { isValid: false, isDecodable: true, isSignatureValid: true, isPayloadValid: false };
    }
    const exp = payload.exp;
    const iat = payload.iat;
    if (typeof exp !== 'number' || !Number.isFinite(exp) || typeof iat !== 'number' || !Number.isFinite(iat)) {
        return { isValid: false, isDecodable: true, isSignatureValid: true, isPayloadValid: false };
    }
    if (exp <= Math.floor(Date.now() / 1000)) {
        return {
            isValid: false,
            isDecodable: true,
            isSignatureValid: true,
            isPayloadValid: true,
            isNotExpired: false
        };
    }
    return formatPayload(payload);
}
function formatPayload(payload) {
    const { jti, sub, iss, aud, iat, exp, ...extras } = payload;
    const parsed = new ParsedJwt();
    parsed.id = typeof jti === 'string' ? jti : undefined;
    parsed.subject = String(sub ?? '');
    parsed.issuer = String(iss ?? '');
    parsed.audience = Array.isArray(aud) ? aud.map(String) : aud === undefined ? undefined : String(aud);
    parsed.issuedAtMs = (typeof iat === 'number' ? iat : Number(iat ?? 0)) * 1000;
    parsed.expiresAtMs = (typeof exp === 'number' ? exp : Number(exp ?? 0)) * 1000;
    parsed.payload = extras;
    parsed.rawPayload = payload;
    return parsed;
}
function formatJwtError(err) {
    if (err instanceof fast_jwt_1.TokenError) {
        if (TokenErrorClasses.decode.has(err.code)) {
            return { isValid: false, isDecodable: false };
        }
        if (TokenErrorClasses.verify.has(err.code)) {
            return { isValid: false, isDecodable: true, isSignatureValid: false };
        }
        if (TokenErrorClasses.payload.has(err.code)) {
            return { isValid: false, isDecodable: true, isSignatureValid: true, isPayloadValid: false };
        }
        if (TokenErrorClasses.expiry.has(err.code)) {
            return { isValid: false, isDecodable: true, isSignatureValid: true, isPayloadValid: true, isNotExpired: false };
        }
    }
    throw err;
}
function resolveSigningJwtAlgorithm(config) {
    validateJwtSecretConfig(config);
    return config.AUTH_JWT_ED_SECRET ? 'EdDSA' : 'HS256';
}
function resolveExplicitJwtAlgorithm(explicit) {
    if (explicit === undefined)
        return undefined;
    return JWT_ALGORITHMS.has(explicit) ? explicit : undefined;
}
function validateJwtSecretConfig(config) {
    if (config.AUTH_JWT_SECRET && config.AUTH_JWT_SECRET_B64)
        throw new Error('AUTH_JWT_SECRET and AUTH_JWT_SECRET_B64 cannot both be configured');
    if ((config.AUTH_JWT_SECRET || config.AUTH_JWT_SECRET_B64) && config.AUTH_JWT_ED_SECRET) {
        throw new Error('AUTH_JWT_SECRET/AUTH_JWT_SECRET_B64 and AUTH_JWT_ED_SECRET cannot both be configured');
    }
}
function getJwtSigningKey(config, algorithm) {
    return algorithm === 'EdDSA' ? getJwtPrivateKey(config) : getJwtHmacSecret(config);
}
function getJwtHmacSecret(config) {
    validateJwtSecretConfig(config);
    if (config.AUTH_JWT_SECRET_B64) {
        if (!isBase64(config.AUTH_JWT_SECRET_B64))
            throw new Error('AUTH_JWT_SECRET_B64 must be valid base64');
        const secret = Buffer.from(config.AUTH_JWT_SECRET_B64, 'base64');
        if (!secret.length)
            throw new Error('AUTH_JWT_SECRET_B64 must decode to at least one byte');
        return secret;
    }
    if (config.AUTH_JWT_SECRET)
        return config.AUTH_JWT_SECRET;
    throw new Error('AUTH_JWT_SECRET/AUTH_JWT_SECRET_B64/AUTH_JWT_ED_SECRET is not configured');
}
function getJwtPrivateKey(config) {
    validateJwtSecretConfig(config);
    if (!config.AUTH_JWT_ED_SECRET)
        throw new Error('AUTH_JWT_SECRET/AUTH_JWT_SECRET_B64/AUTH_JWT_ED_SECRET is not configured');
    return normalizeEdPrivateKey(config.AUTH_JWT_ED_SECRET);
}
function getJwtVerificationKey(config) {
    return config.AUTH_JWT_ED_SECRET ? deriveEdPublicKey(getJwtPrivateKey(config)) : getJwtHmacSecret(config);
}
function normalizeVerificationKey(key, algorithm) {
    return algorithm === 'EdDSA' ? normalizeEdPublicKey(key) : key;
}
function getExpirationTs(options, config, now) {
    if (options.expiresAt instanceof Date)
        return Math.floor(options.expiresAt.getTime() / 1000);
    if (typeof options.expiresAt === 'number')
        return Math.floor(options.expiresAt / 1000);
    return now + (options.expiryMins ?? config.AUTH_JWT_EXPIRATION_MINS ?? 60) * 60;
}
function getCookieName() {
    return (0, app_1.getAppConfig)().AUTH_JWT_COOKIE_NAME ?? 'jwt';
}
function appendSetCookie(response, cookie) {
    const existing = response.getHeader('set-cookie');
    if (existing === undefined) {
        response.setHeader('set-cookie', cookie);
    }
    else if (Array.isArray(existing)) {
        response.setHeader('set-cookie', [...existing, cookie]);
    }
    else {
        response.setHeader('set-cookie', [String(existing), cookie]);
    }
}
function buildCookieAttributes(options) {
    const parts = ['Path=/', 'HttpOnly'];
    if (options?.secure !== false)
        parts.push('Secure');
    parts.push(`SameSite=${options?.sameSite ?? 'Lax'}`);
    if (options?.domain)
        parts.push(`Domain=${options.domain}`);
    return parts.join('; ');
}
function deriveEdPublicKey(privateKey) {
    const privateKeyObject = (0, node_crypto_1.createPrivateKey)(privateKey);
    const privateKeyPem = privateKeyObject.export({ type: 'pkcs8', format: 'pem' });
    return (0, node_crypto_1.createPublicKey)(privateKeyPem).export({ type: 'spki', format: 'pem' }).toString();
}
function normalizeEdPrivateKey(key) {
    if (Buffer.isBuffer(key))
        return key;
    if (key.includes('-----BEGIN'))
        return key;
    return `-----BEGIN PRIVATE KEY-----\n${key}\n-----END PRIVATE KEY-----`;
}
function normalizeEdPublicKey(key) {
    if (Buffer.isBuffer(key))
        return key;
    if (key.includes('-----BEGIN'))
        return key;
    return `-----BEGIN PUBLIC KEY-----\n${key}\n-----END PUBLIC KEY-----`;
}
function isBase64(value) {
    return /^[A-Za-z0-9+/]+={0,2}$/.test(value) && value.length % 4 !== 1;
}
function isAudienceClaim(value) {
    return value === undefined || typeof value === 'string' || (Array.isArray(value) && value.every(item => typeof item === 'string'));
}
function isAudienceAccepted(claim, expected) {
    if (claim === undefined)
        return false;
    const claimValues = Array.isArray(claim) ? claim : [claim];
    const expectedValues = Array.isArray(expected) ? expected : [expected];
    return expectedValues.some(value => claimValues.includes(value));
}
function readCookie(cookieHeader, name) {
    for (const part of cookieHeader.split(';')) {
        const [key, ...rest] = part.trim().split('=');
        if (key === name)
            return rest.join('=');
    }
}
exports.__tsfTypeAliases = _a.decodeCompactMetadataV1("[1,{\"InvalidJwtValidationResult\":{\"kind\":18,\"typeName\":\"InvalidJwtValidationResult\",\"types\":[{\"kind\":20,\"name\":\"isValid\",\"type\":{\"kind\":10,\"literal\":false},\"optional\":false},{\"kind\":20,\"name\":\"isDecodable\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"isSignatureValid\",\"type\":{\"kind\":12,\"types\":[{\"kind\":8},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"isPayloadValid\",\"type\":{\"kind\":12,\"types\":[{\"kind\":8},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"isNotExpired\",\"type\":{\"kind\":12,\"types\":[{\"kind\":8},{\"kind\":4}]},\"optional\":true}]},\"JwtCookieOptions\":{\"kind\":18,\"typeName\":\"JwtCookieOptions\",\"types\":[{\"kind\":20,\"name\":\"secure\",\"type\":{\"kind\":12,\"types\":[{\"kind\":8},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"sameSite\",\"type\":{\"kind\":12,\"types\":[{\"kind\":12,\"types\":[{\"kind\":10,\"literal\":\"Strict\"},{\"kind\":10,\"literal\":\"Lax\"},{\"kind\":10,\"literal\":\"None\"}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"domain\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true}]},\"JwtGenerationOptions\":{\"kind\":18,\"typeName\":\"JwtGenerationOptions\",\"types\":[{\"kind\":20,\"name\":\"id\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"issuer\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"audience\",\"type\":{\"kind\":12,\"types\":[{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":14,\"type\":{\"kind\":6}}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"subject\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"expiresAt\",\"type\":{\"kind\":12,\"types\":[{\"kind\":12,\"types\":[{\"kind\":16,\"typeName\":\"Date\",\"classType\":{\"$tsf\":0}},{\"kind\":7}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"expiryMins\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"payload\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"T\"},{\"kind\":4}]},\"optional\":true}],\"typeParameters\":[\"T\"]},\"JwtVerifierOptions\":{\"kind\":18,\"typeName\":\"JwtVerifierOptions\",\"types\":[{\"kind\":20,\"name\":\"issuer\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"audience\",\"type\":{\"kind\":12,\"types\":[{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":14,\"type\":{\"kind\":6}}]},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"key\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":16,\"typeName\":\"Buffer\",\"classType\":{\"$tsf\":1}}]},\"optional\":false},{\"kind\":20,\"name\":\"algorithm\",\"type\":{\"kind\":12,\"types\":[{\"kind\":12,\"types\":[{\"$tsfAlias\":[2,\"fast-jwt\",\"Algorithm\",\"Algorithm\"]},{\"kind\":6}]},{\"kind\":4}]},\"optional\":true}]}}]", [() => (typeof Date !== "undefined" ? Date : (typeof exports !== "undefined" ? exports.Date : undefined)), () => (typeof Buffer !== "undefined" ? Buffer : (typeof exports !== "undefined" ? exports.Buffer : undefined)), require], __tsf_metadata_type);
//# sourceMappingURL=jwt.js.map