/*! firebase-admin v14.0.0 */
"use strict";
/*!
 * @license
 * Copyright 2017 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.UserRecord = exports.UserInfo = exports.UserMetadata = exports.MultiFactorSettings = exports.TotpMultiFactorInfo = exports.TotpInfo = exports.PhoneMultiFactorInfo = exports.MultiFactorInfo = void 0;
const deep_copy_1 = require("../utils/deep-copy");
const validator_1 = require("../utils/validator");
const utils = require("../utils");
const error_1 = require("./error");
/**
 * 'REDACTED', encoded as a base64 string.
 */
const B64_REDACTED = Buffer.from('REDACTED').toString('base64');
/**
 * Parses a time stamp string or number and returns the corresponding date if valid.
 *
 * @param time - The unix timestamp string or number in milliseconds.
 * @returns The corresponding date as a UTC string, if valid. Otherwise, null.
 */
function parseDate(time) {
    try {
        const date = new Date(parseInt(time, 10));
        if (!isNaN(date.getTime())) {
            return date.toUTCString();
        }
    }
    catch (e) {
        // Do nothing. null will be returned.
    }
    return null;
}
var MultiFactorId;
(function (MultiFactorId) {
    MultiFactorId["Phone"] = "phone";
    MultiFactorId["Totp"] = "totp";
})(MultiFactorId || (MultiFactorId = {}));
/**
 * Interface representing the common properties of a user-enrolled second factor.
 */
