/**
 * JWT Manager
 * JSON Web Token generation, verification, and claims management
 * Uses native Node.js crypto module (zero external dependencies)
 */
import { securityConfig } from '../config/security.js';
import { ErrorFactory } from '../exceptions/ZintrustError.js';
import { createHmac, createSign, createVerify, randomBytes } from '../node-singletons/crypto.js';
import { JwtSessions } from './JwtSessions.js';
const createJwt = () => {
    const algorithm = securityConfig.jwt.algorithm;
    const secret = securityConfig.jwt.secret;
    const jwt = JwtManager.create();
    if (algorithm === 'HS256' || algorithm === 'HS512') {
        jwt.setHmacSecret(secret);
    }
    return jwt;
};
const logout = async (authHeader) => {
    await JwtSessions.logout(authHeader);
};
const logoutAll = async (sub) => {
    await JwtSessions.logoutAll(sub);
};
const signAccessToken = async (payload, expiresIn) => {
    const algorithm = securityConfig.jwt.algorithm;
    const jwt = createJwt();
    let token;
    // JwtManager currently supports HMAC secrets directly for HS algorithms.
    // For other algorithms, verify will still reject mismatched tokens.
    if (algorithm !== 'HS256' && algorithm !== 'HS512') {
        token = jwt.sign(payload, {
            algorithm,
            issuer: securityConfig.jwt.issuer,
            audience: securityConfig.jwt.audience,
            jwtId: jwt.generateJwtId(),
        });
    }
    else {
        token = jwt.sign(payload, {
            algorithm,
            expiresIn: expiresIn ?? securityConfig.jwt.expiresIn,
            issuer: securityConfig.jwt.issuer,
            audience: securityConfig.jwt.audience,
            subject: typeof payload.sub === 'string' ? payload.sub : undefined,
            jwtId: jwt.generateJwtId(),
        });
    }
    await JwtSessions.register(token);
    return token;
};
/**
 * Create a new JWT manager instance
 */
const create = () => {
    const state = {
        hmacSecret: null,
        rsaPrivateKey: null,
        rsaPublicKey: null,
    };
    return {
        setHmacSecret(secret) {
            state.hmacSecret = secret;
        },
        setRsaKeys(privateKey, publicKey) {
            state.rsaPrivateKey = privateKey;
            state.rsaPublicKey = publicKey;
        },
        sign(payload, options = {}) {
            return signToken(state, payload, options);
        },
        verify(token, algorithm = 'HS256') {
            return verifyToken(state, token, algorithm);
        },
        decode(token) {
            return decodeToken(token);
        },
        signRsa(message) {
            return signRsa(message, state.rsaPrivateKey);
        },
        generateJwtId() {
            return randomBytes(16).toString('hex');
        },
    };
};
/**
 * JwtManager namespace - sealed for immutability
 */
export const JwtManager = Object.freeze({
    create,
    signAccessToken,
    logout,
    logoutAll,
});
/**
 * Sign JWT token
 */
function signToken(state, payload, options) {
    const algorithm = options.algorithm ?? 'HS256';
    const now = Math.floor(Date.now() / 1000);
    const claims = buildClaims(payload, options, now);
    const header = { alg: algorithm, typ: 'JWT' };
    const encodedHeader = base64Encode(JSON.stringify(header));
    const encodedPayload = base64Encode(JSON.stringify(claims));
    const message = `${encodedHeader}.${encodedPayload}`;
    const signature = generateSignature(message, algorithm, state.hmacSecret, state.rsaPrivateKey);
    return `${message}.${signature}`;
}
/**
 * Verify JWT token
 */
function verifyToken(state, token, algorithm) {
    const parts = token.split('.');
    if (parts.length !== 3) {
        throw ErrorFactory.createSecurityError('Invalid token format');
    }
    const [encodedHeader, encodedPayload, encodedSignature] = parts;
    try {
        const header = JSON.parse(base64Decode(encodedHeader));
        if (header['alg'] !== algorithm) {
            throw ErrorFactory.createSecurityError(`Algorithm mismatch: expected ${algorithm}, got ${header['alg']}`);
        }
        const message = `${encodedHeader}.${encodedPayload}`;
        const isValid = verifySignature(message, encodedSignature, algorithm, state.hmacSecret, state.rsaPublicKey);
        if (!isValid) {
            throw ErrorFactory.createSecurityError('Invalid signature');
        }
        const payload = JSON.parse(base64Decode(encodedPayload));
        verifyClaims(payload);
        return payload;
    }
    catch (error) {
        throw ErrorFactory.createSecurityError(`Token verification failed: ${error.message}`);
    }
}
/**
 * Decode JWT token without verification
 */
