/*! firebase-admin v5.2.1 */
"use strict";
/*!
 * Copyright 2017 Google Inc.
 *
 * 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 });
var validator = require("../utils/validator");
var deep_copy_1 = require("../utils/deep-copy");
var error_1 = require("../utils/error");
var error_2 = require("../utils/error");
var api_request_1 = require("../utils/api-request");
/** Firebase Auth backend host. */
var FIREBASE_AUTH_HOST = 'www.googleapis.com';
/** Firebase Auth backend port number. */
var FIREBASE_AUTH_PORT = 443;
/** Firebase Auth backend path. */
var FIREBASE_AUTH_PATH = '/identitytoolkit/v3/relyingparty/';
/** Firebase Auth request header. */
var FIREBASE_AUTH_HEADER = {
    'Content-Type': 'application/json',
    'X-Client-Version': 'Node/Admin/5.2.1',
};
/** Firebase Auth request timeout duration in milliseconds. */
var FIREBASE_AUTH_TIMEOUT = 10000;
/**
 * Validates a create/edit request object. All unsupported parameters
 * are removed from the original request. If an invalid field is passed
 * an error is thrown.
 *
 * @param {any} request The create/edit request object.
 */
function validateCreateEditRequest(request) {
    // Hash set of whitelisted parameters.
    var validKeys = {
        displayName: true,
        localId: true,
        email: true,
        password: true,
        rawPassword: true,
        emailVerified: true,
        photoUrl: true,
        disabled: true,
        disableUser: true,
        deleteAttribute: true,
        deleteProvider: true,
        sanityCheck: true,
        phoneNumber: true,
    };
    // Remove invalid keys from original request.
    for (var key in request) {
        if (!(key in validKeys)) {
            delete request[key];
        }
    }
    // For any invalid parameter, use the external key name in the error description.
    // displayName should be a string.
    if (typeof request.displayName !== 'undefined' &&
        typeof request.displayName !== 'string') {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_DISPLAY_NAME);
    }
    if (typeof request.localId !== 'undefined' && !validator.isUid(request.localId)) {
        // This is called localId on the backend but the developer specifies this as
        // uid externally. So the error message should use the client facing name.
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_UID);
    }
    // email should be a string and a valid email.
    if (typeof request.email !== 'undefined' && !validator.isEmail(request.email)) {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_EMAIL);
    }
    // phoneNumber should be a string and a valid phone number.
    if (typeof request.phoneNumber !== 'undefined' &&
        !validator.isPhoneNumber(request.phoneNumber)) {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_PHONE_NUMBER);
    }
    // password should be a string and a minimum of 6 chars.
    if (typeof request.password !== 'undefined' &&
        !validator.isPassword(request.password)) {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_PASSWORD);
    }
    // rawPassword should be a string and a minimum of 6 chars.
    if (typeof request.rawPassword !== 'undefined' &&
        !validator.isPassword(request.rawPassword)) {
        // This is called rawPassword on the backend but the developer specifies this as
        // password externally. So the error message should use the client facing name.
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_PASSWORD);
    }
    // emailVerified should be a boolean.
    if (typeof request.emailVerified !== 'undefined' &&
        typeof request.emailVerified !== 'boolean') {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_EMAIL_VERIFIED);
    }
    // photoUrl should be a URL.
    if (typeof request.photoUrl !== 'undefined' &&
        !validator.isURL(request.photoUrl)) {
        // This is called photoUrl on the backend but the developer specifies this as
        // photoURL externally. So the error message should use the client facing name.
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_PHOTO_URL);
    }
    // disabled should be a boolean.
    if (typeof request.disabled !== 'undefined' &&
        typeof request.disabled !== 'boolean') {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_DISABLED_FIELD);
    }
    // disableUser should be a boolean.
    if (typeof request.disableUser !== 'undefined' &&
        typeof request.disableUser !== 'boolean') {
        // This is called disableUser on the backend but the developer specifies this as
        // disabled externally. So the error message should use the client facing name.
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_DISABLED_FIELD);
    }
}
;
/** Instantiates the getAccountInfo endpoint settings. */
exports.FIREBASE_AUTH_GET_ACCOUNT_INFO = new api_request_1.ApiSettings('getAccountInfo', 'POST')
    .setRequestValidator(function (request) {
    if (!request.localId && !request.email && !request.phoneNumber) {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier');
    }
})
    .setResponseValidator(function (response) {
    if (!response.users) {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.USER_NOT_FOUND);
    }
});
/** Instantiates the deleteAccount endpoint settings. */
exports.FIREBASE_AUTH_DELETE_ACCOUNT = new api_request_1.ApiSettings('deleteAccount', 'POST')
    .setRequestValidator(function (request) {
    if (!request.localId) {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier');
    }
});
/** Instantiates the setAccountInfo endpoint settings for updating existing accounts. */
exports.FIREBASE_AUTH_SET_ACCOUNT_INFO = new api_request_1.ApiSettings('setAccountInfo', 'POST')
    .setRequestValidator(function (request) {
    // localId is a required parameter.
    if (typeof request.localId === 'undefined') {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier');
    }
    validateCreateEditRequest(request);
})
    .setResponseValidator(function (response) {
    // If the localId is not returned, then the request failed.
    if (!response.localId) {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.USER_NOT_FOUND);
    }
});
/**
 * Instantiates the signupNewUser endpoint settings for creating a new user with or without
 * uid being specified. The backend will create a new one if not provided and return it.
 */