class MultiFactorInfo {
    /**
     * Initializes the MultiFactorInfo associated subclass using the server side.
     * If no MultiFactorInfo is associated with the response, null is returned.
     *
     * @param response - The server side response.
     * @internal
     */
    static initMultiFactorInfo(response) {
        let multiFactorInfo = null;
        // PhoneMultiFactorInfo, TotpMultiFactorInfo currently available.
        try {
            if (response.phoneInfo !== undefined) {
                multiFactorInfo = new PhoneMultiFactorInfo(response);
            }
            else if (response.totpInfo !== undefined) {
                multiFactorInfo = new TotpMultiFactorInfo(response);
            }
            else {
                // Ignore the other SDK unsupported MFA factors to prevent blocking developers using the current SDK.
            }
        }
        catch (e) {
            // Ignore error.
        }
        return multiFactorInfo;
    }
    /**
     * Initializes the MultiFactorInfo object using the server side response.
     *
     * @param response - The server side response.
     * @constructor
     * @internal
     */
    constructor(response) {
        this.initFromServerResponse(response);
    }
    /**
     * Returns a JSON-serializable representation of this object.
     *
     * @returns A JSON-serializable representation of this object.
     */
    toJSON() {
        return {
            uid: this.uid,
            displayName: this.displayName,
            factorId: this.factorId,
            enrollmentTime: this.enrollmentTime,
        };
    }
    /**
     * Initializes the MultiFactorInfo object using the provided server response.
     *
     * @param response - The server side response.
     */
    initFromServerResponse(response) {
        const factorId = response && this.getFactorId(response);
        if (!factorId || !response || !response.mfaEnrollmentId) {
            throw new error_1.FirebaseAuthError(error_1.authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid multi-factor info response');
        }
        utils.addReadonlyGetter(this, 'uid', response.mfaEnrollmentId);
        utils.addReadonlyGetter(this, 'factorId', factorId);
        utils.addReadonlyGetter(this, 'displayName', response.displayName);
        // Encoded using [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format.
        // For example, "2017-01-15T01:30:15.01Z".
        // This can be parsed directly via Date constructor.
        // This can be computed using Data.prototype.toISOString.
        if (response.enrolledAt) {
            utils.addReadonlyGetter(this, 'enrollmentTime', new Date(response.enrolledAt).toUTCString());
        }
        else {
            utils.addReadonlyGetter(this, 'enrollmentTime', null);
        }
    }
}
exports.MultiFactorInfo = MultiFactorInfo;
/**
 * Interface representing a phone specific user-enrolled second factor.
 */
class PhoneMultiFactorInfo extends MultiFactorInfo {
    /**
     * Initializes the PhoneMultiFactorInfo object using the server side response.
     *
     * @param response - The server side response.
     * @constructor
     * @internal
     */
    constructor(response) {
        super(response);
        utils.addReadonlyGetter(this, 'phoneNumber', response.phoneInfo);
    }
    /**
     * {@inheritdoc MultiFactorInfo.toJSON}
     */
    toJSON() {
        return Object.assign(super.toJSON(), {
            phoneNumber: this.phoneNumber,
        });
    }
    /**
     * Returns the factor ID based on the response provided.
     *
     * @param response - The server side response.
     * @returns The multi-factor ID associated with the provided response. If the response is
     *     not associated with any known multi-factor ID, null is returned.
     *
     * @internal
     */
    getFactorId(response) {
        return (response && response.phoneInfo) ? MultiFactorId.Phone : null;
    }
}
exports.PhoneMultiFactorInfo = PhoneMultiFactorInfo;
/**
 * `TotpInfo` struct associated with a second factor
 */
class TotpInfo {
}
exports.TotpInfo = TotpInfo;
/**
 * Interface representing a TOTP specific user-enrolled second factor.
 */
class TotpMultiFactorInfo extends MultiFactorInfo {
    /**
     * Initializes the `TotpMultiFactorInfo` object using the server side response.
     *
     * @param response - The server side response.
     * @constructor
     * @internal
     */
    constructor(response) {
        super(response);
        utils.addReadonlyGetter(this, 'totpInfo', response.totpInfo);
    }
    /**
     * {@inheritdoc MultiFactorInfo.toJSON}
     */
    toJSON() {
        return Object.assign(super.toJSON(), {
            totpInfo: this.totpInfo,
        });
    }
    /**
     * Returns the factor ID based on the response provided.
     *
     * @param response - The server side response.
     * @returns The multi-factor ID associated with the provided response. If the response is
     *     not associated with any known multi-factor ID, `null` is returned.
     *
     * @internal
     */
    getFactorId(response) {
        return (response && response.totpInfo) ? MultiFactorId.Totp : null;
    }
}
exports.TotpMultiFactorInfo = TotpMultiFactorInfo;
/**
 * The multi-factor related user settings.
 */
class MultiFactorSettings {
    /**
     * Initializes the `MultiFactor` object using the server side or JWT format response.
     *
     * @param response - The server side response.
     * @constructor
     * @internal
     */
    constructor(response) {
        const parsedEnrolledFactors = [];
        if (!(0, validator_1.isNonNullObject)(response)) {
            throw new error_1.FirebaseAuthError(error_1.authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid multi-factor response');
        }
        else if (response.mfaInfo) {
            response.mfaInfo.forEach((factorResponse) => {
                const multiFactorInfo = MultiFactorInfo.initMultiFactorInfo(factorResponse);
                if (multiFactorInfo) {
                    parsedEnrolledFactors.push(multiFactorInfo);
                }
            });
        }
        // Make enrolled factors immutable.
        utils.addReadonlyGetter(this, 'enrolledFactors', Object.freeze(parsedEnrolledFactors));
    }
    /**
     * Returns a JSON-serializable representation of this multi-factor object.
     *
     * @returns A JSON-serializable representation of this multi-factor object.
     */
    toJSON() {
        return {
            enrolledFactors: this.enrolledFactors.map((info) => info.toJSON()),
        };
    }
}
exports.MultiFactorSettings = MultiFactorSettings;
/**
 * Represents a user's metadata.
 */
class UserMetadata {
    /**
     * @param response - The server side response returned from the `getAccountInfo`
     *     endpoint.
     * @constructor
     * @internal
     */
    constructor(response) {
        // Creation date should always be available but due to some backend bugs there
        // were cases in the past where users did not have creation date properly set.
        // This included legacy Firebase migrating project users and some anonymous users.
        // These bugs have already been addressed since then.
        utils.addReadonlyGetter(this, 'creationTime', parseDate(response.createdAt));
        utils.addReadonlyGetter(this, 'lastSignInTime', parseDate(response.lastLoginAt));
        const lastRefreshAt = response.lastRefreshAt ? new Date(response.lastRefreshAt).toUTCString() : null;
        utils.addReadonlyGetter(this, 'lastRefreshTime', lastRefreshAt);
    }
    /**
     * Returns a JSON-serializable representation of this object.
     *
     * @returns A JSON-serializable representation of this object.
     */
    toJSON() {
        return {
            lastSignInTime: this.lastSignInTime,
            creationTime: this.creationTime,
            lastRefreshTime: this.lastRefreshTime,
        };
    }
}
exports.UserMetadata = UserMetadata;
/**
 * Represents a user's info from a third-party identity provider
 * such as Google or Facebook.
 */
class UserInfo {
    /**
     * @param response - The server side response returned from the `getAccountInfo`
     *     endpoint.
     * @constructor
     * @internal
     */
    constructor(response) {
        // Provider user id and provider id are required.
        if (!response.rawId || !response.providerId) {
            throw new error_1.FirebaseAuthError(error_1.authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid user info response');
        }
        utils.addReadonlyGetter(this, 'uid', response.rawId);
        utils.addReadonlyGetter(this, 'displayName', response.displayName);
        utils.addReadonlyGetter(this, 'email', response.email);
        utils.addReadonlyGetter(this, 'photoURL', response.photoUrl);
        utils.addReadonlyGetter(this, 'providerId', response.providerId);
        utils.addReadonlyGetter(this, 'phoneNumber', response.phoneNumber);
    }
    /**
     * Returns a JSON-serializable representation of this object.
     *
     * @returns A JSON-serializable representation of this object.
     */
    toJSON() {
        return {
            uid: this.uid,
            displayName: this.displayName,
            email: this.email,
            photoURL: this.photoURL,
            providerId: this.providerId,
            phoneNumber: this.phoneNumber,
        };
    }
}
exports.UserInfo = UserInfo;
/**
 * Represents a user.
 */
class UserRecord {
    /**
     * @param response - The server side response returned from the getAccountInfo
     *     endpoint.
     * @constructor
     * @internal
     */
    constructor(response) {
        // The Firebase user id is required.
        if (!response.localId) {
            throw new error_1.FirebaseAuthError(error_1.authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid user response');
        }
        utils.addReadonlyGetter(this, 'uid', response.localId);
        utils.addReadonlyGetter(this, 'email', response.email);
        utils.addReadonlyGetter(this, 'emailVerified', !!response.emailVerified);
        utils.addReadonlyGetter(this, 'displayName', response.displayName);
        utils.addReadonlyGetter(this, 'photoURL', response.photoUrl);
        utils.addReadonlyGetter(this, 'phoneNumber', response.phoneNumber);
        // If disabled is not provided, the account is enabled by default.
        utils.addReadonlyGetter(this, 'disabled', response.disabled || false);
        utils.addReadonlyGetter(this, 'metadata', new UserMetadata(response));
        const providerData = [];
        for (const entry of (response.providerUserInfo || [])) {
            providerData.push(new UserInfo(entry));
        }
        utils.addReadonlyGetter(this, 'providerData', providerData);
        // If the password hash is redacted (probably due to missing permissions)
        // then clear it out, similar to how the salt is returned. (Otherwise, it
        // *looks* like a b64-encoded hash is present, which is confusing.)
        if (response.passwordHash === B64_REDACTED) {
            utils.addReadonlyGetter(this, 'passwordHash', undefined);
        }
        else {
            utils.addReadonlyGetter(this, 'passwordHash', response.passwordHash);
        }
        utils.addReadonlyGetter(this, 'passwordSalt', response.salt);
        if (response.customAttributes) {
            utils.addReadonlyGetter(this, 'customClaims', JSON.parse(response.customAttributes));
        }
        let validAfterTime = null;
        // Convert validSince first to UTC milliseconds and then to UTC date string.
        if (typeof response.validSince !== 'undefined') {
            validAfterTime = parseDate(parseInt(response.validSince, 10) * 1000);
        }
        utils.addReadonlyGetter(this, 'tokensValidAfterTime', validAfterTime || undefined);
        utils.addReadonlyGetter(this, 'tenantId', response.tenantId);
        const multiFactor = new MultiFactorSettings(response);
        if (multiFactor.enrolledFactors.length > 0) {
            utils.addReadonlyGetter(this, 'multiFactor', multiFactor);
        }
    }
    /**
     * Returns a JSON-serializable representation of this object.
     *
     * @returns A JSON-serializable representation of this object.
     */
    toJSON() {
        const json = {
            uid: this.uid,
            email: this.email,
            emailVerified: this.emailVerified,
            displayName: this.displayName,
            photoURL: this.photoURL,
            phoneNumber: this.phoneNumber,
            disabled: this.disabled,
            // Convert metadata to json.
            metadata: this.metadata.toJSON(),
            passwordHash: this.passwordHash,
            passwordSalt: this.passwordSalt,
            customClaims: (0, deep_copy_1.deepCopy)(this.customClaims),
            tokensValidAfterTime: this.tokensValidAfterTime,
            tenantId: this.tenantId,
        };
        if (this.multiFactor) {
            json.multiFactor = this.multiFactor.toJSON();
        }
        json.providerData = [];
        for (const entry of this.providerData) {
            // Convert each provider data to json.
            json.providerData.push(entry.toJSON());
        }
        return json;
    }
}
exports.UserRecord = UserRecord;