function decodeToken(token) {
    const parts = token.split('.');
    if (parts.length !== 3) {
        throw ErrorFactory.createSecurityError('Invalid token format');
    }
    try {
        const payload = JSON.parse(base64Decode(parts[1]));
        return payload;
    }
    catch (error) {
        throw ErrorFactory.createSecurityError(`Invalid token payload: ${error.message}`);
    }
}
/**
 * Base64 URL encoding
 */
function base64Encode(data) {
    const buffer = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
    return buffer.toString('base64').replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
}
/**
 * Base64 URL decoding to Buffer
 */
function base64DecodeBuffer(data) {
    const padded = data + '==='.slice((data.length + 3) % 4);
    const base64 = padded.replaceAll('-', '+').replaceAll('_', '/');
    return Buffer.from(base64, 'base64');
}
/**
 * Base64 URL decoding to string
 */
function base64Decode(data) {
    return base64DecodeBuffer(data).toString('utf8');
}
/**
 * Timing safe string comparison
 */
function timingSafeEquals(a, b) {
    if (a.length !== b.length) {
        return false;
    }
    let result = 0;
    for (let i = 0; i < a.length; i++) {
        result |= (a.codePointAt(i) ?? 0) ^ (b.codePointAt(i) ?? 0);
    }
    return result === 0;
}
/**
 * Sign message using HMAC
 */
function signHmac(message, algorithm, secret) {
    if (secret === null) {
        throw ErrorFactory.createSecurityError('HMAC secret not configured');
    }
    const digestAlgorithm = algorithm === 'HS256' ? 'sha256' : 'sha512';
    const signature = createHmac(digestAlgorithm, secret).update(message).digest();
    return base64Encode(signature);
}
/**
 * Sign message using RSA
 */
function signRsa(message, privateKey) {
    if (privateKey === null) {
        throw ErrorFactory.createSecurityError('RSA private key not configured');
    }
    const sign = createSign('RSA-SHA256');
    sign.update(message);
    const signature = sign.sign(privateKey);
    return base64Encode(signature);
}
/**
 * Generate signature based on algorithm
 */
function generateSignature(message, algorithm, hmacSecret, rsaPrivateKey) {
    if (algorithm.startsWith('HS')) {
        return signHmac(message, algorithm, hmacSecret);
    }
    if (algorithm === 'RS256') {
        return signRsa(message, rsaPrivateKey);
    }
    throw ErrorFactory.createSecurityError(`Unsupported algorithm: ${algorithm}`);
}
/**
 * Verify signature based on algorithm
 */
function verifySignature(message, encodedSignature, algorithm, hmacSecret, rsaPublicKey) {
    if (algorithm.startsWith('HS')) {
        const expectedSignature = signHmac(message, algorithm, hmacSecret);
        return timingSafeEquals(encodedSignature, expectedSignature);
    }
    else if (algorithm === 'RS256') {
        if (rsaPublicKey === null) {
            throw ErrorFactory.createSecurityError('RSA public key not configured');
        }
        const verify = createVerify('RSA-SHA256'); // NOSONAR LCHECK
        verify.update(message);
        const signature = base64DecodeBuffer(encodedSignature);
        return verify.verify(rsaPublicKey, signature);
    }
    return false;
}
/**
 * Build JWT claims
 */
function buildClaims(payload, options, now) {
    const claims = {
        ...payload,
        iat: now,
    };
    if (options.expiresIn !== undefined && options.expiresIn !== null) {
        claims.exp = now + options.expiresIn;
    }
    if (options.issuer !== undefined && options.issuer !== null) {
        claims.iss = options.issuer;
    }
    if (options.audience !== undefined && options.audience !== null) {
        claims.aud = options.audience;
    }
    if (options.subject !== undefined && options.subject !== null) {
        claims.sub = options.subject;
    }
    if (options.jwtId !== undefined && options.jwtId !== null) {
        claims.jti = options.jwtId;
    }
    return claims;
}
/**
 * Verify JWT claims (expiration, not before)
 */
function verifyClaims(payload) {
    const now = Math.floor(Date.now() / 1000);
    if (payload.exp !== undefined && payload.exp !== null && payload.exp <= now) {
        throw ErrorFactory.createSecurityError('Token expired');
    }
    if (payload.nbf !== undefined && payload.nbf !== null && payload.nbf > now) {
        throw ErrorFactory.createSecurityError('Token not yet valid');
    }
}
