/*! firebase-admin v13.10.0 */
"use strict";
/*!
 * @license
 * Copyright 2025 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.PhoneNumberTokenVerifier = void 0;
const phone_number_verification_api_1 = require("./phone-number-verification-api");
const util = require("../utils/index");
const validator = require("../utils/validator");
const jwt_1 = require("../utils/jwt");
const phone_number_verification_api_client_internal_1 = require("./phone-number-verification-api-client-internal");
class PhoneNumberTokenVerifier {
    constructor(jwksUrl, issuer, tokenInfo, app) {
        this.issuer = issuer;
        this.tokenInfo = tokenInfo;
        this.app = app;
        if (!validator.isURL(jwksUrl)) {
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'The provided public client certificate URL is an invalid URL.');
        }
        else if (!validator.isURL(issuer)) {
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'The provided JWT issuer is an invalid URL.');
        }
        else if (!validator.isNonNullObject(tokenInfo)) {
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'The provided JWT information is not an object or null.');
        }
        else if (!validator.isURL(tokenInfo.url)) {
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'The provided JWT verification documentation URL is invalid.');
        }
        else if (!validator.isNonEmptyString(tokenInfo.verifyApiName)) {
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'The JWT verify API name must be a non-empty string.');
        }
        else if (!validator.isNonEmptyString(tokenInfo.jwtName)) {
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'The JWT public full name must be a non-empty string.');
        }
        else if (!validator.isNonEmptyString(tokenInfo.shortName)) {
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'The JWT public short name must be a non-empty string.');
        }
        this.shortNameArticle = tokenInfo.shortName.charAt(0).match(/[aeiou]/i) ? 'an' : 'a';
        this.signatureVerifier = jwt_1.PublicKeySignatureVerifier.withJwksUrl(jwksUrl, app.options.httpAgent);
        // Project ID is validated in the verification call.
    }
    async verifyJWT(jwtToken) {
        if (!validator.isString(jwtToken)) {
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_TOKEN, `First argument to ${this.tokenInfo.verifyApiName} must be a string.`);
        }
        const projectId = await this.ensureProjectId();
        const decoded = await this.decodeAndVerify(jwtToken, projectId);
        const decodedIdToken = decoded.payload;
        decodedIdToken.phoneNumber = decodedIdToken.sub;
        return decodedIdToken;
    }
    async ensureProjectId() {
        const projectId = await util.findProjectId(this.app);
        if (!validator.isNonEmptyString(projectId)) {
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'Must initialize app with a cert credential or set your Firebase project ID as the ' +
                `GOOGLE_CLOUD_PROJECT environment variable to call ${this.tokenInfo.verifyApiName}.`);
        }
        return projectId;
    }
    async decodeAndVerify(token, projectId) {
        const decodedToken = await this.safeDecode(token);
        this.verifyContent(decodedToken, projectId);
        await this.verifySignature(token);
        return decodedToken;
    }
    async safeDecode(jwtToken) {
        try {
            return await (0, jwt_1.decodeJwt)(jwtToken);
        }
        catch (err) {
            if (err.code === jwt_1.JwtErrorCode.INVALID_ARGUMENT) {
                const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` +
                    `for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`;
                const errorMessage = `Decoding ${this.tokenInfo.jwtName} failed. Make sure you passed ` +
                    `the entire string JWT which represents ${this.shortNameArticle} ` +
                    `${this.tokenInfo.shortName}.` + verifyJwtTokenDocsMessage;
                throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, errorMessage);
            }
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, err.message);
        }
    }
    verifyContent(fullDecodedToken, projectId) {
        const header = fullDecodedToken && fullDecodedToken.header;
        const payload = fullDecodedToken && fullDecodedToken.payload;
        const projectIdMatchMessage = ` Make sure the ${this.tokenInfo.shortName} comes from the same ` +
            'Firebase project as the service account used to authenticate this SDK.';
        const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` +
            `for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`;
        const scopedProjectId = `${this.issuer}${projectId}`;
        let errorMessage;
        // JWT Header
        if (!header || typeof header.kid === 'undefined') {
            errorMessage = `${this.tokenInfo.jwtName} has no "kid" claim.`;
            errorMessage += verifyJwtTokenDocsMessage;
        }
        else if (header.alg !== jwt_1.ALGORITHM_ES256) {
            errorMessage = `${this.tokenInfo.jwtName} has incorrect algorithm. Expected ` +
                `"${jwt_1.ALGORITHM_ES256}" but got "${header.alg}". ${verifyJwtTokenDocsMessage}`;
        }
        else if (header.typ !== this.tokenInfo.typ) {
            errorMessage = `${this.tokenInfo.jwtName} has incorrect typ. Expected "${this.tokenInfo.typ}" but got ` +
                `"${header.typ}". ${verifyJwtTokenDocsMessage}`;
        }
        // FPNV Token
        else if (!payload) {
            errorMessage = `${this.tokenInfo.jwtName} has no payload. ${verifyJwtTokenDocsMessage}`;
        }
        else if (typeof payload.iss !== 'string' || !payload.iss.startsWith(this.issuer)) {
            errorMessage = `${this.tokenInfo.jwtName} has incorrect "iss" (issuer) claim. Expected ` +
                `an issuer starting with "${this.issuer}" but got "${payload.iss}".`
                + ` ${projectIdMatchMessage} ${verifyJwtTokenDocsMessage}`;
        }
        else if (!validator.isNonEmptyArray(payload.aud) || !payload.aud.includes(scopedProjectId)) {
            errorMessage = `${this.tokenInfo.jwtName} has incorrect "aud" (audience) claim. Expected ` +
                `"${scopedProjectId}" to be one of "${payload.aud}". ${projectIdMatchMessage} ${verifyJwtTokenDocsMessage}`;
        }
        else if (typeof payload.sub !== 'string') {
            errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim. ${verifyJwtTokenDocsMessage}`;
        }
        else if (payload.sub === '') {
            errorMessage = `${this.tokenInfo.jwtName} has an empty "sub" (subject) claim. ${verifyJwtTokenDocsMessage}`;
        }
        if (errorMessage) {
            throw new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, errorMessage);
        }
    }
    async verifySignature(jwtToken) {
        try {
            return await this.signatureVerifier.verify(jwtToken);
        }
        catch (error) {
            throw this.mapJwtErrorToAuthError(error);
        }
    }
    mapJwtErrorToAuthError(error) {
        const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` +
            `for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`;
        if (error.code === jwt_1.JwtErrorCode.TOKEN_EXPIRED) {
            const errorMessage = `${this.tokenInfo.jwtName} has expired. Get a fresh ${this.tokenInfo.shortName}` +
                ` from your client app and try again. ${verifyJwtTokenDocsMessage}`;
            return new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.EXPIRED_TOKEN, errorMessage);
        }
        else if (error.code === jwt_1.JwtErrorCode.INVALID_SIGNATURE) {
            const errorMessage = `${this.tokenInfo.jwtName} has invalid signature. ${verifyJwtTokenDocsMessage}`;
            return new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, errorMessage);
        }
        else if (error.code === jwt_1.JwtErrorCode.NO_MATCHING_KID) {
            const errorMessage = `${this.tokenInfo.jwtName} has "kid" claim which does not ` +
                `correspond to a known public key. Most likely the ${this.tokenInfo.shortName} ` +
                'is expired, so get a fresh token from your client app and try again.';
            return new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, errorMessage);
        }
        return new phone_number_verification_api_1.FirebasePhoneNumberVerificationError(phone_number_verification_api_client_internal_1.FPNV_ERROR_CODE_MAPPING.INVALID_ARGUMENT, error.message);
    }
}
exports.PhoneNumberTokenVerifier = PhoneNumberTokenVerifier;