exports.FIREBASE_AUTH_SIGN_UP_NEW_USER = new api_request_1.ApiSettings('signupNewUser', 'POST')
    .setRequestValidator(function (request) {
    validateCreateEditRequest(request);
})
    .setResponseValidator(function (response) {
    // If the localId is not returned, then the request failed.
    if (!response.localId) {
        throw new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create new user');
    }
});
/**
 * Class that provides mechanism to send requests to the Firebase Auth backend endpoints.
 */
var FirebaseAuthRequestHandler = (function () {
    /**
     * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests.
     * @constructor
     */
    function FirebaseAuthRequestHandler(app) {
        this.host = FIREBASE_AUTH_HOST;
        this.port = FIREBASE_AUTH_PORT;
        this.path = FIREBASE_AUTH_PATH;
        this.headers = FIREBASE_AUTH_HEADER;
        this.timeout = FIREBASE_AUTH_TIMEOUT;
        this.signedApiRequestHandler = new api_request_1.SignedApiRequestHandler(app);
    }
    /**
     * @param {Object} response The response to check for errors.
     * @return {string|null} The error code if present; null otherwise.
     */
    FirebaseAuthRequestHandler.getErrorCode = function (response) {
        return (validator.isNonNullObject(response) && response.error && response.error.message) || null;
    };
    /**
     * Looks up a user by uid.
     *
     * @param {string} uid The uid of the user to lookup.
     * @return {Promise<Object>} A promise that resolves with the user information.
     */
    FirebaseAuthRequestHandler.prototype.getAccountInfoByUid = function (uid) {
        if (!validator.isUid(uid)) {
            return Promise.reject(new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_UID));
        }
        var request = {
            localId: [uid],
        };
        return this.invokeRequestHandler(exports.FIREBASE_AUTH_GET_ACCOUNT_INFO, request);
    };
    /**
     * Looks up a user by email.
     *
     * @param {string} email The email of the user to lookup.
     * @return {Promise<Object>} A promise that resolves with the user information.
     */
    FirebaseAuthRequestHandler.prototype.getAccountInfoByEmail = function (email) {
        if (!validator.isEmail(email)) {
            return Promise.reject(new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_EMAIL));
        }
        var request = {
            email: [email],
        };
        return this.invokeRequestHandler(exports.FIREBASE_AUTH_GET_ACCOUNT_INFO, request);
    };
    /**
     * Looks up a user by phone number.
     *
     * @param {string} phoneNumber The phone number of the user to lookup.
     * @return {Promise<Object>} A promise that resolves with the user information.
     */
    FirebaseAuthRequestHandler.prototype.getAccountInfoByPhoneNumber = function (phoneNumber) {
        if (!validator.isPhoneNumber(phoneNumber)) {
            return Promise.reject(new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_PHONE_NUMBER));
        }
        var request = {
            phoneNumber: [phoneNumber],
        };
        return this.invokeRequestHandler(exports.FIREBASE_AUTH_GET_ACCOUNT_INFO, request);
    };
    /**
     * Deletes an account identified by a uid.
     *
     * @param {string} uid The uid of the user to delete.
     * @return {Promise<Object>} A promise that resolves when the user is deleted.
     */
    FirebaseAuthRequestHandler.prototype.deleteAccount = function (uid) {
        if (!validator.isUid(uid)) {
            return Promise.reject(new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_UID));
        }
        var request = {
            localId: uid,
        };
        return this.invokeRequestHandler(exports.FIREBASE_AUTH_DELETE_ACCOUNT, request);
    };
    /**
     * Edits an existing user.
     *
     * @param {string} uid The user to edit.
     * @param {Object} properties The properties to set on the user.
     * @return {Promise<string>} A promise that resolves when the operation completes
     *     with the user id that was edited.
     */
    FirebaseAuthRequestHandler.prototype.updateExistingAccount = function (uid, properties) {
        if (!validator.isUid(uid)) {
            return Promise.reject(new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_UID));
        }
        else if (!validator.isNonNullObject(properties)) {
            return Promise.reject(new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_ARGUMENT, 'Properties argument must be a non-null object.'));
        }
        // Build the setAccountInfo request.
        var request = deep_copy_1.deepCopy(properties);
        request.localId = uid;
        // For deleting displayName or photoURL, these values must be passed as null.
        // They will be removed from the backend request and an additional parameter
        // deleteAttribute: ['PHOTO_URL', 'DISPLAY_NAME']
        // with an array of the parameter names to delete will be passed.
        // Parameters that are deletable and their deleteAttribute names.
        // Use client facing names, photoURL instead of photoUrl.
        var deletableParams = {
            displayName: 'DISPLAY_NAME',
            photoURL: 'PHOTO_URL',
        };
        // Properties to delete if available.
        request.deleteAttribute = [];
        for (var key in deletableParams) {
            if (request[key] === null) {
                // Add property identifier to list of attributes to delete.
                request.deleteAttribute.push(deletableParams[key]);
                // Remove property from request.
                delete request[key];
            }
        }
        if (request.deleteAttribute.length === 0) {
            delete request.deleteAttribute;
        }
        // For deleting phoneNumber, this value must be passed as null.
        // It will be removed from the backend request and an additional parameter
        // deleteProvider: ['phone'] with an array of providerIds (phone in this case),
        // will be passed.
        // Currently this applies to phone provider only.
        if (request.phoneNumber === null) {
            request.deleteProvider = ['phone'];
            delete request.phoneNumber;
        }
        else {
            // Doesn't apply to other providers in admin SDK.
            delete request.deleteProvider;
        }
        // Rewrite photoURL to photoUrl.
        if (typeof request.photoURL !== 'undefined') {
            request.photoUrl = request.photoURL;
            delete request.photoURL;
        }
        // Rewrite disabled to disableUser.
        if (typeof request.disabled !== 'undefined') {
            request.disableUser = request.disabled;
            delete request.disabled;
        }
        return this.invokeRequestHandler(exports.FIREBASE_AUTH_SET_ACCOUNT_INFO, request)
            .then(function (response) {
            return response.localId;
        });
    };
    /**
     * Create a new user with the properties supplied.
     *
     * @param {Object} properties The properties to set on the user.
     * @return {Promise<string>} A promise that resolves when the operation completes
     *     with the user id that was created.
     */
    FirebaseAuthRequestHandler.prototype.createNewAccount = function (properties) {
        if (!validator.isNonNullObject(properties)) {
            return Promise.reject(new error_2.FirebaseAuthError(error_2.AuthClientErrorCode.INVALID_ARGUMENT, 'Properties argument must be a non-null object.'));
        }
        // Build the signupNewUser request.
        var request = deep_copy_1.deepCopy(properties);
        // Rewrite photoURL to photoUrl.
        if (typeof request.photoURL !== 'undefined') {
            request.photoUrl = request.photoURL;
            delete request.photoURL;
        }
        // Rewrite uid to localId if it exists.
        if (typeof request.uid !== 'undefined') {
            request.localId = request.uid;
            delete request.uid;
        }
        return this.invokeRequestHandler(exports.FIREBASE_AUTH_SIGN_UP_NEW_USER, request)
            .then(function (response) {
            // Return the user id.
            return response.localId;
        });
    };
    /**
     * Invokes the request handler based on the API settings object passed.
     *
     * @param {ApiSettings} apiSettings The API endpoint settings to apply to request and response.
     * @param {Object} requestData The request data.
     * @return {Promise<Object>} A promise that resolves with the response.
     */
    FirebaseAuthRequestHandler.prototype.invokeRequestHandler = function (apiSettings, requestData) {
        var _this = this;
        var path = this.path + apiSettings.getEndpoint();
        var httpMethod = apiSettings.getHttpMethod();
        return Promise.resolve()
            .then(function () {
            // Validate request.
            var requestValidator = apiSettings.getRequestValidator();
            requestValidator(requestData);
            // Process request.
            return _this.signedApiRequestHandler.sendRequest(_this.host, _this.port, path, httpMethod, requestData, _this.headers, _this.timeout);
        })
            .then(function (response) {
            // Check for backend errors in the response.
            var errorCode = FirebaseAuthRequestHandler.getErrorCode(response);
            if (errorCode) {
                throw error_2.FirebaseAuthError.fromServerError(errorCode, /* message */ undefined, response);
            }
            // Validate response.
            var responseValidator = apiSettings.getResponseValidator();
            responseValidator(response);
            // Return entire response.
            return response;
        })
            .catch(function (response) {
            var error;
            if (typeof response === 'object' && 'statusCode' in response) {
                // response came directly from a non-200 response.
                error = response.error;
            }
            else {
                // response came from a thrown error on a 200 response.
                error = response;
            }
            if (error instanceof error_1.FirebaseError) {
                throw error;
            }
            var errorCode = FirebaseAuthRequestHandler.getErrorCode(error);
            throw error_2.FirebaseAuthError.fromServerError(errorCode, /* message */ undefined, error);
        });
    };
    return FirebaseAuthRequestHandler;
}());
exports.FirebaseAuthRequestHandler = FirebaseAuthRequestHandler;
