firebase_id_token_verifier
Version:
Firebase id token verifier
1,090 lines (1,083 loc) • 97.9 kB
JavaScript
/*! firebase-admin v10.0.1 */
"use strict";
/*!
* @license
* 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.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.useEmulator = exports.TenantAwareAuthRequestHandler = exports.AuthRequestHandler = exports.AbstractAuthRequestHandler = exports.FIREBASE_AUTH_SIGN_UP_NEW_USER = exports.FIREBASE_AUTH_SET_ACCOUNT_INFO = exports.FIREBASE_AUTH_BATCH_DELETE_ACCOUNTS = exports.FIREBASE_AUTH_DELETE_ACCOUNT = exports.FIREBASE_AUTH_GET_ACCOUNTS_INFO = exports.FIREBASE_AUTH_GET_ACCOUNT_INFO = exports.FIREBASE_AUTH_DOWNLOAD_ACCOUNT = exports.FIREBASE_AUTH_UPLOAD_ACCOUNT = exports.FIREBASE_AUTH_CREATE_SESSION_COOKIE = exports.EMAIL_ACTION_REQUEST_TYPES = exports.RESERVED_CLAIMS = void 0;
var validator = require("../validator");
var deep_copy_1 = require("../deep-copy");
var error_1 = require("../error");
var api_request_1 = require("../api-request");
var user_import_builder_1 = require("./user-import-builder");
var action_code_settings_builder_1 = require("./action-code-settings-builder");
var tenant_1 = require("./tenant");
var identifier_1 = require("./identifier");
var auth_config_1 = require("./auth-config");
var credential_internal_1 = require("../app/credential-internal");
var sdkVersion;
function getSdkVersion() {
if (!sdkVersion) {
var version = require('../../package.json').firebaseSDKVersion; // eslint-disable-line @typescript-eslint/no-var-requires
sdkVersion = version;
}
return sdkVersion;
}
/** Firebase Auth request header. */
var FIREBASE_AUTH_HEADER = {
'X-Client-Version': "Node/Admin/" + getSdkVersion(),
};
/** Firebase Auth request timeout duration in milliseconds. */
var FIREBASE_AUTH_TIMEOUT = 25000;
/** List of reserved claims which cannot be provided when creating a custom token. */
exports.RESERVED_CLAIMS = [
'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat',
'iss', 'jti', 'nbf', 'nonce', 'sub', 'firebase',
];
/** List of supported email action request types. */
exports.EMAIL_ACTION_REQUEST_TYPES = [
'PASSWORD_RESET', 'VERIFY_EMAIL', 'EMAIL_SIGNIN',
];
/** Maximum allowed number of characters in the custom claims payload. */
var MAX_CLAIMS_PAYLOAD_SIZE = 1000;
/** Maximum allowed number of users to batch download at one time. */
var MAX_DOWNLOAD_ACCOUNT_PAGE_SIZE = 1000;
/** Maximum allowed number of users to batch upload at one time. */
var MAX_UPLOAD_ACCOUNT_BATCH_SIZE = 1000;
/** Maximum allowed number of users to batch get at one time. */
var MAX_GET_ACCOUNTS_BATCH_SIZE = 100;
/** Maximum allowed number of users to batch delete at one time. */
var MAX_DELETE_ACCOUNTS_BATCH_SIZE = 1000;
/** Minimum allowed session cookie duration in seconds (5 minutes). */
var MIN_SESSION_COOKIE_DURATION_SECS = 5 * 60;
/** Maximum allowed session cookie duration in seconds (2 weeks). */
var MAX_SESSION_COOKIE_DURATION_SECS = 14 * 24 * 60 * 60;
/** Maximum allowed number of provider configurations to batch download at one time. */
var MAX_LIST_PROVIDER_CONFIGURATION_PAGE_SIZE = 100;
/** The Firebase Auth backend base URL format. */
var FIREBASE_AUTH_BASE_URL_FORMAT = 'https://identitytoolkit.googleapis.com/{version}/projects/{projectId}{api}';
/** Firebase Auth base URlLformat when using the auth emultor. */
var FIREBASE_AUTH_EMULATOR_BASE_URL_FORMAT = 'http://{host}/identitytoolkit.googleapis.com/{version}/projects/{projectId}{api}';
/** The Firebase Auth backend multi-tenancy base URL format. */
var FIREBASE_AUTH_TENANT_URL_FORMAT = FIREBASE_AUTH_BASE_URL_FORMAT.replace('projects/{projectId}', 'projects/{projectId}/tenants/{tenantId}');
/** Firebase Auth base URL format when using the auth emultor with multi-tenancy. */
var FIREBASE_AUTH_EMULATOR_TENANT_URL_FORMAT = FIREBASE_AUTH_EMULATOR_BASE_URL_FORMAT.replace('projects/{projectId}', 'projects/{projectId}/tenants/{tenantId}');
/** Maximum allowed number of tenants to download at one time. */
var MAX_LIST_TENANT_PAGE_SIZE = 1000;
/**
* Formats a string of form 'project/{projectId}/{api}' and replaces
* with corresponding arguments {projectId: '1234', api: 'resource'}
* and returns output: 'project/1234/resource'.
*
* @param str - The original string where the param need to be
* replaced.
* @param params - The optional parameters to replace in the
* string.
* @returns The resulting formatted string.
*/
function formatString(str, params) {
var formatted = str;
Object.keys(params || {}).forEach(function (key) {
formatted = formatted.replace(new RegExp('{' + key + '}', 'g'), params[key]);
});
return formatted;
}
function getExplicitProjectId(app) {
var options = app.options;
if (validator.isNonEmptyString(options.projectId)) {
return options.projectId;
}
var credential = app.options.credential;
if (credential instanceof credential_internal_1.ServiceAccountCredential) {
return credential.projectId;
}
var projectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
if (validator.isNonEmptyString(projectId)) {
return projectId;
}
return null;
}
/**
* Determines the Google Cloud project ID associated with a Firebase app. This method
* first checks if a project ID is explicitly specified in either the Firebase app options,
* credentials or the local environment in that order. If no explicit project ID is
* configured, but the SDK has been initialized with ComputeEngineCredentials, this
* method attempts to discover the project ID from the local metadata service.
*
* @param app - A Firebase app to get the project ID from.
*
* @returns A project ID string or null.
*/
function findProjectId(app) {
var projectId = getExplicitProjectId(app);
if (projectId) {
return Promise.resolve(projectId);
}
var credential = app.options.credential;
if (credential instanceof credential_internal_1.ComputeEngineCredential) {
return credential.getProjectId();
}
return Promise.resolve(null);
}
/**
* Generates the update mask for the provided object.
* Note this will ignore the last key with value undefined.
*
* @param obj - The object to generate the update mask for.
* @param terminalPaths - The optional map of keys for maximum paths to traverse.
* Nested objects beyond that path will be ignored. This is useful for
* keys with variable object values.
* @param root - The path so far.
* @returns The computed update mask list.
*/
function generateUpdateMask(obj, terminalPaths, root) {
if (terminalPaths === void 0) { terminalPaths = []; }
if (root === void 0) { root = ''; }
var updateMask = [];
if (!validator.isNonNullObject(obj)) {
return updateMask;
}
var _loop_1 = function (key) {
if (typeof obj[key] !== 'undefined') {
var nextPath = root ? root + "." + key : key;
// We hit maximum path.
// Consider switching to Set<string> if the list grows too large.
if (terminalPaths.indexOf(nextPath) !== -1) {
// Add key and stop traversing this branch.
updateMask.push(key);
}
else {
var maskList = generateUpdateMask(obj[key], terminalPaths, nextPath);
if (maskList.length > 0) {
maskList.forEach(function (mask) {
updateMask.push(key + "." + mask);
});
}
else {
updateMask.push(key);
}
}
}
};
for (var key in obj) {
_loop_1(key);
}
return updateMask;
}
/**
* Enum for the user write operation type.
*/
var WriteOperationType;
(function (WriteOperationType) {
WriteOperationType["Create"] = "create";
WriteOperationType["Update"] = "update";
WriteOperationType["Upload"] = "upload";
})(WriteOperationType || (WriteOperationType = {}));
/** Defines a base utility to help with resource URL construction. */
var AuthResourceUrlBuilder = /** @class */ (function () {
/**
* The resource URL builder constructor.
*
* @param projectId - The resource project ID.
* @param version - The endpoint API version.
* @constructor
*/
function AuthResourceUrlBuilder(app, version) {
if (version === void 0) { version = 'v1'; }
this.app = app;
this.version = version;
if (useEmulator()) {
this.urlFormat = formatString(FIREBASE_AUTH_EMULATOR_BASE_URL_FORMAT, {
host: emulatorHost()
});
}
else {
this.urlFormat = FIREBASE_AUTH_BASE_URL_FORMAT;
}
}
/**
* Returns the resource URL corresponding to the provided parameters.
*
* @param api - The backend API name.
* @param params - The optional additional parameters to substitute in the
* URL path.
* @returns The corresponding resource URL.
*/
AuthResourceUrlBuilder.prototype.getUrl = function (api, params) {
var _this = this;
return this.getProjectId()
.then(function (projectId) {
var baseParams = {
version: _this.version,
projectId: projectId,
api: api || '',
};
var baseUrl = formatString(_this.urlFormat, baseParams);
// Substitute additional api related parameters.
return formatString(baseUrl, params || {});
});
};
AuthResourceUrlBuilder.prototype.getProjectId = function () {
var _this = this;
if (this.projectId) {
return Promise.resolve(this.projectId);
}
return findProjectId(this.app)
.then(function (projectId) {
if (!validator.isNonEmptyString(projectId)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, 'Failed to determine project ID for Auth. Initialize the '
+ 'SDK with service account credentials or set project ID as an app option. '
+ 'Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.');
}
_this.projectId = projectId;
return projectId;
});
};
return AuthResourceUrlBuilder;
}());
/** Tenant aware resource builder utility. */
var TenantAwareAuthResourceUrlBuilder = /** @class */ (function (_super) {
__extends(TenantAwareAuthResourceUrlBuilder, _super);
/**
* The tenant aware resource URL builder constructor.
*
* @param projectId - The resource project ID.
* @param version - The endpoint API version.
* @param tenantId - The tenant ID.
* @constructor
*/
function TenantAwareAuthResourceUrlBuilder(app, version, tenantId) {
var _this = _super.call(this, app, version) || this;
_this.app = app;
_this.version = version;
_this.tenantId = tenantId;
if (useEmulator()) {
_this.urlFormat = formatString(FIREBASE_AUTH_EMULATOR_TENANT_URL_FORMAT, {
host: emulatorHost()
});
}
else {
_this.urlFormat = FIREBASE_AUTH_TENANT_URL_FORMAT;
}
return _this;
}
/**
* Returns the resource URL corresponding to the provided parameters.
*
* @param api - The backend API name.
* @param params - The optional additional parameters to substitute in the
* URL path.
* @returns The corresponding resource URL.
*/
TenantAwareAuthResourceUrlBuilder.prototype.getUrl = function (api, params) {
var _this = this;
return _super.prototype.getUrl.call(this, api, params)
.then(function (url) {
return formatString(url, { tenantId: _this.tenantId });
});
};
return TenantAwareAuthResourceUrlBuilder;
}(AuthResourceUrlBuilder));
/**
* Auth-specific HTTP client which uses the special "owner" token
* when communicating with the Auth Emulator.
*/
var AuthHttpClient = /** @class */ (function (_super) {
__extends(AuthHttpClient, _super);
function AuthHttpClient() {
return _super !== null && _super.apply(this, arguments) || this;
}
AuthHttpClient.prototype.getToken = function () {
if (useEmulator()) {
return Promise.resolve('owner');
}
return _super.prototype.getToken.call(this);
};
return AuthHttpClient;
}(api_request_1.AuthorizedHttpClient));
/**
* Validates an AuthFactorInfo object. All unsupported parameters
* are removed from the original request. If an invalid field is passed
* an error is thrown.
*
* @param request - The AuthFactorInfo request object.
*/
function validateAuthFactorInfo(request) {
var validKeys = {
mfaEnrollmentId: true,
displayName: true,
phoneInfo: true,
enrolledAt: true,
};
// Remove unsupported keys from the original request.
for (var key in request) {
if (!(key in validKeys)) {
delete request[key];
}
}
// No enrollment ID is available for signupNewUser. Use another identifier.
var authFactorInfoIdentifier = request.mfaEnrollmentId || request.phoneInfo || JSON.stringify(request);
// Enrollment uid may or may not be specified for update operations.
if (typeof request.mfaEnrollmentId !== 'undefined' &&
!validator.isNonEmptyString(request.mfaEnrollmentId)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_UID, 'The second factor "uid" must be a valid non-empty string.');
}
if (typeof request.displayName !== 'undefined' &&
!validator.isString(request.displayName)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_DISPLAY_NAME, "The second factor \"displayName\" for \"" + authFactorInfoIdentifier + "\" must be a valid string.");
}
// enrolledAt must be a valid UTC date string.
if (typeof request.enrolledAt !== 'undefined' &&
!validator.isISODateString(request.enrolledAt)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ENROLLMENT_TIME, "The second factor \"enrollmentTime\" for \"" + authFactorInfoIdentifier + "\" must be a valid " +
'UTC date string.');
}
// Validate required fields depending on second factor type.
if (typeof request.phoneInfo !== 'undefined') {
// phoneNumber should be a string and a valid phone number.
if (!validator.isPhoneNumber(request.phoneInfo)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PHONE_NUMBER, "The second factor \"phoneNumber\" for \"" + authFactorInfoIdentifier + "\" must be a non-empty " +
'E.164 standard compliant identifier string.');
}
}
else {
// Invalid second factor. For example, a phone second factor may have been provided without
// a phone number. A TOTP based second factor may require a secret key, etc.
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ENROLLED_FACTORS, 'MFAInfo object provided is invalid.');
}
}
/**
* Validates a providerUserInfo object. All unsupported parameters
* are removed from the original request. If an invalid field is passed
* an error is thrown.
*
* @param request - The providerUserInfo request object.
*/
function validateProviderUserInfo(request) {
var validKeys = {
rawId: true,
providerId: true,
email: true,
displayName: true,
photoUrl: true,
};
// Remove invalid keys from original request.
for (var key in request) {
if (!(key in validKeys)) {
delete request[key];
}
}
if (!validator.isNonEmptyString(request.providerId)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PROVIDER_ID);
}
if (typeof request.displayName !== 'undefined' &&
typeof request.displayName !== 'string') {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_DISPLAY_NAME, "The provider \"displayName\" for \"" + request.providerId + "\" must be a valid string.");
}
if (!validator.isNonEmptyString(request.rawId)) {
// 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_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_UID, "The provider \"uid\" for \"" + request.providerId + "\" must be a valid non-empty string.");
}
// email should be a string and a valid email.
if (typeof request.email !== 'undefined' && !validator.isEmail(request.email)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_EMAIL, "The provider \"email\" for \"" + request.providerId + "\" must be a valid email string.");
}
// 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_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PHOTO_URL, "The provider \"photoURL\" for \"" + request.providerId + "\" must be a valid URL string.");
}
}
/**
* 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 request - The create/edit request object.
* @param writeOperationType - The write operation type.
*/
function validateCreateEditRequest(request, writeOperationType) {
var uploadAccountRequest = writeOperationType === WriteOperationType.Upload;
// 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,
customAttributes: true,
validSince: true,
// Pass linkProviderUserInfo only for updates (i.e. not for uploads.)
linkProviderUserInfo: !uploadAccountRequest,
// Pass tenantId only for uploadAccount requests.
tenantId: uploadAccountRequest,
passwordHash: uploadAccountRequest,
salt: uploadAccountRequest,
createdAt: uploadAccountRequest,
lastLoginAt: uploadAccountRequest,
providerUserInfo: uploadAccountRequest,
mfaInfo: uploadAccountRequest,
// Only for non-uploadAccount requests.
mfa: !uploadAccountRequest,
};
// Remove invalid keys from original request.
for (var key in request) {
if (!(key in validKeys)) {
delete request[key];
}
}
if (typeof request.tenantId !== 'undefined' &&
!validator.isNonEmptyString(request.tenantId)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_TENANT_ID);
}
// For any invalid parameter, use the external key name in the error description.
// displayName should be a string.
if (typeof request.displayName !== 'undefined' &&
!validator.isString(request.displayName)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_DISPLAY_NAME);
}
if ((typeof request.localId !== 'undefined' || uploadAccountRequest) &&
!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_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_UID);
}
// email should be a string and a valid email.
if (typeof request.email !== 'undefined' && !validator.isEmail(request.email)) {
throw new error_1.FirebaseAuthError(error_1.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_1.FirebaseAuthError(error_1.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_1.FirebaseAuthError(error_1.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_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PASSWORD);
}
// emailVerified should be a boolean.
if (typeof request.emailVerified !== 'undefined' &&
typeof request.emailVerified !== 'boolean') {
throw new error_1.FirebaseAuthError(error_1.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_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PHOTO_URL);
}
// disabled should be a boolean.
if (typeof request.disabled !== 'undefined' &&
typeof request.disabled !== 'boolean') {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_DISABLED_FIELD);
}
// validSince should be a number.
if (typeof request.validSince !== 'undefined' &&
!validator.isNumber(request.validSince)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_TOKENS_VALID_AFTER_TIME);
}
// createdAt should be a number.
if (typeof request.createdAt !== 'undefined' &&
!validator.isNumber(request.createdAt)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREATION_TIME);
}
// lastSignInAt should be a number.
if (typeof request.lastLoginAt !== 'undefined' &&
!validator.isNumber(request.lastLoginAt)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_LAST_SIGN_IN_TIME);
}
// 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_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_DISABLED_FIELD);
}
// customAttributes should be stringified JSON with no blacklisted claims.
// The payload should not exceed 1KB.
if (typeof request.customAttributes !== 'undefined') {
var developerClaims_1;
try {
developerClaims_1 = JSON.parse(request.customAttributes);
}
catch (error) {
// JSON parsing error. This should never happen as we stringify the claims internally.
// However, we still need to check since setAccountInfo via edit requests could pass
// this field.
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CLAIMS, error.message);
}
var invalidClaims_1 = [];
// Check for any invalid claims.
exports.RESERVED_CLAIMS.forEach(function (blacklistedClaim) {
if (Object.prototype.hasOwnProperty.call(developerClaims_1, blacklistedClaim)) {
invalidClaims_1.push(blacklistedClaim);
}
});
// Throw an error if an invalid claim is detected.
if (invalidClaims_1.length > 0) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.FORBIDDEN_CLAIM, invalidClaims_1.length > 1 ?
"Developer claims \"" + invalidClaims_1.join('", "') + "\" are reserved and cannot be specified." :
"Developer claim \"" + invalidClaims_1[0] + "\" is reserved and cannot be specified.");
}
// Check claims payload does not exceed maxmimum size.
if (request.customAttributes.length > MAX_CLAIMS_PAYLOAD_SIZE) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.CLAIMS_TOO_LARGE, "Developer claims payload should not exceed " + MAX_CLAIMS_PAYLOAD_SIZE + " characters.");
}
}
// passwordHash has to be a base64 encoded string.
if (typeof request.passwordHash !== 'undefined' &&
!validator.isString(request.passwordHash)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PASSWORD_HASH);
}
// salt has to be a base64 encoded string.
if (typeof request.salt !== 'undefined' &&
!validator.isString(request.salt)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PASSWORD_SALT);
}
// providerUserInfo has to be an array of valid UserInfo requests.
if (typeof request.providerUserInfo !== 'undefined' &&
!validator.isArray(request.providerUserInfo)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PROVIDER_DATA);
}
else if (validator.isArray(request.providerUserInfo)) {
request.providerUserInfo.forEach(function (providerUserInfoEntry) {
validateProviderUserInfo(providerUserInfoEntry);
});
}
// linkProviderUserInfo must be a (single) UserProvider value.
if (typeof request.linkProviderUserInfo !== 'undefined') {
validateProviderUserInfo(request.linkProviderUserInfo);
}
// mfaInfo is used for importUsers.
// mfa.enrollments is used for setAccountInfo.
// enrollments has to be an array of valid AuthFactorInfo requests.
var enrollments = null;
if (request.mfaInfo) {
enrollments = request.mfaInfo;
}
else if (request.mfa && request.mfa.enrollments) {
enrollments = request.mfa.enrollments;
}
if (enrollments) {
if (!validator.isArray(enrollments)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ENROLLED_FACTORS);
}
enrollments.forEach(function (authFactorInfoEntry) {
validateAuthFactorInfo(authFactorInfoEntry);
});
}
}
/**
* Instantiates the createSessionCookie endpoint settings.
*
* @internal
*/
exports.FIREBASE_AUTH_CREATE_SESSION_COOKIE = new api_request_1.ApiSettings(':createSessionCookie', 'POST')
// Set request validator.
.setRequestValidator(function (request) {
// Validate the ID token is a non-empty string.
if (!validator.isNonEmptyString(request.idToken)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ID_TOKEN);
}
// Validate the custom session cookie duration.
if (!validator.isNumber(request.validDuration) ||
request.validDuration < MIN_SESSION_COOKIE_DURATION_SECS ||
request.validDuration > MAX_SESSION_COOKIE_DURATION_SECS) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION);
}
})
// Set response validator.
.setResponseValidator(function (response) {
// Response should always contain the session cookie.
if (!validator.isNonEmptyString(response.sessionCookie)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR);
}
});
/**
* Instantiates the uploadAccount endpoint settings.
*
* @internal
*/
exports.FIREBASE_AUTH_UPLOAD_ACCOUNT = new api_request_1.ApiSettings('/accounts:batchCreate', 'POST');
/**
* Instantiates the downloadAccount endpoint settings.
*
* @internal
*/
exports.FIREBASE_AUTH_DOWNLOAD_ACCOUNT = new api_request_1.ApiSettings('/accounts:batchGet', 'GET')
// Set request validator.
.setRequestValidator(function (request) {
// Validate next page token.
if (typeof request.nextPageToken !== 'undefined' &&
!validator.isNonEmptyString(request.nextPageToken)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PAGE_TOKEN);
}
// Validate max results.
if (!validator.isNumber(request.maxResults) ||
request.maxResults <= 0 ||
request.maxResults > MAX_DOWNLOAD_ACCOUNT_PAGE_SIZE) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive integer that does not exceed ' +
(MAX_DOWNLOAD_ACCOUNT_PAGE_SIZE + "."));
}
});
/**
* Instantiates the getAccountInfo endpoint settings.
*
* @internal
*/
exports.FIREBASE_AUTH_GET_ACCOUNT_INFO = new api_request_1.ApiSettings('/accounts:lookup', 'POST')
// Set request validator.
.setRequestValidator(function (request) {
if (!request.localId && !request.email && !request.phoneNumber && !request.federatedUserId) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier');
}
})
// Set response validator.
.setResponseValidator(function (response) {
if (!response.users || !response.users.length) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.USER_NOT_FOUND);
}
});
/**
* Instantiates the getAccountInfo endpoint settings for use when fetching info
* for multiple accounts.
*
* @internal
*/
exports.FIREBASE_AUTH_GET_ACCOUNTS_INFO = new api_request_1.ApiSettings('/accounts:lookup', 'POST')
// Set request validator.
.setRequestValidator(function (request) {
if (!request.localId && !request.email && !request.phoneNumber && !request.federatedUserId) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier');
}
});
/**
* Instantiates the deleteAccount endpoint settings.
*
* @internal
*/
exports.FIREBASE_AUTH_DELETE_ACCOUNT = new api_request_1.ApiSettings('/accounts:delete', 'POST')
// Set request validator.
.setRequestValidator(function (request) {
if (!request.localId) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier');
}
});
/**
* @internal
*/
exports.FIREBASE_AUTH_BATCH_DELETE_ACCOUNTS = new api_request_1.ApiSettings('/accounts:batchDelete', 'POST')
.setRequestValidator(function (request) {
if (!request.localIds) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifiers');
}
if (typeof request.force === 'undefined' || request.force !== true) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing force=true field');
}
})
.setResponseValidator(function (response) {
var errors = response.errors || [];
errors.forEach(function (batchDeleteErrorInfo) {
if (typeof batchDeleteErrorInfo.index === 'undefined') {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server BatchDeleteAccountResponse is missing an errors.index field');
}
if (!batchDeleteErrorInfo.localId) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server BatchDeleteAccountResponse is missing an errors.localId field');
}
// Allow the (error) message to be missing/undef.
});
});
/**
* Instantiates the setAccountInfo endpoint settings for updating existing accounts.
*
* @internal
*/
exports.FIREBASE_AUTH_SET_ACCOUNT_INFO = new api_request_1.ApiSettings('/accounts:update', 'POST')
// Set request validator.
.setRequestValidator(function (request) {
// localId is a required parameter.
if (typeof request.localId === 'undefined') {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier');
}
// Throw error when tenantId is passed in POST body.
if (typeof request.tenantId !== 'undefined') {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, '"tenantId" is an invalid "UpdateRequest" property.');
}
validateCreateEditRequest(request, WriteOperationType.Update);
})
// Set response validator.
.setResponseValidator(function (response) {
// If the localId is not returned, then the request failed.
if (!response.localId) {
throw new error_1.FirebaseAuthError(error_1.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.
*
* @internal
*/
exports.FIREBASE_AUTH_SIGN_UP_NEW_USER = new api_request_1.ApiSettings('/accounts', 'POST')
// Set request validator.
.setRequestValidator(function (request) {
// signupNewUser does not support customAttributes.
if (typeof request.customAttributes !== 'undefined') {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, '"customAttributes" cannot be set when creating a new user.');
}
// signupNewUser does not support validSince.
if (typeof request.validSince !== 'undefined') {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, '"validSince" cannot be set when creating a new user.');
}
// Throw error when tenantId is passed in POST body.
if (typeof request.tenantId !== 'undefined') {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, '"tenantId" is an invalid "CreateRequest" property.');
}
validateCreateEditRequest(request, WriteOperationType.Create);
})
// Set response validator.
.setResponseValidator(function (response) {
// If the localId is not returned, then the request failed.
if (!response.localId) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create new user');
}
});
var FIREBASE_AUTH_GET_OOB_CODE = new api_request_1.ApiSettings('/accounts:sendOobCode', 'POST')
// Set request validator.
.setRequestValidator(function (request) {
if (!validator.isEmail(request.email)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_EMAIL);
}
if (exports.EMAIL_ACTION_REQUEST_TYPES.indexOf(request.requestType) === -1) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "\"" + request.requestType + "\" is not a supported email action request type.");
}
})
// Set response validator.
.setResponseValidator(function (response) {
// If the oobLink is not returned, then the request failed.
if (!response.oobLink) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create the email action link');
}
});
/**
* Instantiates the retrieve OIDC configuration endpoint settings.
*
* @internal
*/
var GET_OAUTH_IDP_CONFIG = new api_request_1.ApiSettings('/oauthIdpConfigs/{providerId}', 'GET')
// Set response validator.
.setResponseValidator(function (response) {
// Response should always contain the OIDC provider resource name.
if (!validator.isNonEmptyString(response.name)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to get OIDC configuration');
}
});
/**
* Instantiates the delete OIDC configuration endpoint settings.
*
* @internal
*/
var DELETE_OAUTH_IDP_CONFIG = new api_request_1.ApiSettings('/oauthIdpConfigs/{providerId}', 'DELETE');
/**
* Instantiates the create OIDC configuration endpoint settings.
*
* @internal
*/
var CREATE_OAUTH_IDP_CONFIG = new api_request_1.ApiSettings('/oauthIdpConfigs?oauthIdpConfigId={providerId}', 'POST')
// Set response validator.
.setResponseValidator(function (response) {
// Response should always contain the OIDC provider resource name.
if (!validator.isNonEmptyString(response.name)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create new OIDC configuration');
}
});
/**
* Instantiates the update OIDC configuration endpoint settings.
*
* @internal
*/
var UPDATE_OAUTH_IDP_CONFIG = new api_request_1.ApiSettings('/oauthIdpConfigs/{providerId}?updateMask={updateMask}', 'PATCH')
// Set response validator.
.setResponseValidator(function (response) {
// Response should always contain the configuration resource name.
if (!validator.isNonEmptyString(response.name)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to update OIDC configuration');
}
});
/**
* Instantiates the list OIDC configuration endpoint settings.
*
* @internal
*/
var LIST_OAUTH_IDP_CONFIGS = new api_request_1.ApiSettings('/oauthIdpConfigs', 'GET')
// Set request validator.
.setRequestValidator(function (request) {
// Validate next page token.
if (typeof request.pageToken !== 'undefined' &&
!validator.isNonEmptyString(request.pageToken)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PAGE_TOKEN);
}
// Validate max results.
if (!validator.isNumber(request.pageSize) ||
request.pageSize <= 0 ||
request.pageSize > MAX_LIST_PROVIDER_CONFIGURATION_PAGE_SIZE) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive integer that does not exceed ' +
(MAX_LIST_PROVIDER_CONFIGURATION_PAGE_SIZE + "."));
}
});
/**
* Instantiates the retrieve SAML configuration endpoint settings.
*
* @internal
*/
var GET_INBOUND_SAML_CONFIG = new api_request_1.ApiSettings('/inboundSamlConfigs/{providerId}', 'GET')
// Set response validator.
.setResponseValidator(function (response) {
// Response should always contain the SAML provider resource name.
if (!validator.isNonEmptyString(response.name)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to get SAML configuration');
}
});
/**
* Instantiates the delete SAML configuration endpoint settings.
*
* @internal
*/
var DELETE_INBOUND_SAML_CONFIG = new api_request_1.ApiSettings('/inboundSamlConfigs/{providerId}', 'DELETE');
/**
* Instantiates the create SAML configuration endpoint settings.
*
* @internal
*/
var CREATE_INBOUND_SAML_CONFIG = new api_request_1.ApiSettings('/inboundSamlConfigs?inboundSamlConfigId={providerId}', 'POST')
// Set response validator.
.setResponseValidator(function (response) {
// Response should always contain the SAML provider resource name.
if (!validator.isNonEmptyString(response.name)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create new SAML configuration');
}
});
/**
* Instantiates the update SAML configuration endpoint settings.
*
* @internal
*/
var UPDATE_INBOUND_SAML_CONFIG = new api_request_1.ApiSettings('/inboundSamlConfigs/{providerId}?updateMask={updateMask}', 'PATCH')
// Set response validator.
.setResponseValidator(function (response) {
// Response should always contain the configuration resource name.
if (!validator.isNonEmptyString(response.name)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to update SAML configuration');
}
});
/**
* Instantiates the list SAML configuration endpoint settings.
*
* @internal
*/
var LIST_INBOUND_SAML_CONFIGS = new api_request_1.ApiSettings('/inboundSamlConfigs', 'GET')
// Set request validator.
.setRequestValidator(function (request) {
// Validate next page token.
if (typeof request.pageToken !== 'undefined' &&
!validator.isNonEmptyString(request.pageToken)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PAGE_TOKEN);
}
// Validate max results.
if (!validator.isNumber(request.pageSize) ||
request.pageSize <= 0 ||
request.pageSize > MAX_LIST_PROVIDER_CONFIGURATION_PAGE_SIZE) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive integer that does not exceed ' +
(MAX_LIST_PROVIDER_CONFIGURATION_PAGE_SIZE + "."));
}
});
/**
* Class that provides the mechanism to send requests to the Firebase Auth backend endpoints.
*
* @internal
*/
var AbstractAuthRequestHandler = /** @class */ (function () {
/**
* @param app - The app used to fetch access tokens to sign API requests.
* @constructor
*/
function AbstractAuthRequestHandler(app) {
this.app = app;
if (typeof app !== 'object' || app === null || !('options' in app)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'First argument passed to admin.auth() must be a valid Firebase app instance.');
}
this.httpClient = new AuthHttpClient(app);
}
/**
* @param response - The response to check for errors.
* @returns The error code if present; null otherwise.
*/
AbstractAuthRequestHandler.getErrorCode = function (response) {
return (validator.isNonNullObject(response) && response.error && response.error.message) || null;
};
AbstractAuthRequestHandler.addUidToRequest = function (id, request) {
if (!validator.isUid(id.uid)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_UID);
}
request.localId ? request.localId.push(id.uid) : request.localId = [id.uid];
return request;
};
AbstractAuthRequestHandler.addEmailToRequest = function (id, request) {
if (!validator.isEmail(id.email)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_EMAIL);
}
request.email ? request.email.push(id.email) : request.email = [id.email];
return request;
};
AbstractAuthRequestHandler.addPhoneToRequest = function (id, request) {
if (!validator.isPhoneNumber(id.phoneNumber)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PHONE_NUMBER);
}
request.phoneNumber ? request.phoneNumber.push(id.phoneNumber) : request.phoneNumber = [id.phoneNumber];
return request;
};
AbstractAuthRequestHandler.addProviderToRequest = function (id, request) {
if (!validator.isNonEmptyString(id.providerId)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PROVIDER_ID);
}
if (!validator.isNonEmptyString(id.providerUid)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PROVIDER_UID);
}
var federatedUserId = {
providerId: id.providerId,
rawId: id.providerUid,
};
request.federatedUserId
? request.federatedUserId.push(federatedUserId)
: request.federatedUserId = [federatedUserId];
return request;
};
/**
* Creates a new Firebase session cookie with the specified duration that can be used for
* session management (set as a server side session cookie with custom cookie policy).
* The session cookie JWT will have the same payload claims as the provided ID token.
*
* @param idToken - The Firebase ID token to exchange for a session cookie.
* @param expiresIn - The session cookie duration in milliseconds.
*
* @returns A promise that resolves on success with the created session cookie.
*/
AbstractAuthRequestHandler.prototype.createSessionCookie = function (idToken, expiresIn) {
var request = {
idToken: idToken,
// Convert to seconds.
validDuration: expiresIn / 1000,
};
return this.invokeRequestHandler(this.getAuthUrlBuilder(), exports.FIREBASE_AUTH_CREATE_SESSION_COOKIE, request)
.then(function (response) { return response.sessionCookie; });
};
/**
* Looks up a user by uid.
*
* @param uid - The uid of the user to lookup.
* @returns A promise that resolves with the user information.
*/
AbstractAuthRequestHandler.prototype.getAccountInfoByUid = function (uid) {
if (!validator.isUid(uid)) {
return Promise.reject(new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_UID));
}
var request = {
localId: [uid],
};
return this.invokeRequestHandler(this.getAuthUrlBuilder(), exports.FIREBASE_AUTH_GET_ACCOUNT_INFO, request);
};
/**
* Looks up a user by email.
*
* @param email - The email of the user to lookup.
* @returns A promise that resolves with the user information.
*/
AbstractAuthRequestHandler.prototype.getAccountInfoByEmail = function (email) {
if (!validator.isEmail(email)) {
return Promise.reject(new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_EMAIL));
}
var request = {
email: [email],
};
return this.invokeRequestHandler(this.getAuthUrlBuilder(), exports.FIREBASE_AUTH_GET_ACCOUNT_INFO, request);
};
/**
* Looks up a user by phone number.
*
* @param phoneNumber - The phone number of the user to lookup.
* @returns A promise that resolves with the user information.
*/
AbstractAuthRequestHandler.prototype.getAccountInfoByPhoneNumber = function (phoneNumber) {
if (!validator.isPhoneNumber(phoneNumber)) {
return Promise.reject(new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PHONE_NUMBER));
}
var request = {
phoneNumber: [phoneNumber],
};
return this.invokeRequestHandler(this.getAuthUrlBuilder(), exports.FIREBASE_AUTH_GET_ACCOUNT_INFO, request);
};
AbstractAuthRequestHandler.prototype.getAccountInfoByFederatedUid = function (providerId, rawId) {
if (!validator.isNonEmptyString(providerId) || !validator.isNonEmptyString(rawId)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_PROVIDER_ID);
}
var request = {
federatedUserId: [{
providerId: providerId,
rawId: rawId,
}],
};
return this.invokeRequestHandler(this.getAuthUrlBuilder(), exports.FIREBASE_AUTH_GET_ACCOUNT_INFO, request);
};
/**
* Looks up multiple users by their identifiers (uid, email, etc).
*
* @param identifiers - The identifiers indicating the users
* to be looked up. Must have <= 100 entries.
* @param A - promise that resolves with the set of successfully
* looked up users. Possibly empty if no users were looked up.