import { createLocalJWKSet, createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from 'jose';
import { isEmpty } from 'lodash';
import { UnauthorizedError } from '../errors/index.js';
/**
 * Verifies JWTs using a JWKS endpoint to resolve signing keys on demand.
 */
export class JwtVerifier {
    options;
    // Memoize the remote JWKS loader; without this, we'd recreate it on each verification and jose's own cache would never persist.
    remoteJwkSet;
    constructor(options) {
        this.options = options;
    }
    async verify(token, options) {
        let kid;
        try {
            const header = decodeProtectedHeader(token);
            kid = header.kid;
        }
        catch {
            // Preserve legacy error message relied upon by tests
            throw new Error('Invalid token specified');
        }
        if (isEmpty(kid)) {
            throw new UnauthorizedError('Invalid JWT as kid header is missing.');
        }
        const jwkSet = await this.resolveJwkSet();
        try {
            const result = await jwtVerify(token, jwkSet, options);
            return result.payload;
        }
        catch (err) {
            throw new UnauthorizedError(`JWT verification failed: ${err.message}`);
        }
    }
    async resolveJwkSet() {
        // Prefer interceptor-provided keys for explicit testability and control
        if (this.options.getKeysInterceptor) {
            const keys = await this.options.getKeysInterceptor();
            if (isEmpty(keys)) {
                throw new UnauthorizedError('JWT could not be verified as no corresponding public key was found.');
            }
            return createLocalJWKSet({ keys });
        }
        // Allow custom fetcher to provide JWKS out-of-band
        if (this.options.fetcher) {
            const jwks = await this.options.fetcher(this.options.jwksUri);
            const keys = jwks.keys;
            if (!Array.isArray(keys) || isEmpty(keys)) {
                throw new UnauthorizedError('JWT could not be verified as no corresponding public key was found.');
            }
            return createLocalJWKSet({ keys });
        }
        // Default to remote JWKS. jose v5 supports headers/timeout; pass when provided.
        const url = new URL(this.options.jwksUri);
        const opts = {};
        if (!isEmpty(this.options.requestHeaders)) {
            opts.headers = this.options.requestHeaders;
        }
        if (!isEmpty(this.options.timeoutMs)) {
            opts.timeoutDuration = this.options.timeoutMs;
        }
        if (!this.remoteJwkSet) {
            this.remoteJwkSet = createRemoteJWKSet(url, opts);
        }
        return this.remoteJwkSet;
    }
}
//# sourceMappingURL=verifier.js.map