From 2e2b77943e9fe94101b7cd28279d5880642f12a5 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Fri, 1 May 2026 11:45:51 -0400 Subject: [PATCH 01/22] chore(auth): Refactor auth error logic --- etc/firebase-admin.auth.api.md | 383 +----------- src/auth/action-code-settings-builder.ts | 30 +- src/auth/auth-api-request.ts | 239 ++++---- src/auth/auth-config.ts | 204 +++--- src/auth/base-auth.ts | 43 +- src/auth/error.ts | 466 ++++++++++++++ src/auth/index.ts | 4 +- src/auth/project-config.ts | 6 +- src/auth/tenant-manager.ts | 12 +- src/auth/tenant.ts | 12 +- src/auth/token-generator.ts | 23 +- src/auth/token-verifier.ts | 43 +- src/auth/user-import-builder.ts | 42 +- src/auth/user-record.ts | 10 +- src/utils/error.ts | 579 ------------------ .../auth/action-code-settings-builder.spec.ts | 14 +- test/unit/auth/auth-api-request.spec.ts | 224 +++---- test/unit/auth/auth.spec.ts | 78 +-- test/unit/auth/project-config-manager.spec.ts | 6 +- test/unit/auth/tenant-manager.spec.ts | 12 +- test/unit/auth/token-generator.spec.ts | 2 +- test/unit/auth/token-verifier.spec.ts | 10 +- test/unit/auth/user-import-builder.spec.ts | 54 +- test/unit/utils/error.spec.ts | 9 +- 24 files changed, 1008 insertions(+), 1497 deletions(-) create mode 100644 src/auth/error.ts diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 9cf480f616..c016df5b73 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -56,388 +56,7 @@ export class Auth extends BaseAuth { } // @public -export const AuthClientErrorCode: { - AUTH_BLOCKING_TOKEN_EXPIRED: { - code: string; - message: string; - }; - BILLING_NOT_ENABLED: { - code: string; - message: string; - }; - CLAIMS_TOO_LARGE: { - code: string; - message: string; - }; - CONFIGURATION_EXISTS: { - code: string; - message: string; - }; - CONFIGURATION_NOT_FOUND: { - code: string; - message: string; - }; - ID_TOKEN_EXPIRED: { - code: string; - message: string; - }; - INVALID_ARGUMENT: { - code: string; - message: string; - }; - INVALID_CONFIG: { - code: string; - message: string; - }; - EMAIL_ALREADY_EXISTS: { - code: string; - message: string; - }; - EMAIL_NOT_FOUND: { - code: string; - message: string; - }; - FORBIDDEN_CLAIM: { - code: string; - message: string; - }; - INVALID_ID_TOKEN: { - code: string; - message: string; - }; - ID_TOKEN_REVOKED: { - code: string; - message: string; - }; - INTERNAL_ERROR: { - code: string; - message: string; - }; - INVALID_CLAIMS: { - code: string; - message: string; - }; - INVALID_CONTINUE_URI: { - code: string; - message: string; - }; - INVALID_CREATION_TIME: { - code: string; - message: string; - }; - INVALID_CREDENTIAL: { - code: string; - message: string; - }; - INVALID_DISABLED_FIELD: { - code: string; - message: string; - }; - INVALID_DISPLAY_NAME: { - code: string; - message: string; - }; - INVALID_DYNAMIC_LINK_DOMAIN: { - code: string; - message: string; - }; - INVALID_HOSTING_LINK_DOMAIN: { - code: string; - message: string; - }; - INVALID_EMAIL_VERIFIED: { - code: string; - message: string; - }; - INVALID_EMAIL: { - code: string; - message: string; - }; - INVALID_NEW_EMAIL: { - code: string; - message: string; - }; - INVALID_ENROLLED_FACTORS: { - code: string; - message: string; - }; - INVALID_ENROLLMENT_TIME: { - code: string; - message: string; - }; - INVALID_HASH_ALGORITHM: { - code: string; - message: string; - }; - INVALID_HASH_BLOCK_SIZE: { - code: string; - message: string; - }; - INVALID_HASH_DERIVED_KEY_LENGTH: { - code: string; - message: string; - }; - INVALID_HASH_KEY: { - code: string; - message: string; - }; - INVALID_HASH_MEMORY_COST: { - code: string; - message: string; - }; - INVALID_HASH_PARALLELIZATION: { - code: string; - message: string; - }; - INVALID_HASH_ROUNDS: { - code: string; - message: string; - }; - INVALID_HASH_SALT_SEPARATOR: { - code: string; - message: string; - }; - INVALID_LAST_SIGN_IN_TIME: { - code: string; - message: string; - }; - INVALID_NAME: { - code: string; - message: string; - }; - INVALID_OAUTH_CLIENT_ID: { - code: string; - message: string; - }; - INVALID_PAGE_TOKEN: { - code: string; - message: string; - }; - INVALID_PASSWORD: { - code: string; - message: string; - }; - INVALID_PASSWORD_HASH: { - code: string; - message: string; - }; - INVALID_PASSWORD_SALT: { - code: string; - message: string; - }; - INVALID_PHONE_NUMBER: { - code: string; - message: string; - }; - INVALID_PHOTO_URL: { - code: string; - message: string; - }; - INVALID_PROJECT_ID: { - code: string; - message: string; - }; - INVALID_PROVIDER_DATA: { - code: string; - message: string; - }; - INVALID_PROVIDER_ID: { - code: string; - message: string; - }; - INVALID_PROVIDER_UID: { - code: string; - message: string; - }; - INVALID_OAUTH_RESPONSETYPE: { - code: string; - message: string; - }; - INVALID_SESSION_COOKIE_DURATION: { - code: string; - message: string; - }; - INVALID_TENANT_ID: { - code: string; - message: string; - }; - INVALID_TENANT_TYPE: { - code: string; - message: string; - }; - INVALID_TESTING_PHONE_NUMBER: { - code: string; - message: string; - }; - INVALID_UID: { - code: string; - message: string; - }; - INVALID_USER_IMPORT: { - code: string; - message: string; - }; - INVALID_TOKENS_VALID_AFTER_TIME: { - code: string; - message: string; - }; - MISMATCHING_TENANT_ID: { - code: string; - message: string; - }; - MISSING_ANDROID_PACKAGE_NAME: { - code: string; - message: string; - }; - MISSING_CONFIG: { - code: string; - message: string; - }; - MISSING_CONTINUE_URI: { - code: string; - message: string; - }; - MISSING_DISPLAY_NAME: { - code: string; - message: string; - }; - MISSING_EMAIL: { - code: string; - message: string; - }; - MISSING_IOS_BUNDLE_ID: { - code: string; - message: string; - }; - MISSING_ISSUER: { - code: string; - message: string; - }; - MISSING_HASH_ALGORITHM: { - code: string; - message: string; - }; - MISSING_OAUTH_CLIENT_ID: { - code: string; - message: string; - }; - MISSING_OAUTH_CLIENT_SECRET: { - code: string; - message: string; - }; - MISSING_PROVIDER_ID: { - code: string; - message: string; - }; - MISSING_SAML_RELYING_PARTY_CONFIG: { - code: string; - message: string; - }; - MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED: { - code: string; - message: string; - }; - MAXIMUM_USER_COUNT_EXCEEDED: { - code: string; - message: string; - }; - MISSING_UID: { - code: string; - message: string; - }; - OPERATION_NOT_ALLOWED: { - code: string; - message: string; - }; - PHONE_NUMBER_ALREADY_EXISTS: { - code: string; - message: string; - }; - PROJECT_NOT_FOUND: { - code: string; - message: string; - }; - INSUFFICIENT_PERMISSION: { - code: string; - message: string; - }; - QUOTA_EXCEEDED: { - code: string; - message: string; - }; - SECOND_FACTOR_LIMIT_EXCEEDED: { - code: string; - message: string; - }; - SECOND_FACTOR_UID_ALREADY_EXISTS: { - code: string; - message: string; - }; - SESSION_COOKIE_EXPIRED: { - code: string; - message: string; - }; - SESSION_COOKIE_REVOKED: { - code: string; - message: string; - }; - TENANT_NOT_FOUND: { - code: string; - message: string; - }; - UID_ALREADY_EXISTS: { - code: string; - message: string; - }; - UNAUTHORIZED_DOMAIN: { - code: string; - message: string; - }; - UNSUPPORTED_FIRST_FACTOR: { - code: string; - message: string; - }; - UNSUPPORTED_SECOND_FACTOR: { - code: string; - message: string; - }; - UNSUPPORTED_TENANT_OPERATION: { - code: string; - message: string; - }; - UNVERIFIED_EMAIL: { - code: string; - message: string; - }; - USER_NOT_FOUND: { - code: string; - message: string; - }; - NOT_FOUND: { - code: string; - message: string; - }; - USER_DISABLED: { - code: string; - message: string; - }; - USER_NOT_DISABLED: { - code: string; - message: string; - }; - INVALID_RECAPTCHA_ACTION: { - code: string; - message: string; - }; - INVALID_RECAPTCHA_ENFORCEMENT_STATE: { - code: string; - message: string; - }; - RECAPTCHA_NOT_ENABLED: { - code: string; - message: string; - }; -}; +export type AuthErrorCode = 'auth-blocking-token-expired' | 'billing-not-enabled' | 'claims-too-large' | 'configuration-exists' | 'configuration-not-found' | 'id-token-expired' | 'argument-error' | 'invalid-config' | 'email-already-exists' | 'email-not-found' | 'reserved-claim' | 'invalid-id-token' | 'id-token-revoked' | 'internal-error' | 'invalid-claims' | 'invalid-continue-uri' | 'invalid-creation-time' | 'invalid-credential' | 'invalid-disabled-field' | 'invalid-display-name' | 'invalid-dynamic-link-domain' | 'invalid-hosting-link-domain' | 'invalid-email-verified' | 'invalid-email' | 'invalid-new-email' | 'invalid-enrolled-factors' | 'invalid-enrollment-time' | 'invalid-hash-algorithm' | 'invalid-hash-block-size' | 'invalid-hash-derived-key-length' | 'invalid-hash-key' | 'invalid-hash-memory-cost' | 'invalid-hash-parallelization' | 'invalid-hash-rounds' | 'invalid-hash-salt-separator' | 'invalid-last-sign-in-time' | 'invalid-name' | 'invalid-oauth-client-id' | 'invalid-page-token' | 'invalid-password' | 'invalid-password-hash' | 'invalid-password-salt' | 'invalid-phone-number' | 'invalid-photo-url' | 'invalid-project-id' | 'invalid-provider-data' | 'invalid-provider-id' | 'invalid-provider-uid' | 'invalid-oauth-responsetype' | 'invalid-session-cookie-duration' | 'invalid-tenant-id' | 'invalid-tenant-type' | 'invalid-testing-phone-number' | 'invalid-uid' | 'invalid-user-import' | 'invalid-tokens-valid-after-time' | 'mismatching-tenant-id' | 'missing-android-package-name' | 'missing-config' | 'missing-continue-uri' | 'missing-display-name' | 'missing-email' | 'missing-ios-bundle-id' | 'missing-issuer' | 'missing-hash-algorithm' | 'missing-oauth-client-id' | 'missing-oauth-client-secret' | 'missing-provider-id' | 'missing-saml-relying-party-config' | 'test-phone-number-limit-exceeded' | 'maximum-user-count-exceeded' | 'missing-uid' | 'operation-not-allowed' | 'phone-number-already-exists' | 'project-not-found' | 'insufficient-permission' | 'quota-exceeded' | 'second-factor-limit-exceeded' | 'second-factor-uid-already-exists' | 'session-cookie-expired' | 'session-cookie-revoked' | 'tenant-not-found' | 'uid-already-exists' | 'unauthorized-continue-uri' | 'unsupported-first-factor' | 'unsupported-second-factor' | 'unsupported-tenant-operation' | 'unverified-email' | 'user-not-found' | 'not-found' | 'user-disabled' | 'user-not-disabled' | 'invalid-recaptcha-action' | 'invalid-recaptcha-enforcement-state' | 'racaptcha-not-enabled'; // @public export type AuthFactorType = 'phone'; diff --git a/src/auth/action-code-settings-builder.ts b/src/auth/action-code-settings-builder.ts index 5ffd72d048..f10fda20c1 100644 --- a/src/auth/action-code-settings-builder.ts +++ b/src/auth/action-code-settings-builder.ts @@ -15,7 +15,7 @@ */ import * as validator from '../utils/validator'; -import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; /** * This is the interface that defines the required continue/state URL with @@ -147,17 +147,17 @@ export class ActionCodeSettingsBuilder { constructor(actionCodeSettings: ActionCodeSettings) { if (!validator.isNonNullObject(actionCodeSettings)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings" must be a non-null object.', ); } if (typeof actionCodeSettings.url === 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.MISSING_CONTINUE_URI, + authClientErrorCode.MISSING_CONTINUE_URI, ); } else if (!validator.isURL(actionCodeSettings.url)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONTINUE_URI, + authClientErrorCode.INVALID_CONTINUE_URI, ); } this.continueUrl = actionCodeSettings.url; @@ -165,7 +165,7 @@ export class ActionCodeSettingsBuilder { if (typeof actionCodeSettings.handleCodeInApp !== 'undefined' && !validator.isBoolean(actionCodeSettings.handleCodeInApp)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.handleCodeInApp" must be a boolean.', ); } @@ -174,7 +174,7 @@ export class ActionCodeSettingsBuilder { if (typeof actionCodeSettings.dynamicLinkDomain !== 'undefined' && !validator.isNonEmptyString(actionCodeSettings.dynamicLinkDomain)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_DYNAMIC_LINK_DOMAIN, + authClientErrorCode.INVALID_DYNAMIC_LINK_DOMAIN, ); } this.dynamicLinkDomain = actionCodeSettings.dynamicLinkDomain; @@ -182,7 +182,7 @@ export class ActionCodeSettingsBuilder { if (typeof actionCodeSettings.linkDomain !== 'undefined' && !validator.isNonEmptyString(actionCodeSettings.linkDomain)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HOSTING_LINK_DOMAIN, + authClientErrorCode.INVALID_HOSTING_LINK_DOMAIN, ); } this.linkDomain = actionCodeSettings.linkDomain; @@ -190,16 +190,16 @@ export class ActionCodeSettingsBuilder { if (typeof actionCodeSettings.iOS !== 'undefined') { if (!validator.isNonNullObject(actionCodeSettings.iOS)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.iOS" must be a valid non-null object.', ); } else if (typeof actionCodeSettings.iOS.bundleId === 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.MISSING_IOS_BUNDLE_ID, + authClientErrorCode.MISSING_IOS_BUNDLE_ID, ); } else if (!validator.isNonEmptyString(actionCodeSettings.iOS.bundleId)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.iOS.bundleId" must be a valid non-empty string.', ); } @@ -209,28 +209,28 @@ export class ActionCodeSettingsBuilder { if (typeof actionCodeSettings.android !== 'undefined') { if (!validator.isNonNullObject(actionCodeSettings.android)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.android" must be a valid non-null object.', ); } else if (typeof actionCodeSettings.android.packageName === 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.MISSING_ANDROID_PACKAGE_NAME, + authClientErrorCode.MISSING_ANDROID_PACKAGE_NAME, ); } else if (!validator.isNonEmptyString(actionCodeSettings.android.packageName)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.android.packageName" must be a valid non-empty string.', ); } else if (typeof actionCodeSettings.android.minimumVersion !== 'undefined' && !validator.isNonEmptyString(actionCodeSettings.android.minimumVersion)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.android.minimumVersion" must be a valid non-empty string.', ); } else if (typeof actionCodeSettings.android.installApp !== 'undefined' && !validator.isBoolean(actionCodeSettings.android.installApp)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.android.installApp" must be a valid boolean.', ); } diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 5bf360a18a..04eb358a65 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -20,7 +20,8 @@ import * as validator from '../utils/validator'; import { App } from '../app/index'; import { FirebaseApp } from '../app/firebase-app'; import { deepCopy, deepExtend } from '../utils/deep-copy'; -import { AuthClientErrorCode, FirebaseAuthError, toHttpResponse } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; +import { toHttpResponse } from '../utils/error'; import { ApiSettings, AuthorizedHttpClient, HttpRequestConfig, RequestResponseError, RequestResponse, } from '../utils/api-request'; @@ -171,7 +172,7 @@ class AuthResourceUrlBuilder { .then((projectId) => { if (!validator.isNonEmptyString(projectId)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CREDENTIAL, + 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.', @@ -265,14 +266,14 @@ function validateAuthFactorInfo(request: AuthFactorInfo): void { if (typeof request.mfaEnrollmentId !== 'undefined' && !validator.isNonEmptyString(request.mfaEnrollmentId)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_UID, + 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 FirebaseAuthError( - AuthClientErrorCode.INVALID_DISPLAY_NAME, + authClientErrorCode.INVALID_DISPLAY_NAME, `The second factor "displayName" for "${authFactorInfoIdentifier}" must be a valid string.`, ); } @@ -280,7 +281,7 @@ function validateAuthFactorInfo(request: AuthFactorInfo): void { if (typeof request.enrolledAt !== 'undefined' && !validator.isISODateString(request.enrolledAt)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ENROLLMENT_TIME, + authClientErrorCode.INVALID_ENROLLMENT_TIME, `The second factor "enrollmentTime" for "${authFactorInfoIdentifier}" must be a valid ` + 'UTC date string.'); } @@ -289,7 +290,7 @@ function validateAuthFactorInfo(request: AuthFactorInfo): void { // phoneNumber should be a string and a valid phone number. if (!validator.isPhoneNumber(request.phoneInfo)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_PHONE_NUMBER, + authClientErrorCode.INVALID_PHONE_NUMBER, `The second factor "phoneNumber" for "${authFactorInfoIdentifier}" must be a non-empty ` + 'E.164 standard compliant identifier string.'); } @@ -297,7 +298,7 @@ function validateAuthFactorInfo(request: AuthFactorInfo): void { // 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 FirebaseAuthError( - AuthClientErrorCode.INVALID_ENROLLED_FACTORS, + authClientErrorCode.INVALID_ENROLLED_FACTORS, 'MFAInfo object provided is invalid.'); } } @@ -325,12 +326,12 @@ function validateProviderUserInfo(request: any): void { } } if (!validator.isNonEmptyString(request.providerId)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID); } if (typeof request.displayName !== 'undefined' && typeof request.displayName !== 'string') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_DISPLAY_NAME, + authClientErrorCode.INVALID_DISPLAY_NAME, `The provider "displayName" for "${request.providerId}" must be a valid string.`, ); } @@ -338,14 +339,14 @@ function validateProviderUserInfo(request: any): void { // 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 FirebaseAuthError( - AuthClientErrorCode.INVALID_UID, + 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 FirebaseAuthError( - AuthClientErrorCode.INVALID_EMAIL, + authClientErrorCode.INVALID_EMAIL, `The provider "email" for "${request.providerId}" must be a valid email string.`, ); } @@ -355,7 +356,7 @@ function validateProviderUserInfo(request: any): void { // 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 FirebaseAuthError( - AuthClientErrorCode.INVALID_PHOTO_URL, + authClientErrorCode.INVALID_PHOTO_URL, `The provider "photoURL" for "${request.providerId}" must be a valid URL string.`, ); } @@ -410,79 +411,79 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat } if (typeof request.tenantId !== 'undefined' && !validator.isNonEmptyString(request.tenantId)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_TENANT_ID); + throw new FirebaseAuthError(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 FirebaseAuthError(AuthClientErrorCode.INVALID_DISPLAY_NAME); + throw new FirebaseAuthError(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 FirebaseAuthError(AuthClientErrorCode.INVALID_UID); + throw new FirebaseAuthError(authClientErrorCode.INVALID_UID); } // email should be a string and a valid email. if (typeof request.email !== 'undefined' && !validator.isEmail(request.email)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL); + throw new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL); } // phoneNumber should be a string and a valid phone number. if (typeof request.phoneNumber !== 'undefined' && !validator.isPhoneNumber(request.phoneNumber)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER); + throw new FirebaseAuthError(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 FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD); + throw new FirebaseAuthError(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 FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PASSWORD); } // emailVerified should be a boolean. if (typeof request.emailVerified !== 'undefined' && typeof request.emailVerified !== 'boolean') { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL_VERIFIED); + throw new FirebaseAuthError(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 FirebaseAuthError(AuthClientErrorCode.INVALID_PHOTO_URL); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PHOTO_URL); } // disabled should be a boolean. if (typeof request.disabled !== 'undefined' && typeof request.disabled !== 'boolean') { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_DISABLED_FIELD); + throw new FirebaseAuthError(authClientErrorCode.INVALID_DISABLED_FIELD); } // validSince should be a number. if (typeof request.validSince !== 'undefined' && !validator.isNumber(request.validSince)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_TOKENS_VALID_AFTER_TIME); + throw new FirebaseAuthError(authClientErrorCode.INVALID_TOKENS_VALID_AFTER_TIME); } // createdAt should be a number. if (typeof request.createdAt !== 'undefined' && !validator.isNumber(request.createdAt)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_CREATION_TIME); + throw new FirebaseAuthError(authClientErrorCode.INVALID_CREATION_TIME); } // lastSignInAt should be a number. if (typeof request.lastLoginAt !== 'undefined' && !validator.isNumber(request.lastLoginAt)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_LAST_SIGN_IN_TIME); + throw new FirebaseAuthError(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 FirebaseAuthError(AuthClientErrorCode.INVALID_DISABLED_FIELD); + throw new FirebaseAuthError(authClientErrorCode.INVALID_DISABLED_FIELD); } // customAttributes should be stringified JSON with no blacklisted claims. // The payload should not exceed 1KB. @@ -494,7 +495,7 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat // 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 FirebaseAuthError(AuthClientErrorCode.INVALID_CLAIMS, error.message); + throw new FirebaseAuthError(authClientErrorCode.INVALID_CLAIMS, error.message); } const invalidClaims: string[] = []; // Check for any invalid claims. @@ -506,7 +507,7 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat // Throw an error if an invalid claim is detected. if (invalidClaims.length > 0) { throw new FirebaseAuthError( - AuthClientErrorCode.FORBIDDEN_CLAIM, + authClientErrorCode.FORBIDDEN_CLAIM, invalidClaims.length > 1 ? `Developer claims "${invalidClaims.join('", "')}" are reserved and cannot be specified.` : `Developer claim "${invalidClaims[0]}" is reserved and cannot be specified.`, @@ -515,7 +516,7 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat // Check claims payload does not exceed maxmimum size. if (request.customAttributes.length > MAX_CLAIMS_PAYLOAD_SIZE) { throw new FirebaseAuthError( - AuthClientErrorCode.CLAIMS_TOO_LARGE, + authClientErrorCode.CLAIMS_TOO_LARGE, `Developer claims payload should not exceed ${MAX_CLAIMS_PAYLOAD_SIZE} characters.`, ); } @@ -523,17 +524,17 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat // passwordHash has to be a base64 encoded string. if (typeof request.passwordHash !== 'undefined' && !validator.isString(request.passwordHash)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD_HASH); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PASSWORD_HASH); } // salt has to be a base64 encoded string. if (typeof request.salt !== 'undefined' && !validator.isString(request.salt)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD_SALT); + throw new FirebaseAuthError(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 FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_DATA); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_DATA); } else if (validator.isArray(request.providerUserInfo)) { request.providerUserInfo.forEach((providerUserInfoEntry: any) => { validateProviderUserInfo(providerUserInfoEntry); @@ -556,7 +557,7 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat } if (enrollments) { if (!validator.isArray(enrollments)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ENROLLED_FACTORS); + throw new FirebaseAuthError(authClientErrorCode.INVALID_ENROLLED_FACTORS); } enrollments.forEach((authFactorInfoEntry: AuthFactorInfo) => { validateAuthFactorInfo(authFactorInfoEntry); @@ -576,13 +577,13 @@ export const FIREBASE_AUTH_CREATE_SESSION_COOKIE = .setRequestValidator((request: any) => { // Validate the ID token is a non-empty string. if (!validator.isNonEmptyString(request.idToken)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ID_TOKEN); + throw new FirebaseAuthError(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 FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION); + throw new FirebaseAuthError(authClientErrorCode.INVALID_SESSION_COOKIE_DURATION); } }) // Set response validator. @@ -590,7 +591,7 @@ export const FIREBASE_AUTH_CREATE_SESSION_COOKIE = // Response should always contain the session cookie. if (!validator.isNonEmptyString(response.data?.sessionCookie)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, httpResponse: toHttpResponse(response), }); } @@ -616,14 +617,14 @@ export const FIREBASE_AUTH_DOWNLOAD_ACCOUNT = new ApiSettings('/accounts:batchGe // Validate next page token. if (typeof request.nextPageToken !== 'undefined' && !validator.isNonEmptyString(request.nextPageToken)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PAGE_TOKEN); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PAGE_TOKEN); } // Validate max results. if (!validator.isNumber(request.maxResults) || request.maxResults <= 0 || request.maxResults > MAX_DOWNLOAD_ACCOUNT_PAGE_SIZE) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive integer that does not exceed ' + `${MAX_DOWNLOAD_ACCOUNT_PAGE_SIZE}.`, ); @@ -650,7 +651,7 @@ export const FIREBASE_AUTH_GET_ACCOUNT_INFO = new ApiSettings('/accounts:lookup' .setRequestValidator((request: GetAccountInfoRequest) => { if (!request.localId && !request.email && !request.phoneNumber && !request.federatedUserId) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier'); } }) @@ -659,7 +660,7 @@ export const FIREBASE_AUTH_GET_ACCOUNT_INFO = new ApiSettings('/accounts:lookup' const data = response.data; if (!data.users || !data.users.length) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.USER_NOT_FOUND, + ...authClientErrorCode.USER_NOT_FOUND, httpResponse: toHttpResponse(response), }); } @@ -676,7 +677,7 @@ export const FIREBASE_AUTH_GET_ACCOUNTS_INFO = new ApiSettings('/accounts:lookup .setRequestValidator((request: GetAccountInfoRequest) => { if (!request.localId && !request.email && !request.phoneNumber && !request.federatedUserId) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier'); } }); @@ -692,7 +693,7 @@ export const FIREBASE_AUTH_DELETE_ACCOUNT = new ApiSettings('/accounts:delete', .setRequestValidator((request: any) => { if (!request.localId) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier'); } }); @@ -719,12 +720,12 @@ export const FIREBASE_AUTH_BATCH_DELETE_ACCOUNTS = new ApiSettings('/accounts:ba .setRequestValidator((request: BatchDeleteAccountsRequest) => { if (!request.localIds) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifiers'); } if (typeof request.force === 'undefined' || request.force !== true) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing force=true field'); } }) @@ -734,14 +735,14 @@ export const FIREBASE_AUTH_BATCH_DELETE_ACCOUNTS = new ApiSettings('/accounts:ba errors.forEach((batchDeleteErrorInfo: BatchDeleteErrorInfo) => { if (typeof batchDeleteErrorInfo.index === 'undefined') { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Server BatchDeleteAccountResponse is missing an errors.index field', httpResponse: toHttpResponse(response), }); } if (!batchDeleteErrorInfo.localId) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Server BatchDeleteAccountResponse is missing an errors.localId field', httpResponse: toHttpResponse(response), }); @@ -761,13 +762,13 @@ export const FIREBASE_AUTH_SET_ACCOUNT_INFO = new ApiSettings('/accounts:update' // localId is a required parameter. if (typeof request.localId === 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + 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 FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"tenantId" is an invalid "UpdateRequest" property.'); } validateCreateEditRequest(request, WriteOperationType.Update); @@ -778,7 +779,7 @@ export const FIREBASE_AUTH_SET_ACCOUNT_INFO = new ApiSettings('/accounts:update' // If the localId is not returned, then the request failed. if (!data?.localId) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.USER_NOT_FOUND, + ...authClientErrorCode.USER_NOT_FOUND, httpResponse: toHttpResponse(response), }); } @@ -796,21 +797,21 @@ export const FIREBASE_AUTH_SIGN_UP_NEW_USER = new ApiSettings('/accounts', 'POST // signupNewUser does not support customAttributes. if (typeof request.customAttributes !== 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"customAttributes" cannot be set when creating a new user.', ); } // signupNewUser does not support validSince. if (typeof request.validSince !== 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + 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 FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"tenantId" is an invalid "CreateRequest" property.'); } validateCreateEditRequest(request, WriteOperationType.Create); @@ -821,7 +822,7 @@ export const FIREBASE_AUTH_SIGN_UP_NEW_USER = new ApiSettings('/accounts', 'POST // If the localId is not returned, then the request failed. if (!data?.localId) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to create new user', httpResponse: toHttpResponse(response), }); @@ -833,17 +834,17 @@ const FIREBASE_AUTH_GET_OOB_CODE = new ApiSettings('/accounts:sendOobCode', 'POS .setRequestValidator((request: any) => { if (!validator.isEmail(request.email)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_EMAIL, + authClientErrorCode.INVALID_EMAIL, ); } if (typeof request.newEmail !== 'undefined' && !validator.isEmail(request.newEmail)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_NEW_EMAIL, + authClientErrorCode.INVALID_NEW_EMAIL, ); } if (EMAIL_ACTION_REQUEST_TYPES.indexOf(request.requestType) === -1) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, `"${request.requestType}" is not a supported email action request type.`, ); } @@ -854,7 +855,7 @@ const FIREBASE_AUTH_GET_OOB_CODE = new ApiSettings('/accounts:sendOobCode', 'POS // If the oobLink is not returned, then the request failed. if (!data?.oobLink) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to create the email action link', httpResponse: toHttpResponse(response), }); @@ -873,7 +874,7 @@ const GET_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}', 'G // Response should always contain the OIDC provider resource name. if (!validator.isNonEmptyString(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to get OIDC configuration', httpResponse: toHttpResponse(response), }); @@ -899,7 +900,7 @@ const CREATE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs?oauthIdpConfig // Response should always contain the OIDC provider resource name. if (!validator.isNonEmptyString(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to create new OIDC configuration', httpResponse: toHttpResponse(response), }); @@ -918,7 +919,7 @@ const UPDATE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}?u // Response should always contain the configuration resource name. if (!validator.isNonEmptyString(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to update OIDC configuration', httpResponse: toHttpResponse(response), }); @@ -936,14 +937,14 @@ const LIST_OAUTH_IDP_CONFIGS = new ApiSettings('/oauthIdpConfigs', 'GET') // Validate next page token. if (typeof request.pageToken !== 'undefined' && !validator.isNonEmptyString(request.pageToken)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PAGE_TOKEN); + throw new FirebaseAuthError(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 FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive integer that does not exceed ' + `${MAX_LIST_PROVIDER_CONFIGURATION_PAGE_SIZE}.`, ); @@ -962,7 +963,7 @@ const GET_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{providerId // Response should always contain the SAML provider resource name. if (!validator.isNonEmptyString(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to get SAML configuration', httpResponse: toHttpResponse(response), }); @@ -988,7 +989,7 @@ const CREATE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs?inboundS // Response should always contain the SAML provider resource name. if (!validator.isNonEmptyString(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to create new SAML configuration', httpResponse: toHttpResponse(response), }); @@ -1007,7 +1008,7 @@ const UPDATE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{provide // Response should always contain the configuration resource name. if (!validator.isNonEmptyString(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to update SAML configuration', httpResponse: toHttpResponse(response), }); @@ -1025,14 +1026,14 @@ const LIST_INBOUND_SAML_CONFIGS = new ApiSettings('/inboundSamlConfigs', 'GET') // Validate next page token. if (typeof request.pageToken !== 'undefined' && !validator.isNonEmptyString(request.pageToken)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PAGE_TOKEN); + throw new FirebaseAuthError(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 FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive integer that does not exceed ' + `${MAX_LIST_PROVIDER_CONFIGURATION_PAGE_SIZE}.`, ); @@ -1060,7 +1061,7 @@ export abstract class AbstractAuthRequestHandler { private static addUidToRequest(id: UidIdentifier, request: GetAccountInfoRequest): GetAccountInfoRequest { if (!validator.isUid(id.uid)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_UID); + throw new FirebaseAuthError(authClientErrorCode.INVALID_UID); } request.localId ? request.localId.push(id.uid) : request.localId = [id.uid]; return request; @@ -1068,7 +1069,7 @@ export abstract class AbstractAuthRequestHandler { private static addEmailToRequest(id: EmailIdentifier, request: GetAccountInfoRequest): GetAccountInfoRequest { if (!validator.isEmail(id.email)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL); + throw new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL); } request.email ? request.email.push(id.email) : request.email = [id.email]; return request; @@ -1076,7 +1077,7 @@ export abstract class AbstractAuthRequestHandler { private static addPhoneToRequest(id: PhoneIdentifier, request: GetAccountInfoRequest): GetAccountInfoRequest { if (!validator.isPhoneNumber(id.phoneNumber)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER); } request.phoneNumber ? request.phoneNumber.push(id.phoneNumber) : request.phoneNumber = [id.phoneNumber]; return request; @@ -1084,10 +1085,10 @@ export abstract class AbstractAuthRequestHandler { private static addProviderToRequest(id: ProviderIdentifier, request: GetAccountInfoRequest): GetAccountInfoRequest { if (!validator.isNonEmptyString(id.providerId)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID); } if (!validator.isNonEmptyString(id.providerUid)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_UID); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_UID); } const federatedUserId = { providerId: id.providerId, @@ -1106,7 +1107,7 @@ export abstract class AbstractAuthRequestHandler { constructor(protected readonly app: App) { if (typeof app !== 'object' || app === null || !('options' in app)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'First argument passed to admin.auth() must be a valid Firebase app instance.', ); } @@ -1142,7 +1143,7 @@ export abstract class AbstractAuthRequestHandler { */ public getAccountInfoByUid(uid: string): Promise { if (!validator.isUid(uid)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_UID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_UID)); } const request = { @@ -1159,7 +1160,7 @@ export abstract class AbstractAuthRequestHandler { */ public getAccountInfoByEmail(email: string): Promise { if (!validator.isEmail(email)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL)); } const request = { @@ -1176,7 +1177,7 @@ export abstract class AbstractAuthRequestHandler { */ public getAccountInfoByPhoneNumber(phoneNumber: string): Promise { if (!validator.isPhoneNumber(phoneNumber)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER)); } const request = { @@ -1187,7 +1188,7 @@ export abstract class AbstractAuthRequestHandler { public getAccountInfoByFederatedUid(providerId: string, rawId: string): Promise { if (!validator.isNonEmptyString(providerId) || !validator.isNonEmptyString(rawId)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID); } const request = { @@ -1213,7 +1214,7 @@ export abstract class AbstractAuthRequestHandler { return Promise.resolve({ users: [] }); } else if (identifiers.length > MAX_GET_ACCOUNTS_BATCH_SIZE) { throw new FirebaseAuthError( - AuthClientErrorCode.MAXIMUM_USER_COUNT_EXCEEDED, + authClientErrorCode.MAXIMUM_USER_COUNT_EXCEEDED, '`identifiers` parameter must have <= ' + MAX_GET_ACCOUNTS_BATCH_SIZE + ' entries.'); } @@ -1230,7 +1231,7 @@ export abstract class AbstractAuthRequestHandler { request = AbstractAuthRequestHandler.addProviderToRequest(id, request); } else { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Unrecognized identifier: ' + id); } } @@ -1300,7 +1301,7 @@ export abstract class AbstractAuthRequestHandler { // Fail quickly if more users than allowed are to be imported. if (validator.isArray(users) && users.length > MAX_UPLOAD_ACCOUNT_BATCH_SIZE) { throw new FirebaseAuthError( - AuthClientErrorCode.MAXIMUM_USER_COUNT_EXCEEDED, + authClientErrorCode.MAXIMUM_USER_COUNT_EXCEEDED, `A maximum of ${MAX_UPLOAD_ACCOUNT_BATCH_SIZE} users can be imported at once.`, ); } @@ -1326,7 +1327,7 @@ export abstract class AbstractAuthRequestHandler { */ public deleteAccount(uid: string): Promise { if (!validator.isUid(uid)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_UID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_UID)); } const request = { @@ -1340,7 +1341,7 @@ export abstract class AbstractAuthRequestHandler { return Promise.resolve({}); } else if (uids.length > MAX_DELETE_ACCOUNTS_BATCH_SIZE) { throw new FirebaseAuthError( - AuthClientErrorCode.MAXIMUM_USER_COUNT_EXCEEDED, + authClientErrorCode.MAXIMUM_USER_COUNT_EXCEEDED, '`uids` parameter must have <= ' + MAX_DELETE_ACCOUNTS_BATCH_SIZE + ' entries.'); } @@ -1351,7 +1352,7 @@ export abstract class AbstractAuthRequestHandler { uids.forEach((uid) => { if (!validator.isUid(uid)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_UID); + throw new FirebaseAuthError(authClientErrorCode.INVALID_UID); } request.localIds!.push(uid); }); @@ -1370,11 +1371,11 @@ export abstract class AbstractAuthRequestHandler { public setCustomUserClaims(uid: string, customUserClaims: object | null): Promise { // Validate user UID. if (!validator.isUid(uid)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_UID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_UID)); } else if (!validator.isObject(customUserClaims)) { return Promise.reject( new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'CustomUserClaims argument must be an object or null.', ), ); @@ -1404,11 +1405,11 @@ export abstract class AbstractAuthRequestHandler { */ public updateExistingAccount(uid: string, properties: UpdateRequest): Promise { if (!validator.isUid(uid)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_UID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_UID)); } else if (!validator.isNonNullObject(properties)) { return Promise.reject( new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Properties argument must be a non-null object.', ), ); @@ -1417,25 +1418,25 @@ export abstract class AbstractAuthRequestHandler { // validateProviderUserInfo. It may be possible to refactor a bit. if (!validator.isNonEmptyString(properties.providerToLink.providerId)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'providerToLink.providerId of properties argument must be a non-empty string.'); } if (!validator.isNonEmptyString(properties.providerToLink.uid)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'providerToLink.uid of properties argument must be a non-empty string.'); } } else if (typeof properties.providersToUnlink !== 'undefined') { if (!validator.isArray(properties.providersToUnlink)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'providersToUnlink of properties argument must be an array of strings.'); } properties.providersToUnlink.forEach((providerId) => { if (!validator.isNonEmptyString(providerId)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'providersToUnlink of properties argument must be an array of strings.'); } }); @@ -1548,7 +1549,7 @@ export abstract class AbstractAuthRequestHandler { public revokeRefreshTokens(uid: string): Promise { // Validate user UID. if (!validator.isUid(uid)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_UID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_UID)); } const request: any = { localId: uid, @@ -1572,7 +1573,7 @@ export abstract class AbstractAuthRequestHandler { if (!validator.isNonNullObject(properties)) { return Promise.reject( new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Properties argument must be a non-null object.', ), ); @@ -1605,11 +1606,11 @@ export abstract class AbstractAuthRequestHandler { // They will automatically be provisioned server side. if ('enrollmentTime' in multiFactorInfo) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"enrollmentTime" is not supported when adding second factors via "createUser()"'); } else if ('uid' in multiFactorInfo) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"uid" is not supported when adding second factors via "createUser()"'); } mfaInfo.push(convertMultiFactorInfoToServerFormat(multiFactorInfo)); @@ -1656,7 +1657,7 @@ export abstract class AbstractAuthRequestHandler { if (typeof actionCodeSettings === 'undefined' && requestType === 'EMAIL_SIGNIN') { return Promise.reject( new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, "`actionCodeSettings` is required when `requestType` === 'EMAIL_SIGNIN'", ), ); @@ -1672,7 +1673,7 @@ export abstract class AbstractAuthRequestHandler { if (requestType === 'VERIFY_AND_CHANGE_EMAIL' && typeof newEmail === 'undefined') { return Promise.reject( new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, "`newEmail` is required when `requestType` === 'VERIFY_AND_CHANGE_EMAIL'", ), ); @@ -1692,7 +1693,7 @@ export abstract class AbstractAuthRequestHandler { */ public getOAuthIdpConfig(providerId: string): Promise { if (!OIDCConfig.isProviderId(providerId)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID)); } return this.invokeRequestHandler(this.getProjectConfigUrlBuilder(), GET_OAUTH_IDP_CONFIG, {}, { providerId }); } @@ -1738,7 +1739,7 @@ export abstract class AbstractAuthRequestHandler { */ public deleteOAuthIdpConfig(providerId: string): Promise { if (!OIDCConfig.isProviderId(providerId)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID)); } return this.invokeRequestHandler(this.getProjectConfigUrlBuilder(), DELETE_OAUTH_IDP_CONFIG, {}, { providerId }) .then(() => { @@ -1767,7 +1768,7 @@ export abstract class AbstractAuthRequestHandler { .then((response: any) => { if (!OIDCConfig.getProviderIdFromResourceName(response.name)) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create new OIDC provider configuration'); } return response as OIDCConfigServerResponse; @@ -1785,7 +1786,7 @@ export abstract class AbstractAuthRequestHandler { public updateOAuthIdpConfig( providerId: string, options: OIDCUpdateAuthProviderRequest): Promise { if (!OIDCConfig.isProviderId(providerId)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID)); } // Construct backend request. let request: OIDCConfigServerRequest; @@ -1800,7 +1801,7 @@ export abstract class AbstractAuthRequestHandler { .then((response: any) => { if (!OIDCConfig.getProviderIdFromResourceName(response.name)) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to update OIDC provider configuration'); } return response as OIDCConfigServerResponse; @@ -1815,7 +1816,7 @@ export abstract class AbstractAuthRequestHandler { */ public getInboundSamlConfig(providerId: string): Promise { if (!SAMLConfig.isProviderId(providerId)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID)); } return this.invokeRequestHandler(this.getProjectConfigUrlBuilder(), GET_INBOUND_SAML_CONFIG, {}, { providerId }); } @@ -1861,7 +1862,7 @@ export abstract class AbstractAuthRequestHandler { */ public deleteInboundSamlConfig(providerId: string): Promise { if (!SAMLConfig.isProviderId(providerId)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID)); } return this.invokeRequestHandler(this.getProjectConfigUrlBuilder(), DELETE_INBOUND_SAML_CONFIG, {}, { providerId }) .then(() => { @@ -1890,7 +1891,7 @@ export abstract class AbstractAuthRequestHandler { .then((response: any) => { if (!SAMLConfig.getProviderIdFromResourceName(response.name)) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create new SAML provider configuration'); } return response as SAMLConfigServerResponse; @@ -1908,7 +1909,7 @@ export abstract class AbstractAuthRequestHandler { public updateInboundSamlConfig( providerId: string, options: SAMLUpdateAuthProviderRequest): Promise { if (!SAMLConfig.isProviderId(providerId)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID)); } // Construct backend request. let request: SAMLConfigServerRequest; @@ -1923,7 +1924,7 @@ export abstract class AbstractAuthRequestHandler { .then((response: any) => { if (!SAMLConfig.getProviderIdFromResourceName(response.name)) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to update SAML provider configuration'); } return response as SAMLConfigServerResponse; @@ -1974,7 +1975,7 @@ export abstract class AbstractAuthRequestHandler { if (!errorCode) { // Fallback for unexpected server error responses without a parseable error code. throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'An internal error occurred while attempting to extract the errorcode from the error.', cause: err, httpResponse: toHttpResponse(err.response), @@ -2024,7 +2025,7 @@ const GET_PROJECT_CONFIG = new ApiSettings('/config', 'GET') // Response should always contain at least the config name. if (!validator.isNonEmptyString(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to get project config', httpResponse: toHttpResponse(response), }); @@ -2038,7 +2039,7 @@ const UPDATE_PROJECT_CONFIG = new ApiSettings('/config?updateMask={updateMask}', // Response should always contain at least the config name. if (!validator.isNonEmptyString(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to update project config', httpResponse: toHttpResponse(response), }); @@ -2053,7 +2054,7 @@ const GET_TENANT = new ApiSettings('/tenants/{tenantId}', 'GET') // Response should always contain at least the tenant name. if (!validator.isNonEmptyString(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to get tenant', httpResponse: toHttpResponse(response), }); @@ -2072,7 +2073,7 @@ const UPDATE_TENANT = new ApiSettings('/tenants/{tenantId}?updateMask={updateMas if (!validator.isNonEmptyString(data?.name) || !Tenant.getTenantIdFromResourceName(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to update tenant', httpResponse: toHttpResponse(response), }); @@ -2086,14 +2087,14 @@ const LIST_TENANTS = new ApiSettings('/tenants', 'GET') // Validate next page token. if (typeof request.pageToken !== 'undefined' && !validator.isNonEmptyString(request.pageToken)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PAGE_TOKEN); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PAGE_TOKEN); } // Validate max results. if (!validator.isNumber(request.pageSize) || request.pageSize <= 0 || request.pageSize > MAX_LIST_TENANT_PAGE_SIZE) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive non-zero number that does not exceed ' + `the allowed ${MAX_LIST_TENANT_PAGE_SIZE}.`, ); @@ -2109,7 +2110,7 @@ const CREATE_TENANT = new ApiSettings('/tenants', 'POST') if (!validator.isNonEmptyString(data?.name) || !Tenant.getTenantIdFromResourceName(data?.name)) { throw new FirebaseAuthError({ - ...AuthClientErrorCode.INTERNAL_ERROR, + ...authClientErrorCode.INTERNAL_ERROR, message: 'INTERNAL ASSERT FAILED: Unable to create tenant', httpResponse: toHttpResponse(response), }); @@ -2188,7 +2189,7 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { */ public getTenant(tenantId: string): Promise { if (!validator.isNonEmptyString(tenantId)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_TENANT_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_TENANT_ID)); } return this.invokeRequestHandler(this.authResourceUrlBuilder, GET_TENANT, {}, { tenantId }) .then((response: any) => { @@ -2238,7 +2239,7 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { */ public deleteTenant(tenantId: string): Promise { if (!validator.isNonEmptyString(tenantId)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_TENANT_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_TENANT_ID)); } return this.invokeRequestHandler(this.authResourceUrlBuilder, DELETE_TENANT, undefined, { tenantId }) .then(() => { @@ -2274,7 +2275,7 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { */ public updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise { if (!validator.isNonEmptyString(tenantId)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_TENANT_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_TENANT_ID)); } try { // Construct backend request. @@ -2348,7 +2349,7 @@ export class TenantAwareAuthRequestHandler extends AbstractAuthRequestHandler { if (validator.isNonEmptyString(user.tenantId) && user.tenantId !== this.tenantId) { throw new FirebaseAuthError( - AuthClientErrorCode.MISMATCHING_TENANT_ID, + authClientErrorCode.MISMATCHING_TENANT_ID, `UserRecord of index "${index}" has mismatching tenant ID "${user.tenantId}"`); } }); diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 1dd5565a88..3310a8441e 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -16,7 +16,7 @@ import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; -import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; /** * Interface representing base properties of a user-enrolled second factor for a @@ -605,7 +605,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { }; if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"MultiFactorConfig" must be a non-null object.', ); } @@ -613,7 +613,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid MultiFactorConfig parameter.`, ); } @@ -623,7 +623,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { options.state !== 'ENABLED' && options.state !== 'DISABLED') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"MultiFactorConfig.state" must be either "ENABLED" or "DISABLED".', ); } @@ -631,7 +631,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { if (typeof options.factorIds !== 'undefined') { if (!validator.isArray(options.factorIds)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"MultiFactorConfig.factorIds" must be an array of valid "AuthFactorTypes".', ); } @@ -640,7 +640,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { options.factorIds.forEach((factorId) => { if (typeof AUTH_FACTOR_CLIENT_TO_SERVER_TYPE[factorId] === 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${factorId}" is not a valid "AuthFactorType".`, ); } @@ -650,7 +650,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { if (typeof options.providerConfigs !== 'undefined') { if (!validator.isArray(options.providerConfigs)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"MultiFactorConfig.providerConfigs" must be an array of valid "MultiFactorProviderConfig."', ); } @@ -658,7 +658,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { options.providerConfigs.forEach((multiFactorProviderConfig) => { if (typeof multiFactorProviderConfig === 'undefined' || !validator.isObject(multiFactorProviderConfig)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${multiFactorProviderConfig}" is not a valid "MultiFactorProviderConfig" type.` ) } @@ -669,7 +669,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { for (const key in multiFactorProviderConfig) { if (!(key in validProviderConfigKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid ProviderConfig parameter.`, ); } @@ -678,14 +678,14 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { (multiFactorProviderConfig.state !== 'ENABLED' && multiFactorProviderConfig.state !== 'DISABLED')) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"MultiFactorConfig.providerConfigs.state" must be either "ENABLED" or "DISABLED".', ) } // Since TOTP is the only provider config available right now, not defining it will lead into an error if (typeof multiFactorProviderConfig.totpProviderConfig === 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"MultiFactorConfig.providerConfigs.totpProviderConfig" must be defined.' ) } @@ -695,7 +695,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { for (const key in multiFactorProviderConfig.totpProviderConfig) { if (!(key in validTotpProviderConfigKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid TotpProviderConfig parameter.`, ); } @@ -704,7 +704,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { if (typeof adjIntervals !== 'undefined' && (!Number.isInteger(adjIntervals) || adjIntervals < 0 || adjIntervals > 10)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"MultiFactorConfig.providerConfigs.totpProviderConfig.adjacentIntervals" must' + ' be a valid number between 0 and 10 (both inclusive).' ) @@ -724,7 +724,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { constructor(response: MultiFactorAuthServerConfig) { if (typeof response.state === 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid multi-factor configuration response'); } this.state = response.state; @@ -744,7 +744,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { (typeof providerConfig.totpProviderConfig.adjacentIntervals !== 'undefined' && typeof providerConfig.totpProviderConfig.adjacentIntervals !== 'number')) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid multi-factor configuration response'); } this.providerConfigs.push(providerConfig); @@ -773,18 +773,18 @@ export function validateTestPhoneNumbers( ): void { if (!validator.isObject(testPhoneNumbers)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"testPhoneNumbers" must be a map of phone number / code pairs.', ); } if (Object.keys(testPhoneNumbers).length > MAXIMUM_TEST_PHONE_NUMBERS) { - throw new FirebaseAuthError(AuthClientErrorCode.MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED); + throw new FirebaseAuthError(authClientErrorCode.MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED); } for (const phoneNumber in testPhoneNumbers) { // Validate phone number. if (!validator.isPhoneNumber(phoneNumber)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_TESTING_PHONE_NUMBER, + authClientErrorCode.INVALID_TESTING_PHONE_NUMBER, `"${phoneNumber}" is not a valid E.164 standard compliant phone number.` ); } @@ -793,7 +793,7 @@ export function validateTestPhoneNumbers( if (!validator.isString(testPhoneNumbers[phoneNumber]) || !/^[\d]{6}$/.test(testPhoneNumbers[phoneNumber])) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_TESTING_PHONE_NUMBER, + authClientErrorCode.INVALID_TESTING_PHONE_NUMBER, `"${testPhoneNumbers[phoneNumber]}" is not a valid 6 digit code string.` ); } @@ -860,7 +860,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { }; if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"EmailSignInConfig" must be a non-null object.', ); } @@ -868,7 +868,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, `"${key}" is not a valid EmailSignInConfig parameter.`, ); } @@ -877,14 +877,14 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { if (typeof options.enabled !== 'undefined' && !validator.isBoolean(options.enabled)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"EmailSignInConfig.enabled" must be a boolean.', ); } if (typeof options.passwordRequired !== 'undefined' && !validator.isBoolean(options.passwordRequired)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"EmailSignInConfig.passwordRequired" must be a boolean.', ); } @@ -900,7 +900,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { constructor(response: {[key: string]: any}) { if (typeof response.allowPasswordSignup === 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid email sign-in configuration response'); } this.enabled = response.allowPasswordSignup; @@ -1165,7 +1165,7 @@ export class SAMLConfig implements SAMLAuthProviderConfig { }; if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig" must be a valid non-null object.', ); } @@ -1173,7 +1173,7 @@ export class SAMLConfig implements SAMLAuthProviderConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid SAML config parameter.`, ); } @@ -1182,57 +1182,57 @@ export class SAMLConfig implements SAMLAuthProviderConfig { if (validator.isNonEmptyString(options.providerId)) { if (options.providerId.indexOf('saml.') !== 0) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_PROVIDER_ID, + authClientErrorCode.INVALID_PROVIDER_ID, '"SAMLAuthProviderConfig.providerId" must be a valid non-empty string prefixed with "saml.".', ); } } else if (!ignoreMissingFields) { // providerId is required and not provided correctly. throw new FirebaseAuthError( - !options.providerId ? AuthClientErrorCode.MISSING_PROVIDER_ID : AuthClientErrorCode.INVALID_PROVIDER_ID, + !options.providerId ? authClientErrorCode.MISSING_PROVIDER_ID : authClientErrorCode.INVALID_PROVIDER_ID, '"SAMLAuthProviderConfig.providerId" must be a valid non-empty string prefixed with "saml.".', ); } if (!(ignoreMissingFields && typeof options.idpEntityId === 'undefined') && !validator.isNonEmptyString(options.idpEntityId)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.idpEntityId" must be a valid non-empty string.', ); } if (!(ignoreMissingFields && typeof options.ssoURL === 'undefined') && !validator.isURL(options.ssoURL)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.ssoURL" must be a valid URL string.', ); } if (!(ignoreMissingFields && typeof options.rpEntityId === 'undefined') && !validator.isNonEmptyString(options.rpEntityId)) { throw new FirebaseAuthError( - !options.rpEntityId ? AuthClientErrorCode.MISSING_SAML_RELYING_PARTY_CONFIG : - AuthClientErrorCode.INVALID_CONFIG, + !options.rpEntityId ? authClientErrorCode.MISSING_SAML_RELYING_PARTY_CONFIG : + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.rpEntityId" must be a valid non-empty string.', ); } if (!(ignoreMissingFields && typeof options.callbackURL === 'undefined') && !validator.isURL(options.callbackURL)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.callbackURL" must be a valid URL string.', ); } if (!(ignoreMissingFields && typeof options.x509Certificates === 'undefined') && !validator.isArray(options.x509Certificates)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.x509Certificates" must be a valid array of X509 certificate strings.', ); } (options.x509Certificates || []).forEach((cert: string) => { if (!validator.isNonEmptyString(cert)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.x509Certificates" must be a valid array of X509 certificate strings.', ); } @@ -1240,21 +1240,21 @@ export class SAMLConfig implements SAMLAuthProviderConfig { if (typeof (options as any).enableRequestSigning !== 'undefined' && !validator.isBoolean((options as any).enableRequestSigning)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.enableRequestSigning" must be a boolean.', ); } if (typeof options.enabled !== 'undefined' && !validator.isBoolean(options.enabled)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.enabled" must be a boolean.', ); } if (typeof options.displayName !== 'undefined' && !validator.isString(options.displayName)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.displayName" must be a valid string.', ); } @@ -1277,14 +1277,14 @@ export class SAMLConfig implements SAMLAuthProviderConfig { !(validator.isString(response.name) && SAMLConfig.getProviderIdFromResourceName(response.name))) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid SAML configuration response'); } const providerId = SAMLConfig.getProviderIdFromResourceName(response.name); if (!providerId) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid SAML configuration response'); } this.providerId = providerId; @@ -1418,7 +1418,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { }; if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"OIDCAuthProviderConfig" must be a valid non-null object.', ); } @@ -1426,7 +1426,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid OIDC config parameter.`, ); } @@ -1435,48 +1435,48 @@ export class OIDCConfig implements OIDCAuthProviderConfig { if (validator.isNonEmptyString(options.providerId)) { if (options.providerId.indexOf('oidc.') !== 0) { throw new FirebaseAuthError( - !options.providerId ? AuthClientErrorCode.MISSING_PROVIDER_ID : AuthClientErrorCode.INVALID_PROVIDER_ID, + !options.providerId ? authClientErrorCode.MISSING_PROVIDER_ID : authClientErrorCode.INVALID_PROVIDER_ID, '"OIDCAuthProviderConfig.providerId" must be a valid non-empty string prefixed with "oidc.".', ); } } else if (!ignoreMissingFields) { throw new FirebaseAuthError( - !options.providerId ? AuthClientErrorCode.MISSING_PROVIDER_ID : AuthClientErrorCode.INVALID_PROVIDER_ID, + !options.providerId ? authClientErrorCode.MISSING_PROVIDER_ID : authClientErrorCode.INVALID_PROVIDER_ID, '"OIDCAuthProviderConfig.providerId" must be a valid non-empty string prefixed with "oidc.".', ); } if (!(ignoreMissingFields && typeof options.clientId === 'undefined') && !validator.isNonEmptyString(options.clientId)) { throw new FirebaseAuthError( - !options.clientId ? AuthClientErrorCode.MISSING_OAUTH_CLIENT_ID : AuthClientErrorCode.INVALID_OAUTH_CLIENT_ID, + !options.clientId ? authClientErrorCode.MISSING_OAUTH_CLIENT_ID : authClientErrorCode.INVALID_OAUTH_CLIENT_ID, '"OIDCAuthProviderConfig.clientId" must be a valid non-empty string.', ); } if (!(ignoreMissingFields && typeof options.issuer === 'undefined') && !validator.isURL(options.issuer)) { throw new FirebaseAuthError( - !options.issuer ? AuthClientErrorCode.MISSING_ISSUER : AuthClientErrorCode.INVALID_CONFIG, + !options.issuer ? authClientErrorCode.MISSING_ISSUER : authClientErrorCode.INVALID_CONFIG, '"OIDCAuthProviderConfig.issuer" must be a valid URL string.', ); } if (typeof options.enabled !== 'undefined' && !validator.isBoolean(options.enabled)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"OIDCAuthProviderConfig.enabled" must be a boolean.', ); } if (typeof options.displayName !== 'undefined' && !validator.isString(options.displayName)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"OIDCAuthProviderConfig.displayName" must be a valid string.', ); } if (typeof options.clientSecret !== 'undefined' && !validator.isNonEmptyString(options.clientSecret)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"OIDCAuthProviderConfig.clientSecret" must be a valid string.', ); } @@ -1484,7 +1484,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { Object.keys(options.responseType).forEach((key) => { if (!(key in validResponseTypes)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid OAuthResponseType parameter.`, ); } @@ -1493,7 +1493,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { const idToken = options.responseType.idToken; if (typeof idToken !== 'undefined' && !validator.isBoolean(idToken)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"OIDCAuthProviderConfig.responseType.idToken" must be a boolean.', ); } @@ -1501,14 +1501,14 @@ export class OIDCConfig implements OIDCAuthProviderConfig { if (typeof code !== 'undefined') { if (!validator.isBoolean(code)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"OIDCAuthProviderConfig.responseType.code" must be a boolean.', ); } // If code flow is enabled, client secret must be provided. if (code && typeof options.clientSecret === 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.MISSING_OAUTH_CLIENT_SECRET, + authClientErrorCode.MISSING_OAUTH_CLIENT_SECRET, 'The OAuth configuration client secret is required to enable OIDC code flow.', ); } @@ -1519,7 +1519,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { // Only one of OAuth response types can be set to true. if (allKeys > 1 && enabledCount !== 1) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_OAUTH_RESPONSETYPE, + authClientErrorCode.INVALID_OAUTH_RESPONSETYPE, 'Only exactly one OAuth responseType should be set to true.', ); } @@ -1540,14 +1540,14 @@ export class OIDCConfig implements OIDCAuthProviderConfig { !(validator.isString(response.name) && OIDCConfig.getProviderIdFromResourceName(response.name))) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid OIDC configuration response'); } const providerId = OIDCConfig.getProviderIdFromResourceName(response.name); if (!providerId) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid SAML configuration response'); } this.providerId = providerId; @@ -1649,7 +1649,7 @@ export class SmsRegionsAuthConfig { public static validate(options: SmsRegionConfig): void { if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SmsRegionConfig" must be a non-null object.', ); } @@ -1662,7 +1662,7 @@ export class SmsRegionsAuthConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid SmsRegionConfig parameter.`, ); } @@ -1671,7 +1671,7 @@ export class SmsRegionsAuthConfig { // validate mutual exclusiveness of allowByDefault and allowlistOnly if (typeof options.allowByDefault !== 'undefined' && typeof options.allowlistOnly !== 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, 'SmsRegionConfig cannot have both "allowByDefault" and "allowlistOnly" parameters.', ); } @@ -1683,7 +1683,7 @@ export class SmsRegionsAuthConfig { for (const key in options.allowByDefault) { if (!(key in allowByDefaultValidKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid SmsRegionConfig.allowByDefault parameter.`, ); } @@ -1692,7 +1692,7 @@ export class SmsRegionsAuthConfig { if (typeof options.allowByDefault.disallowedRegions !== 'undefined' && !validator.isArray(options.allowByDefault.disallowedRegions)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SmsRegionConfig.allowByDefault.disallowedRegions" must be a valid string array.', ); } @@ -1705,7 +1705,7 @@ export class SmsRegionsAuthConfig { for (const key in options.allowlistOnly) { if (!(key in allowListOnlyValidKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid SmsRegionConfig.allowlistOnly parameter.`, ); } @@ -1715,7 +1715,7 @@ export class SmsRegionsAuthConfig { if (typeof options.allowlistOnly.allowedRegions !== 'undefined' && !validator.isArray(options.allowlistOnly.allowedRegions)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SmsRegionConfig.allowlistOnly.allowedRegions" must be a valid string array.', ); } @@ -1941,7 +1941,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaConfig" must be a non-null object.', ); } @@ -1949,7 +1949,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid RecaptchaConfig parameter.`, ); } @@ -1959,7 +1959,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { if (typeof options.emailPasswordEnforcementState !== 'undefined') { if (!validator.isNonEmptyString(options.emailPasswordEnforcementState)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"RecaptchaConfig.emailPasswordEnforcementState" must be a valid non-empty string.', ); } @@ -1968,7 +1968,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { options.emailPasswordEnforcementState !== 'AUDIT' && options.emailPasswordEnforcementState !== 'ENFORCE') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaConfig.emailPasswordEnforcementState" must be either "OFF", "AUDIT" or "ENFORCE".', ); } @@ -1977,7 +1977,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { if (typeof options.phoneEnforcementState !== 'undefined') { if (!validator.isNonEmptyString(options.phoneEnforcementState)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"RecaptchaConfig.phoneEnforcementState" must be a valid non-empty string.', ); } @@ -1986,7 +1986,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { options.phoneEnforcementState !== 'AUDIT' && options.phoneEnforcementState !== 'ENFORCE') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaConfig.phoneEnforcementState" must be either "OFF", "AUDIT" or "ENFORCE".', ); } @@ -1996,7 +1996,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { // Validate array if (!validator.isArray(options.managedRules)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaConfig.managedRules" must be an array of valid "RecaptchaManagedRule".', ); } @@ -2009,7 +2009,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { if (typeof options.useAccountDefender !== 'undefined') { if (!validator.isBoolean(options.useAccountDefender)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaConfig.useAccountDefender" must be a boolean value".', ); } @@ -2018,7 +2018,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { if (typeof options.useSmsBotScore !== 'undefined') { if (!validator.isBoolean(options.useSmsBotScore)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaConfig.useSmsBotScore" must be a boolean value".', ); } @@ -2027,7 +2027,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { if (typeof options.useSmsTollFraudProtection !== 'undefined') { if (!validator.isBoolean(options.useSmsTollFraudProtection)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaConfig.useSmsTollFraudProtection" must be a boolean value".', ); } @@ -2037,7 +2037,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { // Validate array if (!validator.isArray(options.smsTollFraudManagedRules)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaConfig.smsTollFraudManagedRules" must be an array of valid "RecaptchaTollFraudManagedRule".', ); } @@ -2059,7 +2059,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { } if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaManagedRule" must be a non-null object.', ); } @@ -2067,7 +2067,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid RecaptchaManagedRule parameter.`, ); } @@ -2077,7 +2077,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { if (typeof options.action !== 'undefined' && options.action !== 'BLOCK') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaManagedRule.action" must be "BLOCK".', ); } @@ -2094,7 +2094,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { } if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaTollFraudManagedRule" must be a non-null object.', ); } @@ -2102,7 +2102,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid RecaptchaTollFraudManagedRule parameter.`, ); } @@ -2112,7 +2112,7 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { if (typeof options.action !== 'undefined' && options.action !== 'BLOCK') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"RecaptchaTollFraudManagedRule.action" must be "BLOCK".', ); } @@ -2162,7 +2162,7 @@ export class MobileLinksAuthConfig { public static validate(options: MobileLinksConfig): void { if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"MobileLinksConfig" must be a non-null object.', ); } @@ -2174,7 +2174,7 @@ export class MobileLinksAuthConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid "MobileLinksConfig" parameter.`, ); } @@ -2184,7 +2184,7 @@ export class MobileLinksAuthConfig { && options.domain !== 'HOSTING_DOMAIN' && options.domain !== 'FIREBASE_DYNAMIC_LINK_DOMAIN') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"MobileLinksConfig.domain" must be either "HOSTING_DOMAIN" or "FIREBASE_DYNAMIC_LINK_DOMAIN".', ); } @@ -2314,7 +2314,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { }; if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig" must be a non-null object.', ); } @@ -2322,7 +2322,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid PasswordPolicyConfig parameter.`, ); } @@ -2332,7 +2332,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { !(options.enforcementState === 'ENFORCE' || options.enforcementState === 'OFF')) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.enforcementState" must be either "ENFORCE" or "OFF".', ); } @@ -2340,7 +2340,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { if (typeof options.forceUpgradeOnSignin !== 'undefined') { if (!validator.isBoolean(options.forceUpgradeOnSignin)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.forceUpgradeOnSignin" must be a boolean.', ); } @@ -2349,7 +2349,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { if (typeof options.constraints !== 'undefined') { if (options.enforcementState === 'ENFORCE' && !validator.isNonNullObject(options.constraints)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.constraints" must be a non-empty object.', ); } @@ -2367,7 +2367,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { for (const key in options.constraints) { if (!(key in validCharKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid PasswordPolicyConfig.constraints parameter.`, ); } @@ -2375,21 +2375,21 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { if (typeof options.constraints.requireUppercase !== 'undefined' && !validator.isBoolean(options.constraints.requireUppercase)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.constraints.requireUppercase" must be a boolean.', ); } if (typeof options.constraints.requireLowercase !== 'undefined' && !validator.isBoolean(options.constraints.requireLowercase)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.constraints.requireLowercase" must be a boolean.', ); } if (typeof options.constraints.requireNonAlphanumeric !== 'undefined' && !validator.isBoolean(options.constraints.requireNonAlphanumeric)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.constraints.requireNonAlphanumeric"' + ' must be a boolean.', ); @@ -2397,7 +2397,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { if (typeof options.constraints.requireNumeric !== 'undefined' && !validator.isBoolean(options.constraints.requireNumeric)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.constraints.requireNumeric" must be a boolean.', ); } @@ -2405,14 +2405,14 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { options.constraints.minLength = 6; } else if (!validator.isNumber(options.constraints.minLength)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.constraints.minLength" must be a number.', ); } else { if (!(options.constraints.minLength >= 6 && options.constraints.minLength <= 30)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.constraints.minLength"' + ' must be an integer between 6 and 30, inclusive.', ); @@ -2422,14 +2422,14 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { options.constraints.maxLength = 4096; } else if (!validator.isNumber(options.constraints.maxLength)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.constraints.maxLength" must be a number.', ); } else { if (!(options.constraints.maxLength >= options.constraints.minLength && options.constraints.maxLength <= 4096)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.constraints.maxLength"' + ' must be greater than or equal to minLength and at max 4096.', ); @@ -2438,7 +2438,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { } else { if (options.enforcementState === 'ENFORCE') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"PasswordPolicyConfig.constraints" must be defined.', ); } @@ -2456,7 +2456,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { constructor(response: PasswordPolicyAuthServerConfig) { if (typeof response.passwordPolicyEnforcementState === 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid password policy configuration response'); } this.enforcementState = response.passwordPolicyEnforcementState; @@ -2525,7 +2525,7 @@ export class EmailPrivacyAuthConfig { public static validate(options: EmailPrivacyConfig): void { if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"EmailPrivacyConfig" must be a non-null object.', ); } @@ -2537,7 +2537,7 @@ export class EmailPrivacyAuthConfig { for (const key in options) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid "EmailPrivacyConfig" parameter.`, ); } @@ -2546,7 +2546,7 @@ export class EmailPrivacyAuthConfig { if (typeof options.enableImprovedEmailPrivacy !== 'undefined' && !validator.isBoolean(options.enableImprovedEmailPrivacy)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"EmailPrivacyConfig.enableImprovedEmailPrivacy" must be a valid boolean value.', ); } diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts index 25e1a3db2b..1d71210fe1 100644 --- a/src/auth/base-auth.ts +++ b/src/auth/base-auth.ts @@ -15,7 +15,8 @@ */ import { App, FirebaseArrayIndexError } from '../app'; -import { AuthClientErrorCode, ErrorInfo, FirebaseAuthError } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; +import { ErrorInfo } from '../utils/error'; import { deepCopy } from '../utils/deep-copy'; import * as validator from '../utils/validator'; @@ -217,7 +218,7 @@ export abstract class BaseAuth { if (checkRevoked || isEmulator) { return this.verifyDecodedJWTNotRevokedOrDisabled( decodedIdToken, - AuthClientErrorCode.ID_TOKEN_REVOKED); + authClientErrorCode.ID_TOKEN_REVOKED); } return decodedIdToken; }); @@ -331,7 +332,7 @@ export abstract class BaseAuth { public getUsers(identifiers: UserIdentifier[]): Promise { if (!validator.isArray(identifiers)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, '`identifiers` parameter must be an array'); + authClientErrorCode.INVALID_ARGUMENT, '`identifiers` parameter must be an array'); } return this.authRequestHandler .getAccountInfoByIdentifiers(identifiers) @@ -355,7 +356,7 @@ export abstract class BaseAuth { return !!matchingUserInfo && id.providerUid === matchingUserInfo.uid; } else { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'Unhandled identifier type'); } }); @@ -427,7 +428,7 @@ export abstract class BaseAuth { if (error.code === 'auth/user-not-found') { // Something must have happened after creating the user and then retrieving it. throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'Unable to create the user record provided.'); } throw error; @@ -477,7 +478,7 @@ export abstract class BaseAuth { public deleteUsers(uids: string[]): Promise { if (!validator.isArray(uids)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, '`uids` parameter must be an array'); + authClientErrorCode.INVALID_ARGUMENT, '`uids` parameter must be an array'); } return this.authRequestHandler.deleteAccounts(uids, /*force=*/true) .then((batchDeleteAccountsResponse) => { @@ -496,7 +497,7 @@ export abstract class BaseAuth { result.errors = batchDeleteAccountsResponse.errors.map((batchDeleteErrorInfo) => { if (batchDeleteErrorInfo.index === undefined) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'Corrupt BatchDeleteAccountsResponse detected'); } @@ -504,7 +505,7 @@ export abstract class BaseAuth { // We unconditionally set force=true, so the 'NOT_DISABLED' error // should not be possible. const code = msg && msg.startsWith('NOT_DISABLED') ? - AuthClientErrorCode.USER_NOT_DISABLED : AuthClientErrorCode.INTERNAL_ERROR; + authClientErrorCode.USER_NOT_DISABLED : authClientErrorCode.INTERNAL_ERROR; return new FirebaseAuthError(code, batchDeleteErrorInfo.message); }; @@ -544,7 +545,7 @@ export abstract class BaseAuth { if (properties.providerToLink.providerId === 'email') { if (typeof properties.email !== 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, "Both UpdateRequest.email and UpdateRequest.providerToLink.providerId='email' were set. To " + 'link to the email/password provider, only specify the UpdateRequest.email field.'); } @@ -553,7 +554,7 @@ export abstract class BaseAuth { } else if (properties.providerToLink.providerId === 'phone') { if (typeof properties.phoneNumber !== 'undefined') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, "Both UpdateRequest.phoneNumber and UpdateRequest.providerToLink.providerId='phone' were set. To " + 'link to a phone provider, only specify the UpdateRequest.phoneNumber field.'); } @@ -569,7 +570,7 @@ export abstract class BaseAuth { // to relax this restriction and just unlink it. if (properties.phoneNumber === null) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, "Both UpdateRequest.phoneNumber=null and UpdateRequest.providersToUnlink=['phone'] were set. To " + 'unlink from a phone provider, only specify the UpdateRequest.phoneNumber=null field.'); } @@ -683,7 +684,7 @@ export abstract class BaseAuth { // Return rejected promise if expiresIn is not available. if (!validator.isNonNullObject(sessionCookieOptions) || !validator.isNumber(sessionCookieOptions.expiresIn)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); } return this.authRequestHandler.createSessionCookie( idToken, sessionCookieOptions.expiresIn); @@ -723,7 +724,7 @@ export abstract class BaseAuth { if (checkRevoked || isEmulator) { return this.verifyDecodedJWTNotRevokedOrDisabled( decodedIdToken, - AuthClientErrorCode.SESSION_COOKIE_REVOKED); + authClientErrorCode.SESSION_COOKIE_REVOKED); } return decodedIdToken; }); @@ -966,7 +967,7 @@ export abstract class BaseAuth { } return Promise.reject( new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"AuthProviderConfigFilter.type" must be either "saml" or "oidc"')); } @@ -997,7 +998,7 @@ export abstract class BaseAuth { return new SAMLConfig(response); }); } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID)); } /** @@ -1019,7 +1020,7 @@ export abstract class BaseAuth { } else if (SAMLConfig.isProviderId(providerId)) { return this.authRequestHandler.deleteInboundSamlConfig(providerId); } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID)); } /** @@ -1041,7 +1042,7 @@ export abstract class BaseAuth { providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise { if (!validator.isNonNullObject(updatedConfig)) { return Promise.reject(new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, 'Request is missing "UpdateAuthProviderRequest" configuration.', )); } @@ -1056,7 +1057,7 @@ export abstract class BaseAuth { return new SAMLConfig(response); }); } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID)); } /** @@ -1073,7 +1074,7 @@ export abstract class BaseAuth { public createProviderConfig(config: AuthProviderConfig): Promise { if (!validator.isNonNullObject(config)) { return Promise.reject(new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, 'Request is missing "AuthProviderConfig" configuration.', )); } @@ -1088,7 +1089,7 @@ export abstract class BaseAuth { return new SAMLConfig(response); }); } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID)); } /** @alpha */ @@ -1121,7 +1122,7 @@ export abstract class BaseAuth { .then((user: UserRecord) => { if (user.disabled) { throw new FirebaseAuthError( - AuthClientErrorCode.USER_DISABLED, + authClientErrorCode.USER_DISABLED, 'The user record is disabled.'); } // If no tokens valid after time available, token is not revoked. diff --git a/src/auth/error.ts b/src/auth/error.ts new file mode 100644 index 0000000000..f4fa39c1c4 --- /dev/null +++ b/src/auth/error.ts @@ -0,0 +1,466 @@ +/*! + * 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. + */ + +import { PrefixedFirebaseError, toHttpResponse, ErrorInfo } from '../utils/error'; +import { RequestResponseError } from '../utils/api-request'; +import { deepCopy } from '../utils/deep-copy'; + +/** +* Auth client error codes. +*/ +export type AuthErrorCode = + | 'auth-blocking-token-expired' + | 'billing-not-enabled' + | 'claims-too-large' + | 'configuration-exists' + | 'configuration-not-found' + | 'id-token-expired' + | 'argument-error' + | 'invalid-config' + | 'email-already-exists' + | 'email-not-found' + | 'reserved-claim' + | 'invalid-id-token' + | 'id-token-revoked' + | 'internal-error' + | 'invalid-claims' + | 'invalid-continue-uri' + | 'invalid-creation-time' + | 'invalid-credential' + | 'invalid-disabled-field' + | 'invalid-display-name' + | 'invalid-dynamic-link-domain' + | 'invalid-hosting-link-domain' + | 'invalid-email-verified' + | 'invalid-email' + | 'invalid-new-email' + | 'invalid-enrolled-factors' + | 'invalid-enrollment-time' + | 'invalid-hash-algorithm' + | 'invalid-hash-block-size' + | 'invalid-hash-derived-key-length' + | 'invalid-hash-key' + | 'invalid-hash-memory-cost' + | 'invalid-hash-parallelization' + | 'invalid-hash-rounds' + | 'invalid-hash-salt-separator' + | 'invalid-last-sign-in-time' + | 'invalid-name' + | 'invalid-oauth-client-id' + | 'invalid-page-token' + | 'invalid-password' + | 'invalid-password-hash' + | 'invalid-password-salt' + | 'invalid-phone-number' + | 'invalid-photo-url' + | 'invalid-project-id' + | 'invalid-provider-data' + | 'invalid-provider-id' + | 'invalid-provider-uid' + | 'invalid-oauth-responsetype' + | 'invalid-session-cookie-duration' + | 'invalid-tenant-id' + | 'invalid-tenant-type' + | 'invalid-testing-phone-number' + | 'invalid-uid' + | 'invalid-user-import' + | 'invalid-tokens-valid-after-time' + | 'mismatching-tenant-id' + | 'missing-android-package-name' + | 'missing-config' + | 'missing-continue-uri' + | 'missing-display-name' + | 'missing-email' + | 'missing-ios-bundle-id' + | 'missing-issuer' + | 'missing-hash-algorithm' + | 'missing-oauth-client-id' + | 'missing-oauth-client-secret' + | 'missing-provider-id' + | 'missing-saml-relying-party-config' + | 'test-phone-number-limit-exceeded' + | 'maximum-user-count-exceeded' + | 'missing-uid' + | 'operation-not-allowed' + | 'phone-number-already-exists' + | 'project-not-found' + | 'insufficient-permission' + | 'quota-exceeded' + | 'second-factor-limit-exceeded' + | 'second-factor-uid-already-exists' + | 'session-cookie-expired' + | 'session-cookie-revoked' + | 'tenant-not-found' + | 'uid-already-exists' + | 'unauthorized-continue-uri' + | 'unsupported-first-factor' + | 'unsupported-second-factor' + | 'unsupported-tenant-operation' + | 'unverified-email' + | 'user-not-found' + | 'not-found' + | 'user-disabled' + | 'user-not-disabled' + | 'invalid-recaptcha-action' + | 'invalid-recaptcha-enforcement-state' + | 'racaptcha-not-enabled'; + +/** +* Auth client error codes and their default messages. +*/ +const authClientErrorMessages: Record = { + 'auth-blocking-token-expired': 'The provided Firebase Auth Blocking token is expired.', + 'billing-not-enabled': 'Feature requires billing to be enabled.', + 'claims-too-large': 'Developer claims maximum payload size exceeded.', + 'configuration-exists': 'A configuration already exists with the provided identifier.', + 'configuration-not-found': 'There is no configuration corresponding to the provided identifier.', + 'id-token-expired': 'The provided Firebase ID token is expired.', + 'argument-error': 'Invalid argument provided.', + 'invalid-config': 'The provided configuration is invalid.', + 'email-already-exists': 'The email address is already in use by another account.', + 'email-not-found': 'There is no user record corresponding to the provided email.', + 'reserved-claim': 'The specified developer claim is reserved and cannot be specified.', + 'invalid-id-token': 'The provided ID token is not a valid Firebase ID token.', + 'id-token-revoked': 'The Firebase ID token has been revoked.', + 'internal-error': 'An internal error has occurred.', + 'invalid-claims': 'The provided custom claim attributes are invalid.', + 'invalid-continue-uri': 'The continue URL must be a valid URL string.', + 'invalid-creation-time': 'The creation time must be a valid UTC date string.', + 'invalid-credential': 'Invalid credential object provided.', + 'invalid-disabled-field': 'The disabled field must be a boolean.', + 'invalid-display-name': 'The displayName field must be a valid string.', + 'invalid-dynamic-link-domain': 'The provided dynamic link domain is not configured or authorized ' + + 'for the current project.', + 'invalid-hosting-link-domain': 'The provided hosting link domain is not configured in Firebase ' + + 'Hosting or is not owned by the current project.', + 'invalid-email-verified': 'The emailVerified field must be a boolean.', + 'invalid-email': 'The email address is improperly formatted.', + 'invalid-new-email': 'The new email address is improperly formatted.', + 'invalid-enrolled-factors': 'The enrolled factors must be a valid array of MultiFactorInfo objects.', + 'invalid-enrollment-time': 'The second factor enrollment time must be a valid UTC date string.', + 'invalid-hash-algorithm': 'The hash algorithm must match one of the strings in the list of ' + + 'supported algorithms.', + 'invalid-hash-block-size': 'The hash block size must be a valid number.', + 'invalid-hash-derived-key-length': 'The hash derived key length must be a valid number.', + 'invalid-hash-key': 'The hash key must a valid byte buffer.', + 'invalid-hash-memory-cost': 'The hash memory cost must be a valid number.', + 'invalid-hash-parallelization': 'The hash parallelization must be a valid number.', + 'invalid-hash-rounds': 'The hash rounds must be a valid number.', + 'invalid-hash-salt-separator': 'The hashing algorithm salt separator field must be a valid byte buffer.', + 'invalid-last-sign-in-time': 'The last sign-in time must be a valid UTC date string.', + 'invalid-name': 'The resource name provided is invalid.', + 'invalid-oauth-client-id': 'The provided OAuth client ID is invalid.', + 'invalid-page-token': 'The page token must be a valid non-empty string.', + 'invalid-password': 'The password must be a string with at least 6 characters.', + 'invalid-password-hash': 'The password hash must be a valid byte buffer.', + 'invalid-password-salt': 'The password salt must be a valid byte buffer.', + 'invalid-phone-number': 'The phone number must be a non-empty E.164 standard compliant identifier string.', + 'invalid-photo-url': 'The photoURL field must be a valid URL.', + 'invalid-project-id': 'Invalid parent project. Either parent project doesn\'t exist or didn\'t enable multi-tenancy.', + 'invalid-provider-data': 'The providerData must be a valid array of UserInfo objects.', + 'invalid-provider-id': 'The providerId must be a valid supported provider identifier string.', + 'invalid-provider-uid': 'The providerUid must be a valid provider uid string.', + 'invalid-oauth-responsetype': 'Only exactly one OAuth responseType should be set to true.', + 'invalid-session-cookie-duration': 'The session cookie duration must be a valid number in milliseconds ' + + 'between 5 minutes and 2 weeks.', + 'invalid-tenant-id': 'The tenant ID must be a valid non-empty string.', + 'invalid-tenant-type': 'Tenant type must be either "full_service" or "lightweight".', + 'invalid-testing-phone-number': 'Invalid testing phone number or invalid test code provided.', + 'invalid-uid': 'The uid must be a non-empty string with at most 128 characters.', + 'invalid-user-import': 'The user record to import is invalid.', + 'invalid-tokens-valid-after-time': 'The tokensValidAfterTime must be a valid UTC number in seconds.', + 'mismatching-tenant-id': 'User tenant ID does not match with the current TenantAwareAuth tenant ID.', + 'missing-android-package-name': 'An Android Package Name must be provided if the Android App is required ' + + 'to be installed.', + 'missing-config': 'The provided configuration is missing required attributes.', + 'missing-continue-uri': 'A valid continue URL must be provided in the request.', + 'missing-display-name': 'The resource being created or edited is missing a valid display name.', + 'missing-email': 'The email is required for the specified action. For example, a multi-factor user requires ' + + 'a verified email.', + 'missing-ios-bundle-id': 'The request is missing an iOS Bundle ID.', + 'missing-issuer': 'The OAuth/OIDC configuration issuer must not be empty.', + 'missing-hash-algorithm': 'Importing users with password hashes requires that the hashing algorithm and its ' + + 'parameters be provided.', + 'missing-oauth-client-id': 'The OAuth/OIDC configuration client ID must not be empty.', + 'missing-oauth-client-secret': 'The OAuth configuration client secret is required to enable OIDC code flow.', + 'missing-provider-id': 'A valid provider ID must be provided in the request.', + 'missing-saml-relying-party-config': 'The SAML configuration provided is missing a relying party configuration.', + 'test-phone-number-limit-exceeded': 'The maximum allowed number of test phone number / code pairs has been exceeded.', + 'maximum-user-count-exceeded': 'The maximum allowed number of users to import has been exceeded.', + 'missing-uid': 'A uid identifier is required for the current operation.', + 'operation-not-allowed': 'The given sign-in provider is disabled for this Firebase project. ' + + 'Enable it in the Firebase console, under the sign-in method tab of the Auth section.', + 'phone-number-already-exists': 'The user with the provided phone number already exists.', + 'project-not-found': 'No Firebase project was found for the provided credential.', + 'insufficient-permission': 'Credential implementation provided to initializeApp() via the ' + + '"credential" property has insufficient permission to access the requested resource. See ' + + 'https://firebase.google.com/docs/admin/setup for details on how to authenticate this SDK ' + + 'with appropriate permissions.', + 'quota-exceeded': 'The project quota for the specified operation has been exceeded.', + 'second-factor-limit-exceeded': 'The maximum number of allowed second factors on a user has been exceeded.', + 'second-factor-uid-already-exists': 'The specified second factor "uid" already exists.', + 'session-cookie-expired': 'The Firebase session cookie is expired.', + 'session-cookie-revoked': 'The Firebase session cookie has been revoked.', + 'tenant-not-found': 'There is no tenant corresponding to the provided identifier.', + 'uid-already-exists': 'The user with the provided uid already exists.', + 'unauthorized-continue-uri': 'The domain of the continue URL is not whitelisted. Whitelist ' + + 'the domain in the Firebase console.', + 'unsupported-first-factor': 'A multi-factor user requires a supported first factor.', + 'unsupported-second-factor': 'The request specified an unsupported type of second factor.', + 'unsupported-tenant-operation': 'This operation is not supported in a multi-tenant context.', + 'unverified-email': 'A verified email is required for the specified action. For example, a multi-factor ' + + 'user requires a verified email.', + 'user-not-found': 'There is no user record corresponding to the provided identifier.', + 'not-found': 'The requested resource was not found.', + 'user-disabled': 'The user record is disabled.', + 'user-not-disabled': 'The user must be disabled in order to bulk delete it (or you must pass force=true).', + 'invalid-recaptcha-action': 'reCAPTCHA action must be "BLOCK".', + 'invalid-recaptcha-enforcement-state': 'reCAPTCHA enforcement state must be either "OFF", "AUDIT" or "ENFORCE".', + 'racaptcha-not-enabled': 'reCAPTCHA enterprise is not enabled.', +}; + +/** @const {ServerToClientCode} Auth server to client enum error codes. */ +const AUTH_SERVER_TO_CLIENT_CODE: Record = { + BILLING_NOT_ENABLED: 'BILLING_NOT_ENABLED', + CLAIMS_TOO_LARGE: 'CLAIMS_TOO_LARGE', + CONFIGURATION_EXISTS: 'CONFIGURATION_EXISTS', + CONFIGURATION_NOT_FOUND: 'CONFIGURATION_NOT_FOUND', + INSUFFICIENT_PERMISSION: 'INSUFFICIENT_PERMISSION', + INVALID_CLAIMS: 'INVALID_CLAIMS', + INVALID_CONFIG: 'INVALID_CONFIG', + INVALID_CONTINUE_URI: 'INVALID_CONTINUE_URI', + INVALID_CREATION_TIME: 'INVALID_CREATION_TIME', + INVALID_DYNAMIC_LINK_DOMAIN: 'INVALID_DYNAMIC_LINK_DOMAIN', + INVALID_EMAIL_VERIFIED: 'INVALID_EMAIL_VERIFIED', + INVALID_EMAIL: 'INVALID_EMAIL', + INVALID_ENROLLED_FACTORS: 'INVALID_ENROLLED_FACTORS', + INVALID_ENROLLMENT_TIME: 'INVALID_ENROLLMENT_TIME', + INVALID_HASH_ALGORITHM: 'INVALID_HASH_ALGORITHM', + INVALID_HASH_BLOCK_SIZE: 'INVALID_HASH_BLOCK_SIZE', + INVALID_HASH_DERIVED_KEY_LENGTH: 'INVALID_HASH_DERIVED_KEY_LENGTH', + INVALID_HASH_KEY: 'INVALID_HASH_KEY', + INVALID_HASH_MEMORY_COST: 'INVALID_HASH_MEMORY_COST', + INVALID_HASH_PARALLELIZATION: 'INVALID_HASH_PARALLELIZATION', + INVALID_HASH_ROUNDS: 'INVALID_HASH_ROUNDS', + INVALID_HASH_SALT_SEPARATOR: 'INVALID_HASH_SALT_SEPARATOR', + INVALID_LAST_SIGN_IN_TIME: 'INVALID_LAST_SIGN_IN_TIME', + INVALID_MFA_REQUIRED_KEY: 'INVALID_ARGUMENT', + INVALID_OAUTH_CLIENT_ID: 'INVALID_OAUTH_CLIENT_ID', + INVALID_PAGE_TOKEN: 'INVALID_PAGE_TOKEN', + INVALID_PASSWORD: 'INVALID_PASSWORD', + INVALID_PASSWORD_HASH: 'INVALID_PASSWORD_HASH', + INVALID_PASSWORD_SALT: 'INVALID_PASSWORD_SALT', + INVALID_PHONE_NUMBER: 'INVALID_PHONE_NUMBER', + INVALID_PHOTO_URL: 'INVALID_PHOTO_URL', + INVALID_PROJECT_ID: 'INVALID_PROJECT_ID', + INVALID_PROVIDER_DATA: 'INVALID_PROVIDER_DATA', + INVALID_PROVIDER_ID: 'INVALID_PROVIDER_ID', + INVALID_PROVIDER_UID: 'INVALID_PROVIDER_UID', + INVALID_RECAPTCHA_ACTION: 'INVALID_RECAPTCHA_ACTION', + INVALID_RECAPTCHA_ENFORCEMENT_STATE: 'INVALID_RECAPTCHA_ENFORCEMENT_STATE', + INVALID_SESSION_COOKIE_DURATION: 'INVALID_SESSION_COOKIE_DURATION', + INVALID_TENANT_ID: 'INVALID_TENANT_ID', + INVALID_TENANT_TYPE: 'INVALID_TENANT_TYPE', + INVALID_TESTING_PHONE_NUMBER: 'INVALID_TESTING_PHONE_NUMBER', + MISSING_ANDROID_PACKAGE_NAME: 'MISSING_ANDROID_PACKAGE_NAME', + MISSING_CONTINUE_URI: 'MISSING_CONTINUE_URI', + MISSING_HASH_ALGORITHM: 'MISSING_HASH_ALGORITHM', + MISSING_IOS_BUNDLE_ID: 'MISSING_IOS_BUNDLE_ID', + MISSING_MFA_REQUIRED_KEY: 'INVALID_ARGUMENT', + MISSING_UID: 'MISSING_UID', + OPERATION_NOT_ALLOWED: 'OPERATION_NOT_ALLOWED', + RECAPTCHA_NOT_ENABLED: 'RECAPTCHA_NOT_ENABLED', + SECOND_FACTOR_LIMIT_EXCEEDED: 'SECOND_FACTOR_LIMIT_EXCEEDED', + SECOND_FACTOR_UID_ALREADY_EXISTS: 'SECOND_FACTOR_UID_ALREADY_EXISTS', + TENANT_NOT_FOUND: 'TENANT_NOT_FOUND', + UNAUTHORIZED_DOMAIN: 'UNAUTHORIZED_DOMAIN', + UNSUPPORTED_FIRST_FACTOR: 'UNSUPPORTED_FIRST_FACTOR', + UNSUPPORTED_SECOND_FACTOR: 'UNSUPPORTED_SECOND_FACTOR', + UNSUPPORTED_TENANT_OPERATION: 'UNSUPPORTED_TENANT_OPERATION', + UNVERIFIED_EMAIL: 'UNVERIFIED_EMAIL', + USER_NOT_DISABLED: 'USER_NOT_DISABLED', + DUPLICATE_EMAIL: 'EMAIL_ALREADY_EXISTS', + DUPLICATE_LOCAL_ID: 'UID_ALREADY_EXISTS', + EMAIL_EXISTS: 'EMAIL_ALREADY_EXISTS', + EMAIL_NOT_FOUND: 'EMAIL_NOT_FOUND', + INVALID_TENANT_TYPE_CONVERSION: 'INVALID_ARGUMENT', + MISSING_EMAIL: 'INVALID_EMAIL', + MISSING_LOCAL_ID: 'MISSING_UID', + MISSING_PASSWORD: 'INVALID_PASSWORD', + PASSWORD_LOGIN_DISABLED: 'OPERATION_NOT_ALLOWED', + PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_ALREADY_EXISTS', + PROJECT_NOT_FOUND: 'PROJECT_NOT_FOUND', + USER_NOT_FOUND: 'USER_NOT_FOUND', + WEAK_PASSWORD: 'INVALID_PASSWORD', + TOKEN_EXPIRED: 'TOKEN_EXPIRED', + INVALID_ID_TOKEN: 'INVALID_ID_TOKEN', +}; + +/** + * Internal Auth client error code mapping used to construct ErrorInfo. + */ +export const authClientErrorCode = { + AUTH_BLOCKING_TOKEN_EXPIRED: createAuthErrorInfo('auth-blocking-token-expired'), + BILLING_NOT_ENABLED: createAuthErrorInfo('billing-not-enabled'), + CLAIMS_TOO_LARGE: createAuthErrorInfo('claims-too-large'), + CONFIGURATION_EXISTS: createAuthErrorInfo('configuration-exists'), + CONFIGURATION_NOT_FOUND: createAuthErrorInfo('configuration-not-found'), + ID_TOKEN_EXPIRED: createAuthErrorInfo('id-token-expired'), + INVALID_ARGUMENT: createAuthErrorInfo('argument-error'), + INVALID_CONFIG: createAuthErrorInfo('invalid-config'), + EMAIL_ALREADY_EXISTS: createAuthErrorInfo('email-already-exists'), + EMAIL_NOT_FOUND: createAuthErrorInfo('email-not-found'), + FORBIDDEN_CLAIM: createAuthErrorInfo('reserved-claim'), + INVALID_ID_TOKEN: createAuthErrorInfo('invalid-id-token'), + ID_TOKEN_REVOKED: createAuthErrorInfo('id-token-revoked'), + INTERNAL_ERROR: createAuthErrorInfo('internal-error'), + INVALID_CLAIMS: createAuthErrorInfo('invalid-claims'), + INVALID_CONTINUE_URI: createAuthErrorInfo('invalid-continue-uri'), + INVALID_CREATION_TIME: createAuthErrorInfo('invalid-creation-time'), + INVALID_CREDENTIAL: createAuthErrorInfo('invalid-credential'), + INVALID_DISABLED_FIELD: createAuthErrorInfo('invalid-disabled-field'), + INVALID_DISPLAY_NAME: createAuthErrorInfo('invalid-display-name'), + INVALID_DYNAMIC_LINK_DOMAIN: createAuthErrorInfo('invalid-dynamic-link-domain'), + INVALID_HOSTING_LINK_DOMAIN: createAuthErrorInfo('invalid-hosting-link-domain'), + INVALID_EMAIL_VERIFIED: createAuthErrorInfo('invalid-email-verified'), + INVALID_EMAIL: createAuthErrorInfo('invalid-email'), + INVALID_NEW_EMAIL: createAuthErrorInfo('invalid-new-email'), + INVALID_ENROLLED_FACTORS: createAuthErrorInfo('invalid-enrolled-factors'), + INVALID_ENROLLMENT_TIME: createAuthErrorInfo('invalid-enrollment-time'), + INVALID_HASH_ALGORITHM: createAuthErrorInfo('invalid-hash-algorithm'), + INVALID_HASH_BLOCK_SIZE: createAuthErrorInfo('invalid-hash-block-size'), + INVALID_HASH_DERIVED_KEY_LENGTH: createAuthErrorInfo('invalid-hash-derived-key-length'), + INVALID_HASH_KEY: createAuthErrorInfo('invalid-hash-key'), + INVALID_HASH_MEMORY_COST: createAuthErrorInfo('invalid-hash-memory-cost'), + INVALID_HASH_PARALLELIZATION: createAuthErrorInfo('invalid-hash-parallelization'), + INVALID_HASH_ROUNDS: createAuthErrorInfo('invalid-hash-rounds'), + INVALID_HASH_SALT_SEPARATOR: createAuthErrorInfo('invalid-hash-salt-separator'), + INVALID_LAST_SIGN_IN_TIME: createAuthErrorInfo('invalid-last-sign-in-time'), + INVALID_NAME: createAuthErrorInfo('invalid-name'), + INVALID_OAUTH_CLIENT_ID: createAuthErrorInfo('invalid-oauth-client-id'), + INVALID_PAGE_TOKEN: createAuthErrorInfo('invalid-page-token'), + INVALID_PASSWORD: createAuthErrorInfo('invalid-password'), + INVALID_PASSWORD_HASH: createAuthErrorInfo('invalid-password-hash'), + INVALID_PASSWORD_SALT: createAuthErrorInfo('invalid-password-salt'), + INVALID_PHONE_NUMBER: createAuthErrorInfo('invalid-phone-number'), + INVALID_PHOTO_URL: createAuthErrorInfo('invalid-photo-url'), + INVALID_PROJECT_ID: createAuthErrorInfo('invalid-project-id'), + INVALID_PROVIDER_DATA: createAuthErrorInfo('invalid-provider-data'), + INVALID_PROVIDER_ID: createAuthErrorInfo('invalid-provider-id'), + INVALID_PROVIDER_UID: createAuthErrorInfo('invalid-provider-uid'), + INVALID_OAUTH_RESPONSETYPE: createAuthErrorInfo('invalid-oauth-responsetype'), + INVALID_SESSION_COOKIE_DURATION: createAuthErrorInfo('invalid-session-cookie-duration'), + INVALID_TENANT_ID: createAuthErrorInfo('invalid-tenant-id'), + INVALID_TENANT_TYPE: createAuthErrorInfo('invalid-tenant-type'), + INVALID_TESTING_PHONE_NUMBER: createAuthErrorInfo('invalid-testing-phone-number'), + INVALID_UID: createAuthErrorInfo('invalid-uid'), + INVALID_USER_IMPORT: createAuthErrorInfo('invalid-user-import'), + INVALID_TOKENS_VALID_AFTER_TIME: createAuthErrorInfo('invalid-tokens-valid-after-time'), + MISMATCHING_TENANT_ID: createAuthErrorInfo('mismatching-tenant-id'), + MISSING_ANDROID_PACKAGE_NAME: createAuthErrorInfo('missing-android-package-name'), + MISSING_CONFIG: createAuthErrorInfo('missing-config'), + MISSING_CONTINUE_URI: createAuthErrorInfo('missing-continue-uri'), + MISSING_DISPLAY_NAME: createAuthErrorInfo('missing-display-name'), + MISSING_EMAIL: createAuthErrorInfo('missing-email'), + MISSING_IOS_BUNDLE_ID: createAuthErrorInfo('missing-ios-bundle-id'), + MISSING_ISSUER: createAuthErrorInfo('missing-issuer'), + MISSING_HASH_ALGORITHM: createAuthErrorInfo('missing-hash-algorithm'), + MISSING_OAUTH_CLIENT_ID: createAuthErrorInfo('missing-oauth-client-id'), + MISSING_OAUTH_CLIENT_SECRET: createAuthErrorInfo('missing-oauth-client-secret'), + MISSING_PROVIDER_ID: createAuthErrorInfo('missing-provider-id'), + MISSING_SAML_RELYING_PARTY_CONFIG: createAuthErrorInfo('missing-saml-relying-party-config'), + MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED: createAuthErrorInfo('test-phone-number-limit-exceeded'), + MAXIMUM_USER_COUNT_EXCEEDED: createAuthErrorInfo('maximum-user-count-exceeded'), + MISSING_UID: createAuthErrorInfo('missing-uid'), + OPERATION_NOT_ALLOWED: createAuthErrorInfo('operation-not-allowed'), + PHONE_NUMBER_ALREADY_EXISTS: createAuthErrorInfo('phone-number-already-exists'), + PROJECT_NOT_FOUND: createAuthErrorInfo('project-not-found'), + INSUFFICIENT_PERMISSION: createAuthErrorInfo('insufficient-permission'), + QUOTA_EXCEEDED: createAuthErrorInfo('quota-exceeded'), + SECOND_FACTOR_LIMIT_EXCEEDED: createAuthErrorInfo('second-factor-limit-exceeded'), + SECOND_FACTOR_UID_ALREADY_EXISTS: createAuthErrorInfo('second-factor-uid-already-exists'), + SESSION_COOKIE_EXPIRED: createAuthErrorInfo('session-cookie-expired'), + SESSION_COOKIE_REVOKED: createAuthErrorInfo('session-cookie-revoked'), + TENANT_NOT_FOUND: createAuthErrorInfo('tenant-not-found'), + UID_ALREADY_EXISTS: createAuthErrorInfo('uid-already-exists'), + UNAUTHORIZED_DOMAIN: createAuthErrorInfo('unauthorized-continue-uri'), + UNSUPPORTED_FIRST_FACTOR: createAuthErrorInfo('unsupported-first-factor'), + UNSUPPORTED_SECOND_FACTOR: createAuthErrorInfo('unsupported-second-factor'), + UNSUPPORTED_TENANT_OPERATION: createAuthErrorInfo('unsupported-tenant-operation'), + UNVERIFIED_EMAIL: createAuthErrorInfo('unverified-email'), + USER_NOT_FOUND: createAuthErrorInfo('user-not-found'), + NOT_FOUND: createAuthErrorInfo('not-found'), + USER_DISABLED: createAuthErrorInfo('user-disabled'), + USER_NOT_DISABLED: createAuthErrorInfo('user-not-disabled'), + INVALID_RECAPTCHA_ACTION: createAuthErrorInfo('invalid-recaptcha-action'), + INVALID_RECAPTCHA_ENFORCEMENT_STATE: createAuthErrorInfo('invalid-recaptcha-enforcement-state'), + RECAPTCHA_NOT_ENABLED: createAuthErrorInfo('racaptcha-not-enabled'), +}; + +function createAuthErrorInfo(code: AuthErrorCode): ErrorInfo { + return { + code, + message: authClientErrorMessages[code] || 'An unknown error occurred.', + }; +} + +/** + * Firebase Auth error code structure. This extends PrefixedFirebaseError. + */ +export class FirebaseAuthError extends PrefixedFirebaseError { + /** + * Creates the developer-facing error corresponding to the backend error code. + * + * @param serverErrorCode - The server error code. + * @param [message] The error message. The default message is used + * if not provided. + * @param [serverError] The error's raw server response. + * @returns The corresponding developer-facing error. + * @internal + */ + public static fromServerError( + serverErrorCode: string, + message?: string, + serverError?: RequestResponseError, + ): FirebaseAuthError { + // serverErrorCode could contain additional details: + // ERROR_CODE : Detailed message which can also contain colons + const colonSeparator = (serverErrorCode || '').indexOf(':'); + let customMessage = null; + if (colonSeparator !== -1) { + customMessage = serverErrorCode.substring(colonSeparator + 1).trim(); + serverErrorCode = serverErrorCode.substring(0, colonSeparator).trim(); + } + // If not found, default to internal error. + const clientCodeKey = AUTH_SERVER_TO_CLIENT_CODE[serverErrorCode] || 'INTERNAL_ERROR'; + const error: ErrorInfo = deepCopy((authClientErrorCode as any)[clientCodeKey]); + // Server detailed message should have highest priority. + error.message = customMessage || message || error.message; + error.cause = serverError; + error.httpResponse = serverError?.response ? toHttpResponse(serverError.response) : undefined; + return new FirebaseAuthError(error); + } + + /** + * @param info - The error code info. + * @param message - The error message. This will override the default message if provided. + */ + constructor(info: ErrorInfo, message?: string) { + // Override default message if custom message provided. + super('auth', info.code, message || info.message, info.httpResponse, info.cause); + + } +} diff --git a/src/auth/index.ts b/src/auth/index.ts index 4650b25e27..ccef8194d2 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -170,5 +170,5 @@ export { export { FirebaseAuthError, - AuthClientErrorCode, -} from '../utils/error'; + AuthErrorCode, +} from './error'; diff --git a/src/auth/project-config.ts b/src/auth/project-config.ts index 1695ec3a0d..4028dbb515 100644 --- a/src/auth/project-config.ts +++ b/src/auth/project-config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import * as validator from '../utils/validator'; -import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; import { SmsRegionsAuthConfig, SmsRegionConfig, @@ -150,7 +150,7 @@ export class ProjectConfig { private static validate(request: UpdateProjectConfigRequest): void { if (!validator.isNonNullObject(request)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"UpdateProjectConfigRequest" must be a valid non-null object.', ); } @@ -166,7 +166,7 @@ export class ProjectConfig { for (const key in request) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, `"${key}" is not a valid UpdateProjectConfigRequest parameter.`, ); } diff --git a/src/auth/tenant-manager.ts b/src/auth/tenant-manager.ts index 19da5b4c83..654d9241f1 100644 --- a/src/auth/tenant-manager.ts +++ b/src/auth/tenant-manager.ts @@ -17,7 +17,7 @@ import * as validator from '../utils/validator'; import { App } from '../app'; import * as utils from '../utils/index'; -import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; import { BaseAuth, createFirebaseTokenGenerator, SessionCookieOptions } from './base-auth'; import { Tenant, TenantServerResponse, CreateTenantRequest, UpdateTenantRequest } from './tenant'; @@ -93,7 +93,7 @@ export class TenantAwareAuth extends BaseAuth { .then((decodedClaims) => { // Validate tenant ID. if (decodedClaims.firebase.tenant !== this.tenantId) { - throw new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); + throw new FirebaseAuthError(authClientErrorCode.MISMATCHING_TENANT_ID); } return decodedClaims; }); @@ -106,11 +106,11 @@ export class TenantAwareAuth extends BaseAuth { idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { // Validate arguments before processing. if (!validator.isNonEmptyString(idToken)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ID_TOKEN)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_ID_TOKEN)); } if (!validator.isNonNullObject(sessionCookieOptions) || !validator.isNumber(sessionCookieOptions.expiresIn)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); + return Promise.reject(new FirebaseAuthError(authClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); } // This will verify the ID token and then match the tenant ID before creating the session cookie. return this.verifyIdToken(idToken) @@ -127,7 +127,7 @@ export class TenantAwareAuth extends BaseAuth { return super.verifySessionCookie(sessionCookie, checkRevoked) .then((decodedClaims) => { if (decodedClaims.firebase.tenant !== this.tenantId) { - throw new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); + throw new FirebaseAuthError(authClientErrorCode.MISMATCHING_TENANT_ID); } return decodedClaims; }); @@ -171,7 +171,7 @@ export class TenantManager { */ public authForTenant(tenantId: string): TenantAwareAuth { if (!validator.isNonEmptyString(tenantId)) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_TENANT_ID); + throw new FirebaseAuthError(authClientErrorCode.INVALID_TENANT_ID); } if (typeof this.tenantsMap[tenantId] === 'undefined') { this.tenantsMap[tenantId] = new TenantAwareAuth(this.app, tenantId); diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index 19812c02e6..ea46eed609 100644 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -16,7 +16,7 @@ import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; -import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; import { EmailSignInConfig, EmailSignInConfigServerRequest, MultiFactorAuthServerConfig, @@ -258,7 +258,7 @@ export class Tenant { const label = createRequest ? 'CreateTenantRequest' : 'UpdateTenantRequest'; if (!validator.isNonNullObject(request)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, `"${label}" must be a valid non-null object.`, ); } @@ -266,7 +266,7 @@ export class Tenant { for (const key in request) { if (!(key in validKeys)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, `"${key}" is not a valid ${label} parameter.`, ); } @@ -275,7 +275,7 @@ export class Tenant { if (typeof request.displayName !== 'undefined' && !validator.isNonEmptyString(request.displayName)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, `"${label}.displayName" must be a valid non-empty string.`, ); } @@ -291,7 +291,7 @@ export class Tenant { } else if (request.testPhoneNumbers === null && createRequest) { // null allowed only for update operations. throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, `"${label}.testPhoneNumbers" must be a non-null object.`, ); } @@ -330,7 +330,7 @@ export class Tenant { const tenantId = Tenant.getTenantIdFromResourceName(response.name); if (!tenantId) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid tenant response', ); } diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index 3230730104..ac0e63ed3c 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -15,7 +15,8 @@ * limitations under the License. */ -import { AuthClientErrorCode, ErrorInfo, FirebaseAuthError } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; +import { ErrorInfo } from '../utils/error'; import { RequestResponseError } from '../utils/api-request'; import { CryptoSigner, CryptoSignerError, CryptoSignerErrorCode } from '../utils/crypto-signer'; @@ -99,13 +100,13 @@ export class FirebaseTokenGenerator { constructor(signer: CryptoSigner, public readonly tenantId?: string) { if (!validator.isNonNullObject(signer)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CREDENTIAL, + authClientErrorCode.INVALID_CREDENTIAL, 'INTERNAL ASSERT: Must provide a CryptoSigner to use FirebaseTokenGenerator.', ); } if (typeof this.tenantId !== 'undefined' && !validator.isNonEmptyString(this.tenantId)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '`tenantId` argument must be a non-empty string.'); } this.signer = signer; @@ -131,7 +132,7 @@ export class FirebaseTokenGenerator { } if (errorMessage) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); + throw new FirebaseAuthError(authClientErrorCode.INVALID_ARGUMENT, errorMessage); } const claims: {[key: string]: any} = {}; @@ -141,7 +142,7 @@ export class FirebaseTokenGenerator { if (Object.prototype.hasOwnProperty.call(developerClaims, key)) { if (BLACKLISTED_CLAIMS.indexOf(key) !== -1) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, `Developer claim "${key}" is reserved and cannot be specified.`, ); } @@ -222,22 +223,22 @@ export function handleCryptoSignerError(err: Error): Error { return FirebaseAuthError.fromServerError(errorCode, errorMsg, httpError); } - return new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR, + return new FirebaseAuthError(authClientErrorCode.INTERNAL_ERROR, 'Error returned from server: ' + errorResponse + '. Additionally, an ' + 'internal error occurred while attempting to extract the ' + 'errorcode from the error.' ); } - return new FirebaseAuthError(mapToAuthClientErrorCode(err.code), err.message); + return new FirebaseAuthError(mapToauthClientErrorCode(err.code), err.message); } -function mapToAuthClientErrorCode(code: string): ErrorInfo { +function mapToauthClientErrorCode(code: string): ErrorInfo { switch (code) { case CryptoSignerErrorCode.INVALID_CREDENTIAL: - return AuthClientErrorCode.INVALID_CREDENTIAL; + return authClientErrorCode.INVALID_CREDENTIAL; case CryptoSignerErrorCode.INVALID_ARGUMENT: - return AuthClientErrorCode.INVALID_ARGUMENT; + return authClientErrorCode.INVALID_ARGUMENT; default: - return AuthClientErrorCode.INTERNAL_ERROR; + return authClientErrorCode.INTERNAL_ERROR; } } diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 64da8ac516..112b82fc32 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { AuthClientErrorCode, FirebaseAuthError, ErrorInfo } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; +import { ErrorInfo } from '../utils/error'; import * as util from '../utils/index'; import * as validator from '../utils/validator'; import { @@ -274,7 +275,7 @@ export const ID_TOKEN_INFO: FirebaseTokenInfo = { verifyApiName: 'verifyIdToken()', jwtName: 'Firebase ID token', shortName: 'ID token', - expiredErrorCode: AuthClientErrorCode.ID_TOKEN_EXPIRED, + expiredErrorCode: authClientErrorCode.ID_TOKEN_EXPIRED, }; /** @@ -287,7 +288,7 @@ export const AUTH_BLOCKING_TOKEN_INFO: FirebaseTokenInfo = { verifyApiName: '_verifyAuthBlockingToken()', jwtName: 'Firebase Auth Blocking token', shortName: 'Auth Blocking token', - expiredErrorCode: AuthClientErrorCode.AUTH_BLOCKING_TOKEN_EXPIRED, + expiredErrorCode: authClientErrorCode.AUTH_BLOCKING_TOKEN_EXPIRED, }; /** @@ -300,7 +301,7 @@ export const SESSION_COOKIE_INFO: FirebaseTokenInfo = { verifyApiName: 'verifySessionCookie()', jwtName: 'Firebase session cookie', shortName: 'session cookie', - expiredErrorCode: AuthClientErrorCode.SESSION_COOKIE_EXPIRED, + expiredErrorCode: authClientErrorCode.SESSION_COOKIE_EXPIRED, }; /** @@ -336,42 +337,42 @@ export class FirebaseTokenVerifier { if (!validator.isURL(clientCertUrl)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'The provided public client certificate URL is an invalid URL.', ); } else if (!validator.isURL(issuer)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'The provided JWT issuer is an invalid URL.', ); } else if (!validator.isNonNullObject(tokenInfo)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'The provided JWT information is not an object or null.', ); } else if (!validator.isURL(tokenInfo.url)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'The provided JWT verification documentation URL is invalid.', ); } else if (!validator.isNonEmptyString(tokenInfo.verifyApiName)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'The JWT verify API name must be a non-empty string.', ); } else if (!validator.isNonEmptyString(tokenInfo.jwtName)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'The JWT public full name must be a non-empty string.', ); } else if (!validator.isNonEmptyString(tokenInfo.shortName)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'The JWT public short name must be a non-empty string.', ); } else if (!validator.isNonNullObject(tokenInfo.expiredErrorCode) || !('code' in tokenInfo.expiredErrorCode)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'The JWT expiration error code must be a non-null ErrorInfo object.', ); } @@ -393,7 +394,7 @@ export class FirebaseTokenVerifier { public verifyJWT(jwtToken: string, isEmulator = false): Promise { if (!validator.isString(jwtToken)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, `First argument to ${this.tokenInfo.verifyApiName} must be a ${this.tokenInfo.jwtName} string.`, ); } @@ -417,7 +418,7 @@ export class FirebaseTokenVerifier { audience: string | undefined): Promise { if (!validator.isString(jwtToken)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, `First argument to ${this.tokenInfo.verifyApiName} must be a ${this.tokenInfo.jwtName} string.`, ); } @@ -441,7 +442,7 @@ export class FirebaseTokenVerifier { .then((projectId) => { if (!validator.isNonEmptyString(projectId)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CREDENTIAL, + authClientErrorCode.INVALID_CREDENTIAL, '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}.`, ); @@ -472,10 +473,10 @@ export class FirebaseTokenVerifier { 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 FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, + throw new FirebaseAuthError(authClientErrorCode.INVALID_ARGUMENT, errorMessage); } - throw new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR, err.message); + throw new FirebaseAuthError(authClientErrorCode.INTERNAL_ERROR, err.message); }); } @@ -544,7 +545,7 @@ export class FirebaseTokenVerifier { } } if (errorMessage) { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); + throw new FirebaseAuthError(authClientErrorCode.INVALID_ARGUMENT, errorMessage); } } @@ -573,14 +574,14 @@ export class FirebaseTokenVerifier { return new FirebaseAuthError(this.tokenInfo.expiredErrorCode, errorMessage); } else if (error.code === JwtErrorCode.INVALID_SIGNATURE) { const errorMessage = `${this.tokenInfo.jwtName} has invalid signature.` + verifyJwtTokenDocsMessage; - return new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); + return new FirebaseAuthError(authClientErrorCode.INVALID_ARGUMENT, errorMessage); } else if (error.code === 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 FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); + return new FirebaseAuthError(authClientErrorCode.INVALID_ARGUMENT, errorMessage); } - return new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, error.message); + return new FirebaseAuthError(authClientErrorCode.INVALID_ARGUMENT, error.message); } } diff --git a/src/auth/user-import-builder.ts b/src/auth/user-import-builder.ts index f396066b49..4554187b10 100644 --- a/src/auth/user-import-builder.ts +++ b/src/auth/user-import-builder.ts @@ -18,7 +18,7 @@ import { FirebaseArrayIndexError } from '../app/index'; import { deepCopy, deepExtend } from '../utils/deep-copy'; import * as utils from '../utils'; import * as validator from '../utils/validator'; -import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; import { UpdateMultiFactorInfoRequest, UpdatePhoneMultiFactorInfoRequest, MultiFactorUpdateSettings } from './auth-config'; @@ -329,7 +329,7 @@ export function convertMultiFactorInfoToServerFormat(multiFactorInfo: UpdateMult enrolledAt = new Date(multiFactorInfo.enrollmentTime).toISOString(); } else { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ENROLLMENT_TIME, + authClientErrorCode.INVALID_ENROLLMENT_TIME, `The second factor "enrollmentTime" for "${multiFactorInfo.uid}" must be a valid ` + 'UTC date string.'); } @@ -353,7 +353,7 @@ export function convertMultiFactorInfoToServerFormat(multiFactorInfo: UpdateMult } else { // Unsupported second factor. throw new FirebaseAuthError( - AuthClientErrorCode.UNSUPPORTED_SECOND_FACTOR, + authClientErrorCode.UNSUPPORTED_SECOND_FACTOR, `Unsupported second factor "${JSON.stringify(multiFactorInfo)}" provided.`); } } @@ -401,7 +401,7 @@ function populateUploadAccountUser( if (typeof user.passwordHash !== 'undefined') { if (!validator.isBuffer(user.passwordHash)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_PASSWORD_HASH, + authClientErrorCode.INVALID_PASSWORD_HASH, ); } result.passwordHash = utils.toWebSafeBase64(user.passwordHash); @@ -409,7 +409,7 @@ function populateUploadAccountUser( if (typeof user.passwordSalt !== 'undefined') { if (!validator.isBuffer(user.passwordSalt)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_PASSWORD_SALT, + authClientErrorCode.INVALID_PASSWORD_SALT, ); } result.salt = utils.toWebSafeBase64(user.passwordSalt); @@ -527,7 +527,7 @@ export class UserImportBuilder { // Map backend request index to original developer provided array index. index: this.indexMap[failedUpload.index], error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_USER_IMPORT, + authClientErrorCode.INVALID_USER_IMPORT, failedUpload.message, ), }); @@ -555,20 +555,20 @@ export class UserImportBuilder { } if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"UserImportOptions" are required when importing users with passwords.', ); } if (!validator.isNonNullObject(options.hash)) { throw new FirebaseAuthError( - AuthClientErrorCode.MISSING_HASH_ALGORITHM, + authClientErrorCode.MISSING_HASH_ALGORITHM, '"hash.algorithm" is missing from the provided "UserImportOptions".', ); } if (typeof options.hash.algorithm === 'undefined' || !validator.isNonEmptyString(options.hash.algorithm)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_ALGORITHM, + authClientErrorCode.INVALID_HASH_ALGORITHM, '"hash.algorithm" must be a string matching the list of supported algorithms.', ); } @@ -581,7 +581,7 @@ export class UserImportBuilder { case 'HMAC_MD5': if (!validator.isBuffer(options.hash.key)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_KEY, + authClientErrorCode.INVALID_HASH_KEY, 'A non-empty "hash.key" byte buffer must be provided for ' + `hash algorithm ${options.hash.algorithm}.`, ); @@ -601,7 +601,7 @@ export class UserImportBuilder { const minRounds = options.hash.algorithm === 'MD5' ? 0 : 1; if (isNaN(rounds) || rounds < minRounds || rounds > 8192) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_ROUNDS, + authClientErrorCode.INVALID_HASH_ROUNDS, `A valid "hash.rounds" number between ${minRounds} and 8192 must be provided for ` + `hash algorithm ${options.hash.algorithm}.`, ); @@ -617,7 +617,7 @@ export class UserImportBuilder { rounds = getNumberField(options.hash, 'rounds'); if (isNaN(rounds) || rounds < 0 || rounds > 120000) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_ROUNDS, + authClientErrorCode.INVALID_HASH_ROUNDS, 'A valid "hash.rounds" number between 0 and 120000 must be provided for ' + `hash algorithm ${options.hash.algorithm}.`, ); @@ -631,7 +631,7 @@ export class UserImportBuilder { case 'SCRYPT': { if (!validator.isBuffer(options.hash.key)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_KEY, + authClientErrorCode.INVALID_HASH_KEY, 'A "hash.key" byte buffer must be provided for ' + `hash algorithm ${options.hash.algorithm}.`, ); @@ -639,7 +639,7 @@ export class UserImportBuilder { rounds = getNumberField(options.hash, 'rounds'); if (isNaN(rounds) || rounds <= 0 || rounds > 8) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_ROUNDS, + authClientErrorCode.INVALID_HASH_ROUNDS, 'A valid "hash.rounds" number between 1 and 8 must be provided for ' + `hash algorithm ${options.hash.algorithm}.`, ); @@ -647,7 +647,7 @@ export class UserImportBuilder { const memoryCost = getNumberField(options.hash, 'memoryCost'); if (isNaN(memoryCost) || memoryCost <= 0 || memoryCost > 14) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_MEMORY_COST, + authClientErrorCode.INVALID_HASH_MEMORY_COST, 'A valid "hash.memoryCost" number between 1 and 14 must be provided for ' + `hash algorithm ${options.hash.algorithm}.`, ); @@ -655,7 +655,7 @@ export class UserImportBuilder { if (typeof options.hash.saltSeparator !== 'undefined' && !validator.isBuffer(options.hash.saltSeparator)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_SALT_SEPARATOR, + authClientErrorCode.INVALID_HASH_SALT_SEPARATOR, '"hash.saltSeparator" must be a byte buffer.', ); } @@ -678,7 +678,7 @@ export class UserImportBuilder { const cpuMemCost = getNumberField(options.hash, 'memoryCost'); if (isNaN(cpuMemCost)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_MEMORY_COST, + authClientErrorCode.INVALID_HASH_MEMORY_COST, 'A valid "hash.memoryCost" number must be provided for ' + `hash algorithm ${options.hash.algorithm}.`, ); @@ -686,7 +686,7 @@ export class UserImportBuilder { const parallelization = getNumberField(options.hash, 'parallelization'); if (isNaN(parallelization)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_PARALLELIZATION, + authClientErrorCode.INVALID_HASH_PARALLELIZATION, 'A valid "hash.parallelization" number must be provided for ' + `hash algorithm ${options.hash.algorithm}.`, ); @@ -694,7 +694,7 @@ export class UserImportBuilder { const blockSize = getNumberField(options.hash, 'blockSize'); if (isNaN(blockSize)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_BLOCK_SIZE, + authClientErrorCode.INVALID_HASH_BLOCK_SIZE, 'A valid "hash.blockSize" number must be provided for ' + `hash algorithm ${options.hash.algorithm}.`, ); @@ -702,7 +702,7 @@ export class UserImportBuilder { const dkLen = getNumberField(options.hash, 'derivedKeyLength'); if (isNaN(dkLen)) { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_DERIVED_KEY_LENGTH, + authClientErrorCode.INVALID_HASH_DERIVED_KEY_LENGTH, 'A valid "hash.derivedKeyLength" number must be provided for ' + `hash algorithm ${options.hash.algorithm}.`, ); @@ -718,7 +718,7 @@ export class UserImportBuilder { } default: throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_ALGORITHM, + authClientErrorCode.INVALID_HASH_ALGORITHM, `Unsupported hash algorithm provider "${options.hash.algorithm}".`, ); } diff --git a/src/auth/user-record.ts b/src/auth/user-record.ts index 2310968849..1807ffd01c 100644 --- a/src/auth/user-record.ts +++ b/src/auth/user-record.ts @@ -18,7 +18,7 @@ import { deepCopy } from '../utils/deep-copy'; import { isNonNullObject } from '../utils/validator'; import * as utils from '../utils'; -import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; +import { authClientErrorCode, FirebaseAuthError } from './error'; /** * 'REDACTED', encoded as a base64 string. @@ -188,7 +188,7 @@ export abstract class MultiFactorInfo { const factorId = response && this.getFactorId(response); if (!factorId || !response || !response.mfaEnrollmentId) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid multi-factor info response'); } utils.addReadonlyGetter(this, 'uid', response.mfaEnrollmentId); @@ -330,7 +330,7 @@ export class MultiFactorSettings { const parsedEnrolledFactors: MultiFactorInfo[] = []; if (!isNonNullObject(response)) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid multi-factor response'); } else if (response.mfaInfo) { response.mfaInfo.forEach((factorResponse) => { @@ -457,7 +457,7 @@ export class UserInfo { // Provider user id and provider id are required. if (!response.rawId || !response.providerId) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid user info response'); } @@ -592,7 +592,7 @@ export class UserRecord { // The Firebase user id is required. if (!response.localId) { throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid user response'); } diff --git a/src/utils/error.ts b/src/utils/error.ts index f428e28780..c7f4279ec9 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -215,53 +215,6 @@ export class FirebaseAppError extends PrefixedFirebaseError { } } -/** - * Firebase Auth error code structure. This extends PrefixedFirebaseError. - */ -export class FirebaseAuthError extends PrefixedFirebaseError { - /** - * Creates the developer-facing error corresponding to the backend error code. - * - * @param serverErrorCode - The server error code. - * @param [message] The error message. The default message is used - * if not provided. - * @param [serverError] The error's raw server response. - * @returns The corresponding developer-facing error. - * @internal - */ - public static fromServerError( - serverErrorCode: string, - message?: string, - serverError?: RequestResponseError, - ): FirebaseAuthError { - // serverErrorCode could contain additional details: - // ERROR_CODE : Detailed message which can also contain colons - const colonSeparator = (serverErrorCode || '').indexOf(':'); - let customMessage = null; - if (colonSeparator !== -1) { - customMessage = serverErrorCode.substring(colonSeparator + 1).trim(); - serverErrorCode = serverErrorCode.substring(0, colonSeparator).trim(); - } - // If not found, default to internal error. - const clientCodeKey = AUTH_SERVER_TO_CLIENT_CODE[serverErrorCode] || 'INTERNAL_ERROR'; - const error: ErrorInfo = deepCopy((AuthClientErrorCode as any)[clientCodeKey]); - // Server detailed message should have highest priority. - error.message = customMessage || message || error.message; - error.cause = serverError; - error.httpResponse = serverError?.response ? toHttpResponse(serverError.response) : undefined; - return new FirebaseAuthError(error); - } - - /** - * @param info - The error code info. - * @param message - The error message. This will override the default message if provided. - */ - constructor(info: ErrorInfo, message?: string) { - // Override default message if custom message provided. - super('auth', info.code, message || info.message, info.httpResponse, info.cause); - - } -} /** * Firebase Database error code structure. This extends FirebaseError. @@ -483,407 +436,6 @@ export class AppErrorCodes { public static UNABLE_TO_PARSE_RESPONSE = 'unable-to-parse-response'; } -/** - * Auth client error codes and their default messages. - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export const AuthClientErrorCode = { - AUTH_BLOCKING_TOKEN_EXPIRED: { - code: 'auth-blocking-token-expired', - message: 'The provided Firebase Auth Blocking token is expired.', - }, - BILLING_NOT_ENABLED: { - code: 'billing-not-enabled', - message: 'Feature requires billing to be enabled.', - }, - CLAIMS_TOO_LARGE: { - code: 'claims-too-large', - message: 'Developer claims maximum payload size exceeded.', - }, - CONFIGURATION_EXISTS: { - code: 'configuration-exists', - message: 'A configuration already exists with the provided identifier.', - }, - CONFIGURATION_NOT_FOUND: { - code: 'configuration-not-found', - message: 'There is no configuration corresponding to the provided identifier.', - }, - ID_TOKEN_EXPIRED: { - code: 'id-token-expired', - message: 'The provided Firebase ID token is expired.', - }, - INVALID_ARGUMENT: { - code: 'argument-error', - message: 'Invalid argument provided.', - }, - INVALID_CONFIG: { - code: 'invalid-config', - message: 'The provided configuration is invalid.', - }, - EMAIL_ALREADY_EXISTS: { - code: 'email-already-exists', - message: 'The email address is already in use by another account.', - }, - EMAIL_NOT_FOUND: { - code: 'email-not-found', - message: 'There is no user record corresponding to the provided email.', - }, - FORBIDDEN_CLAIM: { - code: 'reserved-claim', - message: 'The specified developer claim is reserved and cannot be specified.', - }, - INVALID_ID_TOKEN: { - code: 'invalid-id-token', - message: 'The provided ID token is not a valid Firebase ID token.', - }, - ID_TOKEN_REVOKED: { - code: 'id-token-revoked', - message: 'The Firebase ID token has been revoked.', - }, - INTERNAL_ERROR: { - code: 'internal-error', - message: 'An internal error has occurred.', - }, - INVALID_CLAIMS: { - code: 'invalid-claims', - message: 'The provided custom claim attributes are invalid.', - }, - INVALID_CONTINUE_URI: { - code: 'invalid-continue-uri', - message: 'The continue URL must be a valid URL string.', - }, - INVALID_CREATION_TIME: { - code: 'invalid-creation-time', - message: 'The creation time must be a valid UTC date string.', - }, - INVALID_CREDENTIAL: { - code: 'invalid-credential', - message: 'Invalid credential object provided.', - }, - INVALID_DISABLED_FIELD: { - code: 'invalid-disabled-field', - message: 'The disabled field must be a boolean.', - }, - INVALID_DISPLAY_NAME: { - code: 'invalid-display-name', - message: 'The displayName field must be a valid string.', - }, - INVALID_DYNAMIC_LINK_DOMAIN: { - code: 'invalid-dynamic-link-domain', - message: 'The provided dynamic link domain is not configured or authorized ' + - 'for the current project.', - }, - INVALID_HOSTING_LINK_DOMAIN: { - code: 'invalid-hosting-link-domain', - message: 'The provided hosting link domain is not configured in Firebase ' + - 'Hosting or is not owned by the current project.', - }, - INVALID_EMAIL_VERIFIED: { - code: 'invalid-email-verified', - message: 'The emailVerified field must be a boolean.', - }, - INVALID_EMAIL: { - code: 'invalid-email', - message: 'The email address is improperly formatted.', - }, - INVALID_NEW_EMAIL: { - code: 'invalid-new-email', - message: 'The new email address is improperly formatted.', - }, - INVALID_ENROLLED_FACTORS: { - code: 'invalid-enrolled-factors', - message: 'The enrolled factors must be a valid array of MultiFactorInfo objects.', - }, - INVALID_ENROLLMENT_TIME: { - code: 'invalid-enrollment-time', - message: 'The second factor enrollment time must be a valid UTC date string.', - }, - INVALID_HASH_ALGORITHM: { - code: 'invalid-hash-algorithm', - message: 'The hash algorithm must match one of the strings in the list of ' + - 'supported algorithms.', - }, - INVALID_HASH_BLOCK_SIZE: { - code: 'invalid-hash-block-size', - message: 'The hash block size must be a valid number.', - }, - INVALID_HASH_DERIVED_KEY_LENGTH: { - code: 'invalid-hash-derived-key-length', - message: 'The hash derived key length must be a valid number.', - }, - INVALID_HASH_KEY: { - code: 'invalid-hash-key', - message: 'The hash key must a valid byte buffer.', - }, - INVALID_HASH_MEMORY_COST: { - code: 'invalid-hash-memory-cost', - message: 'The hash memory cost must be a valid number.', - }, - INVALID_HASH_PARALLELIZATION: { - code: 'invalid-hash-parallelization', - message: 'The hash parallelization must be a valid number.', - }, - INVALID_HASH_ROUNDS: { - code: 'invalid-hash-rounds', - message: 'The hash rounds must be a valid number.', - }, - INVALID_HASH_SALT_SEPARATOR: { - code: 'invalid-hash-salt-separator', - message: 'The hashing algorithm salt separator field must be a valid byte buffer.', - }, - INVALID_LAST_SIGN_IN_TIME: { - code: 'invalid-last-sign-in-time', - message: 'The last sign-in time must be a valid UTC date string.', - }, - INVALID_NAME: { - code: 'invalid-name', - message: 'The resource name provided is invalid.', - }, - INVALID_OAUTH_CLIENT_ID: { - code: 'invalid-oauth-client-id', - message: 'The provided OAuth client ID is invalid.', - }, - INVALID_PAGE_TOKEN: { - code: 'invalid-page-token', - message: 'The page token must be a valid non-empty string.', - }, - INVALID_PASSWORD: { - code: 'invalid-password', - message: 'The password must be a string with at least 6 characters.', - }, - INVALID_PASSWORD_HASH: { - code: 'invalid-password-hash', - message: 'The password hash must be a valid byte buffer.', - }, - INVALID_PASSWORD_SALT: { - code: 'invalid-password-salt', - message: 'The password salt must be a valid byte buffer.', - }, - INVALID_PHONE_NUMBER: { - code: 'invalid-phone-number', - message: 'The phone number must be a non-empty E.164 standard compliant identifier ' + - 'string.', - }, - INVALID_PHOTO_URL: { - code: 'invalid-photo-url', - message: 'The photoURL field must be a valid URL.', - }, - INVALID_PROJECT_ID: { - code: 'invalid-project-id', - message: 'Invalid parent project. Either parent project doesn\'t exist or didn\'t enable multi-tenancy.', - }, - INVALID_PROVIDER_DATA: { - code: 'invalid-provider-data', - message: 'The providerData must be a valid array of UserInfo objects.', - }, - INVALID_PROVIDER_ID: { - code: 'invalid-provider-id', - message: 'The providerId must be a valid supported provider identifier string.', - }, - INVALID_PROVIDER_UID: { - code: 'invalid-provider-uid', - message: 'The providerUid must be a valid provider uid string.', - }, - INVALID_OAUTH_RESPONSETYPE: { - code: 'invalid-oauth-responsetype', - message: 'Only exactly one OAuth responseType should be set to true.', - }, - INVALID_SESSION_COOKIE_DURATION: { - code: 'invalid-session-cookie-duration', - message: 'The session cookie duration must be a valid number in milliseconds ' + - 'between 5 minutes and 2 weeks.', - }, - INVALID_TENANT_ID: { - code: 'invalid-tenant-id', - message: 'The tenant ID must be a valid non-empty string.', - }, - INVALID_TENANT_TYPE: { - code: 'invalid-tenant-type', - message: 'Tenant type must be either "full_service" or "lightweight".', - }, - INVALID_TESTING_PHONE_NUMBER: { - code: 'invalid-testing-phone-number', - message: 'Invalid testing phone number or invalid test code provided.', - }, - INVALID_UID: { - code: 'invalid-uid', - message: 'The uid must be a non-empty string with at most 128 characters.', - }, - INVALID_USER_IMPORT: { - code: 'invalid-user-import', - message: 'The user record to import is invalid.', - }, - INVALID_TOKENS_VALID_AFTER_TIME: { - code: 'invalid-tokens-valid-after-time', - message: 'The tokensValidAfterTime must be a valid UTC number in seconds.', - }, - MISMATCHING_TENANT_ID: { - code: 'mismatching-tenant-id', - message: 'User tenant ID does not match with the current TenantAwareAuth tenant ID.', - }, - MISSING_ANDROID_PACKAGE_NAME: { - code: 'missing-android-pkg-name', - message: 'An Android Package Name must be provided if the Android App is ' + - 'required to be installed.', - }, - MISSING_CONFIG: { - code: 'missing-config', - message: 'The provided configuration is missing required attributes.', - }, - MISSING_CONTINUE_URI: { - code: 'missing-continue-uri', - message: 'A valid continue URL must be provided in the request.', - }, - MISSING_DISPLAY_NAME: { - code: 'missing-display-name', - message: 'The resource being created or edited is missing a valid display name.', - }, - MISSING_EMAIL: { - code: 'missing-email', - message: 'The email is required for the specified action. For example, a multi-factor user ' + - 'requires a verified email.', - }, - MISSING_IOS_BUNDLE_ID: { - code: 'missing-ios-bundle-id', - message: 'The request is missing an iOS Bundle ID.', - }, - MISSING_ISSUER: { - code: 'missing-issuer', - message: 'The OAuth/OIDC configuration issuer must not be empty.', - }, - MISSING_HASH_ALGORITHM: { - code: 'missing-hash-algorithm', - message: 'Importing users with password hashes requires that the hashing ' + - 'algorithm and its parameters be provided.', - }, - MISSING_OAUTH_CLIENT_ID: { - code: 'missing-oauth-client-id', - message: 'The OAuth/OIDC configuration client ID must not be empty.', - }, - MISSING_OAUTH_CLIENT_SECRET: { - code: 'missing-oauth-client-secret', - message: 'The OAuth configuration client secret is required to enable OIDC code flow.', - }, - MISSING_PROVIDER_ID: { - code: 'missing-provider-id', - message: 'A valid provider ID must be provided in the request.', - }, - MISSING_SAML_RELYING_PARTY_CONFIG: { - code: 'missing-saml-relying-party-config', - message: 'The SAML configuration provided is missing a relying party configuration.', - }, - MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED: { - code: 'test-phone-number-limit-exceeded', - message: 'The maximum allowed number of test phone number / code pairs has been exceeded.', - }, - MAXIMUM_USER_COUNT_EXCEEDED: { - code: 'maximum-user-count-exceeded', - message: 'The maximum allowed number of users to import has been exceeded.', - }, - MISSING_UID: { - code: 'missing-uid', - message: 'A uid identifier is required for the current operation.', - }, - OPERATION_NOT_ALLOWED: { - code: 'operation-not-allowed', - message: 'The given sign-in provider is disabled for this Firebase project. ' + - 'Enable it in the Firebase console, under the sign-in method tab of the ' + - 'Auth section.', - }, - PHONE_NUMBER_ALREADY_EXISTS: { - code: 'phone-number-already-exists', - message: 'The user with the provided phone number already exists.', - }, - PROJECT_NOT_FOUND: { - code: 'project-not-found', - message: 'No Firebase project was found for the provided credential.', - }, - INSUFFICIENT_PERMISSION: { - code: 'insufficient-permission', - message: 'Credential implementation provided to initializeApp() via the "credential" property ' + - 'has insufficient permission to access the requested resource. See ' + - 'https://firebase.google.com/docs/admin/setup for details on how to authenticate this SDK ' + - 'with appropriate permissions.', - }, - QUOTA_EXCEEDED: { - code: 'quota-exceeded', - message: 'The project quota for the specified operation has been exceeded.', - }, - SECOND_FACTOR_LIMIT_EXCEEDED: { - code: 'second-factor-limit-exceeded', - message: 'The maximum number of allowed second factors on a user has been exceeded.', - }, - SECOND_FACTOR_UID_ALREADY_EXISTS: { - code: 'second-factor-uid-already-exists', - message: 'The specified second factor "uid" already exists.', - }, - SESSION_COOKIE_EXPIRED: { - code: 'session-cookie-expired', - message: 'The Firebase session cookie is expired.', - }, - SESSION_COOKIE_REVOKED: { - code: 'session-cookie-revoked', - message: 'The Firebase session cookie has been revoked.', - }, - TENANT_NOT_FOUND: { - code: 'tenant-not-found', - message: 'There is no tenant corresponding to the provided identifier.', - }, - UID_ALREADY_EXISTS: { - code: 'uid-already-exists', - message: 'The user with the provided uid already exists.', - }, - UNAUTHORIZED_DOMAIN: { - code: 'unauthorized-continue-uri', - message: 'The domain of the continue URL is not whitelisted. Whitelist the domain in the ' + - 'Firebase console.', - }, - UNSUPPORTED_FIRST_FACTOR: { - code: 'unsupported-first-factor', - message: 'A multi-factor user requires a supported first factor.', - }, - UNSUPPORTED_SECOND_FACTOR: { - code: 'unsupported-second-factor', - message: 'The request specified an unsupported type of second factor.', - }, - UNSUPPORTED_TENANT_OPERATION: { - code: 'unsupported-tenant-operation', - message: 'This operation is not supported in a multi-tenant context.', - }, - UNVERIFIED_EMAIL: { - code: 'unverified-email', - message: 'A verified email is required for the specified action. For example, a multi-factor user ' + - 'requires a verified email.', - }, - USER_NOT_FOUND: { - code: 'user-not-found', - message: 'There is no user record corresponding to the provided identifier.', - }, - NOT_FOUND: { - code: 'not-found', - message: 'The requested resource was not found.', - }, - USER_DISABLED: { - code: 'user-disabled', - message: 'The user record is disabled.', - }, - USER_NOT_DISABLED: { - code: 'user-not-disabled', - message: 'The user must be disabled in order to bulk delete it (or you must pass force=true).', - }, - INVALID_RECAPTCHA_ACTION: { - code: 'invalid-recaptcha-action', - message: 'reCAPTCHA action must be "BLOCK".' - }, - INVALID_RECAPTCHA_ENFORCEMENT_STATE: { - code: 'invalid-recaptcha-enforcement-state', - message: 'reCAPTCHA enforcement state must be either "OFF", "AUDIT" or "ENFORCE".' - }, - RECAPTCHA_NOT_ENABLED: { - code: 'racaptcha-not-enabled', - message: 'reCAPTCHA enterprise is not enabled.' - } -} satisfies Record; /** * Messaging client error codes and their default messages. @@ -1024,137 +576,6 @@ export type ProjectManagementErrorCode = | 'service-unavailable' | 'unknown-error'; -/** @const {ServerToClientCode} Auth server to client enum error codes. */ -const AUTH_SERVER_TO_CLIENT_CODE: ServerToClientCode = { - // Feature being configured or used requires a billing account. - BILLING_NOT_ENABLED: 'BILLING_NOT_ENABLED', - // Claims payload is too large. - CLAIMS_TOO_LARGE: 'CLAIMS_TOO_LARGE', - // Configuration being added already exists. - CONFIGURATION_EXISTS: 'CONFIGURATION_EXISTS', - // Configuration not found. - CONFIGURATION_NOT_FOUND: 'CONFIGURATION_NOT_FOUND', - // Provided credential has insufficient permissions. - INSUFFICIENT_PERMISSION: 'INSUFFICIENT_PERMISSION', - // Provided configuration has invalid fields. - INVALID_CONFIG: 'INVALID_CONFIG', - // Provided configuration identifier is invalid. - INVALID_CONFIG_ID: 'INVALID_PROVIDER_ID', - // ActionCodeSettings missing continue URL. - INVALID_CONTINUE_URI: 'INVALID_CONTINUE_URI', - // Dynamic link domain in provided ActionCodeSettings is not authorized. - INVALID_DYNAMIC_LINK_DOMAIN: 'INVALID_DYNAMIC_LINK_DOMAIN', - // Hosting link domain in provided ActionCodeSettings is not owned by the current project. - INVALID_HOSTING_LINK_DOMAIN: 'INVALID_HOSTING_LINK_DOMAIN', - // uploadAccount provides an email that already exists. - DUPLICATE_EMAIL: 'EMAIL_ALREADY_EXISTS', - // uploadAccount provides a localId that already exists. - DUPLICATE_LOCAL_ID: 'UID_ALREADY_EXISTS', - // Request specified a multi-factor enrollment ID that already exists. - DUPLICATE_MFA_ENROLLMENT_ID: 'SECOND_FACTOR_UID_ALREADY_EXISTS', - // setAccountInfo email already exists. - EMAIL_EXISTS: 'EMAIL_ALREADY_EXISTS', - // /accounts:sendOobCode for password reset when user is not found. - EMAIL_NOT_FOUND: 'EMAIL_NOT_FOUND', - // Reserved claim name. - FORBIDDEN_CLAIM: 'FORBIDDEN_CLAIM', - // Invalid claims provided. - INVALID_CLAIMS: 'INVALID_CLAIMS', - // Invalid session cookie duration. - INVALID_DURATION: 'INVALID_SESSION_COOKIE_DURATION', - // Invalid email provided. - INVALID_EMAIL: 'INVALID_EMAIL', - // Invalid new email provided. - INVALID_NEW_EMAIL: 'INVALID_NEW_EMAIL', - // Invalid tenant display name. This can be thrown on CreateTenant and UpdateTenant. - INVALID_DISPLAY_NAME: 'INVALID_DISPLAY_NAME', - // Invalid ID token provided. - INVALID_ID_TOKEN: 'INVALID_ID_TOKEN', - // Invalid tenant/parent resource name. - INVALID_NAME: 'INVALID_NAME', - // OIDC configuration has an invalid OAuth client ID. - INVALID_OAUTH_CLIENT_ID: 'INVALID_OAUTH_CLIENT_ID', - // Invalid page token. - INVALID_PAGE_SELECTION: 'INVALID_PAGE_TOKEN', - // Invalid phone number. - INVALID_PHONE_NUMBER: 'INVALID_PHONE_NUMBER', - // Invalid agent project. Either agent project doesn't exist or didn't enable multi-tenancy. - INVALID_PROJECT_ID: 'INVALID_PROJECT_ID', - // Invalid provider ID. - INVALID_PROVIDER_ID: 'INVALID_PROVIDER_ID', - // Invalid service account. - INVALID_SERVICE_ACCOUNT: 'INVALID_SERVICE_ACCOUNT', - // Invalid testing phone number. - INVALID_TESTING_PHONE_NUMBER: 'INVALID_TESTING_PHONE_NUMBER', - // Invalid tenant type. - INVALID_TENANT_TYPE: 'INVALID_TENANT_TYPE', - // Missing Android package name. - MISSING_ANDROID_PACKAGE_NAME: 'MISSING_ANDROID_PACKAGE_NAME', - // Missing configuration. - MISSING_CONFIG: 'MISSING_CONFIG', - // Missing configuration identifier. - MISSING_CONFIG_ID: 'MISSING_PROVIDER_ID', - // Missing tenant display name: This can be thrown on CreateTenant and UpdateTenant. - MISSING_DISPLAY_NAME: 'MISSING_DISPLAY_NAME', - // Email is required for the specified action. For example a multi-factor user requires - // a verified email. - MISSING_EMAIL: 'MISSING_EMAIL', - // Missing iOS bundle ID. - MISSING_IOS_BUNDLE_ID: 'MISSING_IOS_BUNDLE_ID', - // Missing OIDC issuer. - MISSING_ISSUER: 'MISSING_ISSUER', - // No localId provided (deleteAccount missing localId). - MISSING_LOCAL_ID: 'MISSING_UID', - // OIDC configuration is missing an OAuth client ID. - MISSING_OAUTH_CLIENT_ID: 'MISSING_OAUTH_CLIENT_ID', - // Missing provider ID. - MISSING_PROVIDER_ID: 'MISSING_PROVIDER_ID', - // Missing SAML RP config. - MISSING_SAML_RELYING_PARTY_CONFIG: 'MISSING_SAML_RELYING_PARTY_CONFIG', - // Empty user list in uploadAccount. - MISSING_USER_ACCOUNT: 'MISSING_UID', - // Password auth disabled in console. - OPERATION_NOT_ALLOWED: 'OPERATION_NOT_ALLOWED', - // Provided credential has insufficient permissions. - PERMISSION_DENIED: 'INSUFFICIENT_PERMISSION', - // Phone number already exists. - PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_ALREADY_EXISTS', - // Project not found. - PROJECT_NOT_FOUND: 'PROJECT_NOT_FOUND', - // In multi-tenancy context: project creation quota exceeded. - QUOTA_EXCEEDED: 'QUOTA_EXCEEDED', - // Currently only 5 second factors can be set on the same user. - SECOND_FACTOR_LIMIT_EXCEEDED: 'SECOND_FACTOR_LIMIT_EXCEEDED', - // Tenant not found. - TENANT_NOT_FOUND: 'TENANT_NOT_FOUND', - // Tenant ID mismatch. - TENANT_ID_MISMATCH: 'MISMATCHING_TENANT_ID', - // Token expired error. - TOKEN_EXPIRED: 'ID_TOKEN_EXPIRED', - // Continue URL provided in ActionCodeSettings has a domain that is not whitelisted. - UNAUTHORIZED_DOMAIN: 'UNAUTHORIZED_DOMAIN', - // A multi-factor user requires a supported first factor. - UNSUPPORTED_FIRST_FACTOR: 'UNSUPPORTED_FIRST_FACTOR', - // The request specified an unsupported type of second factor. - UNSUPPORTED_SECOND_FACTOR: 'UNSUPPORTED_SECOND_FACTOR', - // Operation is not supported in a multi-tenant context. - UNSUPPORTED_TENANT_OPERATION: 'UNSUPPORTED_TENANT_OPERATION', - // A verified email is required for the specified action. For example a multi-factor user - // requires a verified email. - UNVERIFIED_EMAIL: 'UNVERIFIED_EMAIL', - // User on which action is to be performed is not found. - USER_NOT_FOUND: 'USER_NOT_FOUND', - // User record is disabled. - USER_DISABLED: 'USER_DISABLED', - // Password provided is too weak. - WEAK_PASSWORD: 'INVALID_PASSWORD', - // Unrecognized reCAPTCHA action. - INVALID_RECAPTCHA_ACTION: 'INVALID_RECAPTCHA_ACTION', - // Unrecognized reCAPTCHA enforcement state. - INVALID_RECAPTCHA_ENFORCEMENT_STATE: 'INVALID_RECAPTCHA_ENFORCEMENT_STATE', - // reCAPTCHA is not enabled for account defender. - RECAPTCHA_NOT_ENABLED: 'RECAPTCHA_NOT_ENABLED' -}; /** @const {ServerToClientCode} Messaging server to client enum error codes. */ const MESSAGING_SERVER_TO_CLIENT_CODE: ServerToClientCode = { diff --git a/test/unit/auth/action-code-settings-builder.spec.ts b/test/unit/auth/action-code-settings-builder.spec.ts index f8eed33d74..eb3fadf774 100644 --- a/test/unit/auth/action-code-settings-builder.spec.ts +++ b/test/unit/auth/action-code-settings-builder.spec.ts @@ -20,7 +20,7 @@ import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; import { ActionCodeSettingsBuilder } from '../../../src/auth/action-code-settings-builder'; -import { AuthClientErrorCode } from '../../../src/utils/error'; +import { authClientErrorCode } from '../../../src/auth/error'; chai.should(); @@ -75,7 +75,7 @@ describe('ActionCodeSettingsBuilder', () => { dynamicLinkDomain: 'custom.page.link', linkDomain: TEST_LINK_DOMAIN, } as any); - }).to.throw(AuthClientErrorCode.MISSING_CONTINUE_URI.message); + }).to.throw(authClientErrorCode.MISSING_CONTINUE_URI.message); }); const invalidUrls = [null, NaN, 0, 1, true, false, '', 'a', [], [1, 'a'], {}, { a: 1 }, _.noop]; @@ -85,7 +85,7 @@ describe('ActionCodeSettingsBuilder', () => { return new ActionCodeSettingsBuilder({ url, } as any); - }).to.throw(AuthClientErrorCode.INVALID_CONTINUE_URI.message); + }).to.throw(authClientErrorCode.INVALID_CONTINUE_URI.message); }); }); @@ -110,7 +110,7 @@ describe('ActionCodeSettingsBuilder', () => { handleCodeInApp: true, dynamicLinkDomain: domain, } as any); - }).to.throw(AuthClientErrorCode.INVALID_DYNAMIC_LINK_DOMAIN.message); + }).to.throw(authClientErrorCode.INVALID_DYNAMIC_LINK_DOMAIN.message); }); }); @@ -124,7 +124,7 @@ describe('ActionCodeSettingsBuilder', () => { handleCodeInApp: true, linkDomain: domain, } as any); - }).to.throw(AuthClientErrorCode.INVALID_HOSTING_LINK_DOMAIN.message); + }).to.throw(authClientErrorCode.INVALID_HOSTING_LINK_DOMAIN.message); }); }); @@ -148,7 +148,7 @@ describe('ActionCodeSettingsBuilder', () => { handleCodeInApp: true, iOS: {}, } as any); - }).to.throw(AuthClientErrorCode.MISSING_IOS_BUNDLE_ID.message); + }).to.throw(authClientErrorCode.MISSING_IOS_BUNDLE_ID.message); }); const invalidBundleIds = [null, NaN, 0, 1, true, false, '', ['com.example.ios'], _.noop]; @@ -184,7 +184,7 @@ describe('ActionCodeSettingsBuilder', () => { handleCodeInApp: true, android: {}, } as any); - }).to.throw(AuthClientErrorCode.MISSING_ANDROID_PACKAGE_NAME.message); + }).to.throw(authClientErrorCode.MISSING_ANDROID_PACKAGE_NAME.message); }); const invalidPackageNames = [null, NaN, 0, 1, true, false, '', ['com.example.android'], _.noop]; diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index 8bcad5c2ac..36872c4498 100644 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -38,7 +38,7 @@ import { EMAIL_ACTION_REQUEST_TYPES, TenantAwareAuthRequestHandler, AbstractAuthRequestHandler, } from '../../../src/auth/auth-api-request'; import { UserImportBuilder } from '../../../src/auth/user-import-builder'; -import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error'; +import { authClientErrorCode, FirebaseAuthError } from '../../../src/auth/error'; import { ActionCodeSettingsBuilder } from '../../../src/auth/action-code-settings-builder'; import { SAMLConfigServerResponse } from '../../../src/auth/auth-config'; import { expectUserImportResult } from './user-import-builder.spec'; @@ -1015,7 +1015,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given an invalid ID token', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ID_TOKEN, + authClientErrorCode.INVALID_ID_TOKEN, ); const requestHandler = handler.init(mockApp); @@ -1028,7 +1028,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given an invalid duration', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION, + authClientErrorCode.INVALID_SESSION_COOKIE_DURATION, ); const requestHandler = handler.init(mockApp); @@ -1041,7 +1041,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given a duration less than minimum allowed', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION, + authClientErrorCode.INVALID_SESSION_COOKIE_DURATION, ); const outOfBoundDuration = 60 * 1000 * 5 - 1; @@ -1055,7 +1055,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given a duration greater than maximum allowed', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION, + authClientErrorCode.INVALID_SESSION_COOKIE_DURATION, ); // Add more than a second since this value is Math.floor()'ed const outOfBoundDuration = 60 * 60 * 1000 * 24 * 14 + 1001; @@ -1074,7 +1074,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { message: 'INVALID_ID_TOKEN', }, }); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_ID_TOKEN); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_ID_TOKEN); const data = { idToken: 'invalid-token', validDuration: durationInMs / 1000 }; const stub = sinon.stub(HttpClient.prototype, 'send').rejects(expectedResult); stubs.push(stub); @@ -1120,7 +1120,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const expectedResult = utils.responseFrom({ kind: 'identitytoolkit#GetAccountInfoResponse', }); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); const data = { email: ['user@example.com'] }; const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); stubs.push(stub); @@ -1160,7 +1160,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const expectedResult = utils.responseFrom({ kind: 'identitytoolkit#GetAccountInfoResponse', }); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); const data = { localId: ['uid'] }; const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); stubs.push(stub); @@ -1232,7 +1232,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given an invalid phoneNumber', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_PHONE_NUMBER); + authClientErrorCode.INVALID_PHONE_NUMBER); const stub = sinon.stub(HttpClient.prototype, 'send'); stubs.push(stub); @@ -1250,7 +1250,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const expectedResult = utils.responseFrom({ kind: 'identitytoolkit#GetAccountInfoResponse', }); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); const data = { phoneNumber: ['+11234567890'], }; @@ -1443,7 +1443,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should throw on invalid options without making an underlying API call', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_ALGORITHM, + authClientErrorCode.INVALID_HASH_ALGORITHM, 'Unsupported hash algorithm provider "invalid".', ); const invalidOptions = { @@ -1463,7 +1463,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should throw when 1001 UserImportRecords are provided', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.MAXIMUM_USER_COUNT_EXCEEDED, + authClientErrorCode.MAXIMUM_USER_COUNT_EXCEEDED, 'A maximum of 1000 users can be imported at once.', ); const stub = sinon.stub(HttpClient.prototype, 'send'); @@ -1490,7 +1490,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const mismatchIndex = 34; const mismatchTenantId = 'MISMATCHING-TENANT-ID'; const expectedError = new FirebaseAuthError( - AuthClientErrorCode.MISMATCHING_TENANT_ID, + authClientErrorCode.MISMATCHING_TENANT_ID, `UserRecord of index "${mismatchIndex}" has mismatching tenant ID "${mismatchTenantId}"`, ); const stub = sinon.stub(HttpClient.prototype, 'send'); @@ -1585,8 +1585,8 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { successCount: 0, failureCount: 2, errors: [ - { index: 0, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER) }, - { index: 1, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL) }, + { index: 0, error: new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER) }, + { index: 1, error: new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL) }, ], }; const stub = sinon.stub(HttpClient.prototype, 'send'); @@ -1713,79 +1713,79 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { successCount: 0, failureCount: testUsers.length, errors: [ - { index: 0, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_DISPLAY_NAME) }, - { index: 1, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_UID) }, - { index: 2, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL) }, - { index: 3, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER) }, - { index: 4, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL_VERIFIED) }, - { index: 5, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_PHOTO_URL) }, - { index: 6, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_DISABLED_FIELD) }, - { index: 7, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_CREATION_TIME) }, - { index: 8, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_LAST_SIGN_IN_TIME) }, + { index: 0, error: new FirebaseAuthError(authClientErrorCode.INVALID_DISPLAY_NAME) }, + { index: 1, error: new FirebaseAuthError(authClientErrorCode.INVALID_UID) }, + { index: 2, error: new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL) }, + { index: 3, error: new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER) }, + { index: 4, error: new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL_VERIFIED) }, + { index: 5, error: new FirebaseAuthError(authClientErrorCode.INVALID_PHOTO_URL) }, + { index: 6, error: new FirebaseAuthError(authClientErrorCode.INVALID_DISABLED_FIELD) }, + { index: 7, error: new FirebaseAuthError(authClientErrorCode.INVALID_CREATION_TIME) }, + { index: 8, error: new FirebaseAuthError(authClientErrorCode.INVALID_LAST_SIGN_IN_TIME) }, { index: 9, error: new FirebaseAuthError( - AuthClientErrorCode.FORBIDDEN_CLAIM, + authClientErrorCode.FORBIDDEN_CLAIM, 'Developer claim "aud" is reserved and cannot be specified.', ), }, - { index: 10, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD_HASH) }, - { index: 11, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD_SALT) }, + { index: 10, error: new FirebaseAuthError(authClientErrorCode.INVALID_PASSWORD_HASH) }, + { index: 11, error: new FirebaseAuthError(authClientErrorCode.INVALID_PASSWORD_SALT) }, { index: 12, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_UID, + authClientErrorCode.INVALID_UID, 'The provider "uid" for "google.com" must be a valid non-empty string.', ), }, { index: 13, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_DISPLAY_NAME, + authClientErrorCode.INVALID_DISPLAY_NAME, 'The provider "displayName" for "google.com" must be a valid string.', ), }, { index: 14, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_EMAIL, + authClientErrorCode.INVALID_EMAIL, 'The provider "email" for "google.com" must be a valid email string.', ), }, { index: 15, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_PHOTO_URL, + authClientErrorCode.INVALID_PHOTO_URL, 'The provider "photoURL" for "google.com" must be a valid URL string.', ), }, - { index: 16, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID) }, - { index: 17, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_UID) }, + { index: 16, error: new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID) }, + { index: 17, error: new FirebaseAuthError(authClientErrorCode.INVALID_UID) }, { index: 18, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_UID, + authClientErrorCode.INVALID_UID, 'The second factor "uid" must be a valid non-empty string.', ), }, { index: 19, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_DISPLAY_NAME, + authClientErrorCode.INVALID_DISPLAY_NAME, 'The second factor "displayName" for "mfaUid1" must be a valid string.', ), }, { index: 20, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_ENROLLMENT_TIME, + authClientErrorCode.INVALID_ENROLLMENT_TIME, 'The second factor "enrollmentTime" for "mfaUid2" must be a valid UTC date string.', ), }, { index: 21, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_PHONE_NUMBER, + authClientErrorCode.INVALID_PHONE_NUMBER, 'The second factor "phoneNumber" for "mfaUid3" must be a non-empty ' + 'E.164 standard compliant identifier string.', ), @@ -1793,7 +1793,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { index: 22, error: new FirebaseAuthError( - AuthClientErrorCode.UNSUPPORTED_SECOND_FACTOR, + authClientErrorCode.UNSUPPORTED_SECOND_FACTOR, `Unsupported second factor "${JSON.stringify(testUsers[22].multiFactor.enrolledFactors[0])}" provided.`, ), }, @@ -1817,7 +1817,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }, }); const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'An internal error has occurred. Raw server response: ' + `"${JSON.stringify(expectedServerError.response.data)}"`, ); @@ -1897,7 +1897,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given an invalid maxResults', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive integer that does not ' + 'exceed 1000.', ); @@ -1912,7 +1912,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given an invalid next page token', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_PAGE_TOKEN, + authClientErrorCode.INVALID_PAGE_TOKEN, ); const requestHandler = handler.init(mockApp); @@ -2272,7 +2272,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters such as email', () => { // Expected error when an invalid email is provided. - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL); const requestHandler = handler.init(mockApp); // Send update request with invalid email. return requestHandler.updateExistingAccount(uid, invalidData) @@ -2294,7 +2294,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { name: 'invalid second factor uid', error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_UID, + authClientErrorCode.INVALID_UID, 'The second factor "uid" must be a valid non-empty string.', ), secondFactor: { @@ -2307,7 +2307,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { name: 'invalid second factor display name', error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_DISPLAY_NAME, + authClientErrorCode.INVALID_DISPLAY_NAME, 'The second factor "displayName" for "enrolledSecondFactor1" must be a valid string.', ), secondFactor: { @@ -2320,7 +2320,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { name: 'invalid second factor phone number', error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_PHONE_NUMBER, + authClientErrorCode.INVALID_PHONE_NUMBER, 'The second factor "phoneNumber" for "enrolledSecondFactor1" must be a non-empty ' + 'E.164 standard compliant identifier string.'), secondFactor: { @@ -2333,7 +2333,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { name: 'invalid second factor enrollment time', error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_ENROLLMENT_TIME, + authClientErrorCode.INVALID_ENROLLMENT_TIME, 'The second factor "enrollmentTime" for "enrolledSecondFactor1" must be a valid ' + 'UTC date string.'), secondFactor: { @@ -2347,7 +2347,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { name: 'invalid second factor type', error: new FirebaseAuthError( - AuthClientErrorCode.UNSUPPORTED_SECOND_FACTOR, + authClientErrorCode.UNSUPPORTED_SECOND_FACTOR, `Unsupported second factor "${JSON.stringify(unsupportedSecondFactor)}" provided.`), secondFactor: unsupportedSecondFactor, }, @@ -2375,7 +2375,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { (dataWithModifiedTenantId as any).tenantId = 'MODIFIED-TENANT-ID'; // Expected error when a tenant ID is provided. const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"tenantId" is an invalid "UpdateRequest" property.', ); const requestHandler = handler.init(mockApp); @@ -2391,7 +2391,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters such as phoneNumber', () => { // Expected error when an invalid phone number is provided. - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER); const requestHandler = handler.init(mockApp); // Send update request with invalid phone number. return requestHandler.updateExistingAccount(uid, invalidPhoneNumberData) @@ -2480,7 +2480,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters such as uid', () => { // Expected error when an invalid uid is provided. - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_UID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_UID); const requestHandler = handler.init(mockApp); // Send request with invalid uid. return requestHandler.setCustomUserClaims('', claims) @@ -2495,7 +2495,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters such as customClaims', () => { // Expected error when invalid claims are provided. const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'CustomUserClaims argument must be an object or null.', ); const requestHandler = handler.init(mockApp); @@ -2512,7 +2512,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given customClaims with blacklisted claims', () => { // Expected error when invalid claims are provided. const expectedError = new FirebaseAuthError( - AuthClientErrorCode.FORBIDDEN_CLAIM, + authClientErrorCode.FORBIDDEN_CLAIM, 'Developer claim "aud" is reserved and cannot be specified.', ); const requestHandler = handler.init(mockApp); @@ -2588,7 +2588,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given an invalid uid', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_UID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_UID); const invalidUid: any = { localId: uid }; const requestHandler = handler.init(mockApp); @@ -2730,7 +2730,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters such as email', () => { // Expected error when an invalid email is provided. - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL); const requestHandler = handler.init(mockApp); // Create new account with invalid email. return requestHandler.createNewAccount(invalidData) @@ -2778,7 +2778,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { name: 'unsupported second factor uid', error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"uid" is not supported when adding second factors via "createUser()"', ), secondFactor: { @@ -2791,7 +2791,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { name: 'invalid second factor display name', error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_DISPLAY_NAME, + authClientErrorCode.INVALID_DISPLAY_NAME, 'The second factor "displayName" for "+16505557348" must be a valid string.', ), secondFactor: { @@ -2803,7 +2803,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { name: 'invalid second factor phone number', error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_PHONE_NUMBER, + authClientErrorCode.INVALID_PHONE_NUMBER, 'The second factor "phoneNumber" for "invalid" must be a non-empty ' + 'E.164 standard compliant identifier string.'), secondFactor: { @@ -2815,7 +2815,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { name: 'unsupported second factor enrollment time', error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"enrollmentTime" is not supported when adding second factors via "createUser()"'), secondFactor: { phoneNumber: '+16505557348', @@ -2827,7 +2827,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { { name: 'invalid second factor type', error: new FirebaseAuthError( - AuthClientErrorCode.UNSUPPORTED_SECOND_FACTOR, + authClientErrorCode.UNSUPPORTED_SECOND_FACTOR, `Unsupported second factor "${JSON.stringify(unsupportedSecondFactor)}" provided.`), secondFactor: unsupportedSecondFactor, }, @@ -2857,7 +2857,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given tenantId in CreateRequest', () => { // Expected error when a tenantId is provided. const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"tenantId" is an invalid "CreateRequest" property.'); const validDataWithTenantId = deepCopy(validData); (validDataWithTenantId as any).tenantId = TENANT_ID; @@ -2875,7 +2875,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters such as phoneNumber', () => { // Expected error when an invalid phone number is provided. - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER); const requestHandler = handler.init(mockApp); // Create new account with invalid phone number. return requestHandler.createNewAccount(invalidPhoneNumberData) @@ -2889,7 +2889,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected when the backend returns a user exists error', () => { // Expected error when the uid already exists. - const expectedError = new FirebaseAuthError(AuthClientErrorCode.UID_ALREADY_EXISTS); + const expectedError = new FirebaseAuthError(authClientErrorCode.UID_ALREADY_EXISTS); const expectedResult = utils.errorFrom({ error: { message: 'DUPLICATE_LOCAL_ID', @@ -2913,7 +2913,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected when the backend returns an email exists error', () => { // Expected error when the email already exists. - const expectedError = new FirebaseAuthError(AuthClientErrorCode.EMAIL_ALREADY_EXISTS); + const expectedError = new FirebaseAuthError(authClientErrorCode.EMAIL_ALREADY_EXISTS); const expectedResult = utils.errorFrom({ error: { message: 'EMAIL_EXISTS', @@ -3014,7 +3014,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters such as email', () => { // Expected error when an invalid email is provided. const expectedError = - new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL); + new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL); const requestHandler = handler.init(mockApp); // Send create new account request with invalid data. return requestHandler.createNewAccount(invalidData) @@ -3028,7 +3028,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters such as phone number', () => { // Expected error when an invalid phone number is provided. const expectedError = - new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER); + new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER); const requestHandler = handler.init(mockApp); // Send create new account request with invalid data. return requestHandler.createNewAccount(invalidPhoneNumberData) @@ -3182,7 +3182,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given requestType: VERIFY_AND_CHANGE and no new Email address', () => { const requestHandler = handler.init(mockApp); const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '`newEmail` is required when `requestType` === \'VERIFY_AND_CHANGE_EMAIL\'', ) @@ -3197,7 +3197,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given an invalid email', () => { const invalidEmail = 'invalid'; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL); const requestHandler = handler.init(mockApp); return requestHandler.getEmailActionLink('PASSWORD_RESET', invalidEmail, actionCodeSettings) @@ -3211,7 +3211,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given an invalid new email', () => { const invalidNewEmail = 'invalid'; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_NEW_EMAIL); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_NEW_EMAIL); const requestHandler = handler.init(mockApp); return requestHandler.getEmailActionLink('VERIFY_AND_CHANGE_EMAIL', email, actionCodeSettings, invalidNewEmail) @@ -3226,7 +3226,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given an invalid request type', () => { const invalidRequestType = 'invalid'; const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"invalid" is not a supported email action request type.', ); @@ -3243,7 +3243,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given an invalid ActionCodeSettings object', () => { const invalidActionCodeSettings = 'invalid' as any; const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings" must be a non-null object.', ); @@ -3259,7 +3259,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected when the response does not contain a link', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create the email action link'); const requestData = deepExtend({ requestType: 'VERIFY_EMAIL', @@ -3333,7 +3333,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { null, NaN, 0, 1, true, false, '', 'saml.provider', ['oidc.provider'], [], {}, { a: 1 }, _.noop]; invalidProviderIds.forEach((invalidProviderId) => { it('should be rejected given an invalid provider ID:' + JSON.stringify(invalidProviderId), () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID); const requestHandler = handler.init(mockApp); return requestHandler.getOAuthIdpConfig(invalidProviderId as any) @@ -3346,7 +3346,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given a backend error', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.CONFIGURATION_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.CONFIGURATION_NOT_FOUND); const expectedServerError = utils.errorFrom({ error: { message: 'CONFIGURATION_NOT_FOUND', @@ -3433,7 +3433,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given an invalid maxResults', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive integer that does not ' + 'exceed 100.', ); @@ -3449,7 +3449,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given an invalid next page token', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_PAGE_TOKEN, + authClientErrorCode.INVALID_PAGE_TOKEN, ); const requestHandler = handler.init(mockApp); @@ -3510,7 +3510,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { null, NaN, 0, 1, true, false, '', 'saml.provider', ['oidc.provider'], [], {}, { a: 1 }, _.noop]; invalidProviderIds.forEach((invalidProviderId) => { it('should be rejected given an invalid provider ID:' + JSON.stringify(invalidProviderId), () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID); const requestHandler = handler.init(mockApp); return requestHandler.deleteOAuthIdpConfig(invalidProviderId as any) @@ -3523,7 +3523,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given a backend error', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.CONFIGURATION_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.CONFIGURATION_NOT_FOUND); const expectedServerError = utils.errorFrom({ error: { message: 'CONFIGURATION_NOT_FOUND', @@ -3615,7 +3615,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"OIDCAuthProviderConfig.issuer" must be a valid URL string.', ); const invalidOptions: OIDCAuthProviderConfig = deepCopy(configOptions); @@ -3632,7 +3632,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected when the backend returns a response missing name', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create new OIDC configuration', ); const stub = sinon.stub(HttpClient.prototype, 'send').resolves(utils.responseFrom({})); @@ -3655,7 +3655,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { message: 'INVALID_CONFIG', }, }); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CONFIG); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CONFIG); const stub = sinon.stub(HttpClient.prototype, 'send').rejects(expectedServerError); stubs.push(stub); @@ -3800,7 +3800,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { null, NaN, 0, 1, true, false, '', 'saml.provider', ['oidc.provider'], [], {}, { a: 1 }, _.noop]; invalidProviderIds.forEach((invalidProviderId) => { it('should be rejected given an invalid provider ID:' + JSON.stringify(invalidProviderId), () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID); const requestHandler = handler.init(mockApp); return requestHandler.updateOAuthIdpConfig(invalidProviderId as any, configOptions) @@ -3814,7 +3814,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"OIDCAuthProviderConfig.issuer" must be a valid URL string.', ); const invalidOptions: OIDCUpdateAuthProviderRequest = deepCopy(configOptions); @@ -3832,7 +3832,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected when the backend returns a response missing name', () => { const expectedPath = path + '?updateMask=enabled,displayName,issuer,clientId'; const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to update OIDC configuration', ); const stub = sinon.stub(HttpClient.prototype, 'send').resolves(utils.responseFrom({})); @@ -3856,7 +3856,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { message: 'INVALID_CONFIG', }, }); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CONFIG); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CONFIG); const stub = sinon.stub(HttpClient.prototype, 'send').rejects(expectedServerError); stubs.push(stub); @@ -3897,7 +3897,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { null, NaN, 0, 1, true, false, '', 'oidc.provider', ['saml.provider'], [], {}, { a: 1 }, _.noop]; invalidProviderIds.forEach((invalidProviderId) => { it('should be rejected given an invalid provider ID:' + JSON.stringify(invalidProviderId), () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID); const requestHandler = handler.init(mockApp); return requestHandler.getInboundSamlConfig(invalidProviderId as any) @@ -3910,7 +3910,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given a backend error', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.CONFIGURATION_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.CONFIGURATION_NOT_FOUND); const expectedServerError = utils.errorFrom({ error: { message: 'CONFIGURATION_NOT_FOUND', @@ -3993,7 +3993,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given an invalid maxResults', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive integer that does not ' + 'exceed 100.', ); @@ -4009,7 +4009,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given an invalid next page token', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_PAGE_TOKEN, + authClientErrorCode.INVALID_PAGE_TOKEN, ); const requestHandler = handler.init(mockApp); @@ -4068,7 +4068,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { null, NaN, 0, 1, true, false, '', 'oidc.provider', ['saml.provider'], [], {}, { a: 1 }, _.noop]; invalidProviderIds.forEach((invalidProviderId) => { it('should be rejected given an invalid provider ID:' + JSON.stringify(invalidProviderId), () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID); const requestHandler = handler.init(mockApp); return requestHandler.deleteInboundSamlConfig(invalidProviderId as any) @@ -4081,7 +4081,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given a backend error', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.CONFIGURATION_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.CONFIGURATION_NOT_FOUND); const expectedServerError = utils.errorFrom({ error: { message: 'CONFIGURATION_NOT_FOUND', @@ -4152,7 +4152,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.callbackURL" must be a valid URL string.', ); const invalidOptions: SAMLAuthProviderConfig = deepCopy(configOptions); @@ -4169,7 +4169,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected when the backend returns a response missing name', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create new SAML configuration', ); const stub = sinon.stub(HttpClient.prototype, 'send').resolves(utils.responseFrom({})); @@ -4192,7 +4192,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { message: 'INVALID_CONFIG', }, }); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CONFIG); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CONFIG); const stub = sinon.stub(HttpClient.prototype, 'send').rejects(expectedServerError); stubs.push(stub); @@ -4344,7 +4344,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { null, NaN, 0, 1, true, false, '', 'oidc.provider', ['saml.provider'], [], {}, { a: 1 }, _.noop]; invalidProviderIds.forEach((invalidProviderId) => { it('should be rejected given an invalid provider ID:' + JSON.stringify(invalidProviderId), () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_PROVIDER_ID); const requestHandler = handler.init(mockApp); return requestHandler.updateInboundSamlConfig(invalidProviderId as any, configOptions) @@ -4358,7 +4358,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, + authClientErrorCode.INVALID_CONFIG, '"SAMLAuthProviderConfig.ssoURL" must be a valid URL string.', ); const invalidOptions: SAMLUpdateAuthProviderRequest = deepCopy(configOptions); @@ -4376,7 +4376,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected when the backend returns a response missing name', () => { const expectedPath = path + `?updateMask=${fullUpadateMask}`; const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to update SAML configuration', ); const stub = sinon.stub(HttpClient.prototype, 'send').resolves(utils.responseFrom({})); @@ -4400,7 +4400,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { message: 'INVALID_CONFIG', }, }); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CONFIG); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CONFIG); const stub = sinon.stub(HttpClient.prototype, 'send').rejects(expectedServerError); stubs.push(stub); @@ -4440,7 +4440,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const invalidTenantIds = [null, NaN, 0, 1, true, false, '', ['tenant-id'], [], {}, { a: 1 }, _.noop]; invalidTenantIds.forEach((invalidTenantId) => { it('should be rejected given an invalid tenant ID:' + JSON.stringify(invalidTenantId), () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_TENANT_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_TENANT_ID); const requestHandler = handler.init(mockApp) as AuthRequestHandler; return requestHandler.getTenant(invalidTenantId as any) @@ -4453,7 +4453,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given a backend error', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.TENANT_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.TENANT_NOT_FOUND); const expectedServerError = utils.errorFrom({ error: { message: 'TENANT_NOT_FOUND', @@ -4536,7 +4536,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given an invalid maxResults', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, 'Required "maxResults" must be a positive non-zero number that does not ' + 'exceed the allowed 1000.', ); @@ -4552,7 +4552,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given an invalid next page token', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_PAGE_TOKEN, + authClientErrorCode.INVALID_PAGE_TOKEN, ); const requestHandler = handler.init(mockApp) as AuthRequestHandler; @@ -4610,7 +4610,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const invalidTenantIds = [null, NaN, 0, 1, true, false, '', ['tenant-id'], [], {}, { a: 1 }, _.noop]; invalidTenantIds.forEach((invalidTenantId) => { it('should be rejected given an invalid tenant ID:' + JSON.stringify(invalidTenantId), () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_TENANT_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_TENANT_ID); const requestHandler = handler.init(mockApp) as AuthRequestHandler; return requestHandler.deleteTenant(invalidTenantId as any) @@ -4623,7 +4623,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); it('should be rejected given a backend error', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.TENANT_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.TENANT_NOT_FOUND); const expectedServerError = utils.errorFrom({ error: { message: 'TENANT_NOT_FOUND', @@ -4692,7 +4692,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"EmailSignInConfig" must be a non-null object.', ); const invalidOptions = deepCopy(tenantOptions); @@ -4709,7 +4709,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected when the backend returns a response missing name', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create new tenant', ); const stub = sinon.stub(HttpClient.prototype, 'send').resolves(utils.responseFrom({})); @@ -4727,7 +4727,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected when the backend returns a response missing tenant ID in response name', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to create new tenant', ); // Resource name should have /tenants/tenant-id in path. This should throw an error. @@ -4752,7 +4752,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }, }); const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'An internal error has occurred. Raw server response: ' + `"${JSON.stringify(expectedServerError.response.data)}"`, ); @@ -4864,7 +4864,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const invalidTenantIds = [null, NaN, 0, 1, true, false, '', ['tenant-id'], [], {}, { a: 1 }, _.noop]; invalidTenantIds.forEach((invalidTenantId) => { it('should be rejected given an invalid tenant ID:' + JSON.stringify(invalidTenantId), () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_TENANT_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_TENANT_ID); const requestHandler = handler.init(mockApp) as AuthRequestHandler; return requestHandler.updateTenant(invalidTenantId as any, tenantOptions) @@ -4878,7 +4878,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given invalid parameters', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"EmailSignInConfig" must be a non-null object.', ); const invalidOptions = deepCopy(tenantOptions); @@ -4897,7 +4897,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const expectedPath = path + '?updateMask=allowPasswordSignup,enableEmailLinkSignin,displayName,' + 'mfaConfig.state,mfaConfig.enabledProviders,testPhoneNumbers'; const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to update tenant', ); const stub = sinon.stub(HttpClient.prototype, 'send').resolves(utils.responseFrom({})); @@ -4918,7 +4918,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const expectedPath = path + '?updateMask=allowPasswordSignup,enableEmailLinkSignin,displayName,' + 'mfaConfig.state,mfaConfig.enabledProviders,testPhoneNumbers'; const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Unable to update tenant', ); // Resource name should have /tenants/tenant-id in path. This should throw an error. @@ -4946,7 +4946,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }, }); const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'An internal error has occurred. Raw server response: ' + `"${JSON.stringify(expectedServerError.response.data)}"`, ); diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index 2fd3a3e0b4..f1c8202db9 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -31,7 +31,7 @@ import { FirebaseApp } from '../../../src/app/firebase-app'; import { AuthRequestHandler, TenantAwareAuthRequestHandler, AbstractAuthRequestHandler, } from '../../../src/auth/auth-api-request'; -import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error'; +import { authClientErrorCode, FirebaseAuthError } from '../../../src/auth/error'; import * as validator from '../../../src/utils/validator'; import { DecodedAuthBlockingToken, FirebaseTokenVerifier } from '../../../src/auth/token-verifier'; @@ -497,7 +497,7 @@ AUTH_CONFIGS.forEach((testConfig) => { it('should reject when underlying idTokenVerifier.verifyJWT() rejects with expected error', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, 'Decoding Firebase ID token failed'); + authClientErrorCode.INVALID_ARGUMENT, 'Decoding Firebase ID token failed'); // Restore verifyIdToken stub. stub.restore(); // Simulate ID token is invalid. @@ -650,7 +650,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); it('should be rejected with checkRevoked set to true if underlying RPC fails', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); const getUserStub = sinon.stub(testConfig.Auth.prototype, 'getUser') .rejects(expectedError); stubs.push(getUserStub); @@ -689,7 +689,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); it('should be rejected with checkRevoked set to true using an invalid ID token', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CREDENTIAL); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CREDENTIAL); // Restore verifyIdToken stub. stub.restore(); // Simulate ID token is invalid. @@ -709,7 +709,7 @@ AUTH_CONFIGS.forEach((testConfig) => { if (testConfig.Auth === TenantAwareAuth) { it('should be rejected with ID token missing tenant ID', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.MISMATCHING_TENANT_ID); // Restore verifyIdToken stub. stub.restore(); // Simulate JWT does not contain tenant ID. @@ -726,7 +726,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); it('should be rejected with ID token containing mismatching tenant ID', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.MISMATCHING_TENANT_ID); // Restore verifyIdToken stub. stub.restore(); // Simulate JWT does not contain matching tenant ID. @@ -788,7 +788,7 @@ AUTH_CONFIGS.forEach((testConfig) => { it('should reject when underlying sessionCookieVerifier.verifyJWT() rejects with expected error', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, 'Decoding Firebase session cookie failed'); + authClientErrorCode.INVALID_ARGUMENT, 'Decoding Firebase session cookie failed'); // Restore verifySessionCookie stub. stub.restore(); // Simulate session cookie is invalid. @@ -894,7 +894,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); it('should be rejected with checkRevoked set to true if underlying RPC fails', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); const getUserStub = sinon.stub(testConfig.Auth.prototype, 'getUser') .rejects(expectedError); stubs.push(getUserStub); @@ -980,7 +980,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); it('should be rejected with checkRevoked set to true using an invalid session cookie', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CREDENTIAL); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CREDENTIAL); // Restore verifySessionCookie stub. stub.restore(); // Simulate session cookie is invalid. @@ -1000,7 +1000,7 @@ AUTH_CONFIGS.forEach((testConfig) => { if (testConfig.Auth === TenantAwareAuth) { it('should be rejected with session cookie missing tenant ID', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.MISMATCHING_TENANT_ID); // Restore verifyIdToken stub. stub.restore(); // Simulate JWT does not contain tenant ID.. @@ -1017,7 +1017,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); it('should be rejected with ID token containing mismatching tenant ID', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); + const expectedError = new FirebaseAuthError(authClientErrorCode.MISMATCHING_TENANT_ID); // Restore verifyIdToken stub. stub.restore(); // Simulate JWT does not contain matching tenant ID.. @@ -1080,7 +1080,7 @@ AUTH_CONFIGS.forEach((testConfig) => { it('should reject when underlying idTokenVerifier._verifyAuthBlockingToken() rejects', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, 'Decoding Firebase Auth Blocking token failed'); + authClientErrorCode.INVALID_ARGUMENT, 'Decoding Firebase Auth Blocking token failed'); // Restore _verifyAuthBlockingToken stub. stub.restore(); // Simulate Auth Blocking token is invalid. @@ -1135,7 +1135,7 @@ AUTH_CONFIGS.forEach((testConfig) => { const tenantId = testConfig.supportsTenantManagement ? undefined : TENANT_ID; const expectedGetAccountInfoResult = getValidGetAccountInfoResponse(tenantId); const expectedUserRecord = getValidUserRecord(expectedGetAccountInfoResult); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); // Stubs used to simulate underlying api calls. let stubs: sinon.SinonStub[] = []; @@ -1214,7 +1214,7 @@ AUTH_CONFIGS.forEach((testConfig) => { const tenantId = testConfig.supportsTenantManagement ? undefined : TENANT_ID; const expectedGetAccountInfoResult = getValidGetAccountInfoResponse(tenantId); const expectedUserRecord = getValidUserRecord(expectedGetAccountInfoResult); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); // Stubs used to simulate underlying api calls. let stubs: sinon.SinonStub[] = []; @@ -1293,7 +1293,7 @@ AUTH_CONFIGS.forEach((testConfig) => { const tenantId = testConfig.supportsTenantManagement ? undefined : TENANT_ID; const expectedGetAccountInfoResult = getValidGetAccountInfoResponse(tenantId); const expectedUserRecord = getValidUserRecord(expectedGetAccountInfoResult); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); // Stubs used to simulate underlying api calls. let stubs: sinon.SinonStub[] = []; @@ -1374,7 +1374,7 @@ AUTH_CONFIGS.forEach((testConfig) => { const tenantId = testConfig.supportsTenantManagement ? undefined : TENANT_ID; const expectedGetAccountInfoResult = getValidGetAccountInfoResponse(tenantId); const expectedUserRecord = getValidUserRecord(expectedGetAccountInfoResult); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); // Stubs used to simulate underlying api calls. let stubs: sinon.SinonStub[] = []; @@ -1562,7 +1562,7 @@ AUTH_CONFIGS.forEach((testConfig) => { describe('deleteUser()', () => { const uid = 'abcdefghijklmnopqrstuvwxyz'; const expectedDeleteAccountResult = { kind: 'identitytoolkit#DeleteAccountResponse' }; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); // Stubs used to simulate underlying api calls. let stubs: sinon.SinonStub[] = []; @@ -1700,10 +1700,10 @@ AUTH_CONFIGS.forEach((testConfig) => { const expectedGetAccountInfoResult = getValidGetAccountInfoResponse(tenantId); const expectedUserRecord = getValidUserRecord(expectedGetAccountInfoResult); const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'Unable to create the user record provided.'); const unableToCreateUserError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'Unable to create the user record provided.'); const propertiesToCreate = { displayName: expectedUserRecord.displayName, @@ -1793,7 +1793,7 @@ AUTH_CONFIGS.forEach((testConfig) => { const createUserStub = sinon.stub(testConfig.RequestHandler.prototype, 'createNewAccount') .resolves(uid); // Stub getAccountInfoByUid to throw user not found error. - const userNotFoundError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const userNotFoundError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); const getUserStub = sinon.stub(testConfig.RequestHandler.prototype, 'getAccountInfoByUid') .rejects(userNotFoundError); stubs.push(createUserStub); @@ -1837,7 +1837,7 @@ AUTH_CONFIGS.forEach((testConfig) => { const tenantId = testConfig.supportsTenantManagement ? undefined : TENANT_ID; const expectedGetAccountInfoResult = getValidGetAccountInfoResponse(tenantId); const expectedUserRecord = getValidUserRecord(expectedGetAccountInfoResult); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); const propertiesToEdit = { displayName: expectedUserRecord.displayName, photoURL: expectedUserRecord.photoURL, @@ -2258,7 +2258,7 @@ AUTH_CONFIGS.forEach((testConfig) => { describe('setCustomUserClaims()', () => { const uid = 'abcdefghijklmnopqrstuvwxyz'; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); const customClaims = { admin: true, groupId: '123456', @@ -2359,7 +2359,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); describe('listUsers()', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR); + const expectedError = new FirebaseAuthError(authClientErrorCode.INTERNAL_ERROR); const pageToken = 'PAGE_TOKEN'; const maxResult = 500; const downloadAccountResponse: any = { @@ -2502,7 +2502,7 @@ AUTH_CONFIGS.forEach((testConfig) => { describe('revokeRefreshTokens()', () => { const uid = 'abcdefghijklmnopqrstuvwxyz'; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); // Stubs used to simulate underlying api calls. let stubs: sinon.SinonStub[] = []; beforeEach(() => { @@ -2590,11 +2590,11 @@ AUTH_CONFIGS.forEach((testConfig) => { }, }; const expectedUserImportResultError = - new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER); + new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER); const expectedOptionsError = - new FirebaseAuthError(AuthClientErrorCode.INVALID_HASH_ALGORITHM); + new FirebaseAuthError(authClientErrorCode.INVALID_HASH_ALGORITHM); const expectedServerError = - new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR); + new FirebaseAuthError(authClientErrorCode.INTERNAL_ERROR); const expectedUserImportResult = { successCount: 1, failureCount: 1, @@ -2706,7 +2706,7 @@ AUTH_CONFIGS.forEach((testConfig) => { const idToken = 'ID_TOKEN'; const options = { expiresIn: 60 * 60 * 24 * 1000 }; const sessionCookie = 'SESSION_COOKIE'; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_ID_TOKEN); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_ID_TOKEN); const expectedUserRecord = getValidUserRecord(getValidGetAccountInfoResponse(tenantId)); // Set auth_time of token to expected user's tokensValidAfterTime. if (!expectedUserRecord.tokensValidAfterTime) { @@ -2892,7 +2892,7 @@ AUTH_CONFIGS.forEach((testConfig) => { const expectedLink = 'https://custom.page.link?link=' + encodeURIComponent('https://projectId.firebaseapp.com/__/auth/action?oobCode=CODE') + '&apn=com.example.android&ibi=com.example.ios'; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.USER_NOT_FOUND); // Stubs used to simulate underlying api calls. let stubs: sinon.SinonStub[] = []; afterEach(() => { @@ -3090,7 +3090,7 @@ AUTH_CONFIGS.forEach((testConfig) => { issuer: 'https://oidc.com/issuer', }; const expectedConfig = new OIDCConfig(serverResponse); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.CONFIGURATION_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.CONFIGURATION_NOT_FOUND); it('should resolve with an OIDCConfig on success', () => { // Stub getOAuthIdpConfig to return expected result. @@ -3144,7 +3144,7 @@ AUTH_CONFIGS.forEach((testConfig) => { enabled: true, }; const expectedConfig = new SAMLConfig(serverResponse); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.CONFIGURATION_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.CONFIGURATION_NOT_FOUND); it('should resolve with a SAMLConfig on success', () => { // Stub getInboundSamlConfig to return expected result. @@ -3218,7 +3218,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); describe('using OIDC type filter', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR); + const expectedError = new FirebaseAuthError(authClientErrorCode.INTERNAL_ERROR); const pageToken = 'PAGE_TOKEN'; const maxResults = 50; const filterOptions: AuthProviderConfigFilter = { @@ -3313,7 +3313,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); describe('using SAML type filter', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR); + const expectedError = new FirebaseAuthError(authClientErrorCode.INTERNAL_ERROR); const pageToken = 'PAGE_TOKEN'; const maxResults = 50; const filterOptions: AuthProviderConfigFilter = { @@ -3453,7 +3453,7 @@ AUTH_CONFIGS.forEach((testConfig) => { describe('using OIDC configurations', () => { const providerId = 'oidc.provider'; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.CONFIGURATION_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.CONFIGURATION_NOT_FOUND); it('should resolve with void on success', () => { // Stub deleteOAuthIdpConfig to resolve. @@ -3488,7 +3488,7 @@ AUTH_CONFIGS.forEach((testConfig) => { describe('using SAML configurations', () => { const providerId = 'saml.provider'; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.CONFIGURATION_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.CONFIGURATION_NOT_FOUND); it('should resolve with void on success', () => { // Stub deleteInboundSamlConfig to resolve. @@ -3598,7 +3598,7 @@ AUTH_CONFIGS.forEach((testConfig) => { issuer: 'https://oidc.com/issuer', }; const expectedConfig = new OIDCConfig(serverResponse); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CONFIG); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CONFIG); it('should resolve with an OIDCConfig on updateOAuthIdpConfig request success', () => { // Stub updateOAuthIdpConfig to return expected server response. @@ -3664,7 +3664,7 @@ AUTH_CONFIGS.forEach((testConfig) => { enabled: true, }; const expectedConfig = new SAMLConfig(serverResponse); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CONFIG); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CONFIG); it('should resolve with a SAMLConfig on updateInboundSamlConfig request success', () => { // Stub updateInboundSamlConfig to return expected server response. @@ -3764,7 +3764,7 @@ AUTH_CONFIGS.forEach((testConfig) => { issuer: 'https://oidc.com/issuer', }; const expectedConfig = new OIDCConfig(serverResponse); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CONFIG); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CONFIG); it('should resolve with an OIDCConfig on createOAuthIdpConfig request success', () => { // Stub createOAuthIdpConfig to return expected server response. @@ -3831,7 +3831,7 @@ AUTH_CONFIGS.forEach((testConfig) => { enabled: true, }; const expectedConfig = new SAMLConfig(serverResponse); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CONFIG); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CONFIG); it('should resolve with a SAMLConfig on createInboundSamlConfig request success', () => { // Stub createInboundSamlConfig to return expected server response. diff --git a/test/unit/auth/project-config-manager.spec.ts b/test/unit/auth/project-config-manager.spec.ts index be7df54d99..8d56c34c0d 100644 --- a/test/unit/auth/project-config-manager.spec.ts +++ b/test/unit/auth/project-config-manager.spec.ts @@ -25,7 +25,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { AuthRequestHandler } from '../../../src/auth/auth-api-request'; -import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error'; +import { authClientErrorCode, FirebaseAuthError } from '../../../src/auth/error'; import { ProjectConfigManager } from '../../../src/auth/project-config-manager'; import { ProjectConfig, @@ -81,7 +81,7 @@ describe('ProjectConfigManager', () => { describe('getProjectConfig()', () => { const expectedProjectConfig = new ProjectConfig(GET_CONFIG_RESPONSE); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_CONFIG); + const expectedError = new FirebaseAuthError(authClientErrorCode.INVALID_CONFIG); // Stubs used to simulate underlying API calls. let stubs: sinon.SinonStub[] = []; afterEach(() => { @@ -152,7 +152,7 @@ describe('ProjectConfigManager', () => { }; const expectedProjectConfig = new ProjectConfig(GET_CONFIG_RESPONSE); const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, + authClientErrorCode.INTERNAL_ERROR, 'Unable to update the config provided.'); // Stubs used to simulate underlying API calls. let stubs: sinon.SinonStub[] = []; diff --git a/test/unit/auth/tenant-manager.spec.ts b/test/unit/auth/tenant-manager.spec.ts index 30bf3082dd..dd041419b7 100644 --- a/test/unit/auth/tenant-manager.spec.ts +++ b/test/unit/auth/tenant-manager.spec.ts @@ -26,7 +26,7 @@ import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { AuthRequestHandler } from '../../../src/auth/auth-api-request'; import { TenantServerResponse } from '../../../src/auth/tenant'; -import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error'; +import { authClientErrorCode, FirebaseAuthError } from '../../../src/auth/error'; import { CreateTenantRequest, UpdateTenantRequest, ListTenantsResult, Tenant, TenantManager, } from '../../../src/auth/index'; @@ -102,7 +102,7 @@ describe('TenantManager', () => { describe('getTenant()', () => { const tenantId = 'tenant-id'; const expectedTenant = new Tenant(GET_TENANT_RESPONSE); - const expectedError = new FirebaseAuthError(AuthClientErrorCode.TENANT_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.TENANT_NOT_FOUND); // Stubs used to simulate underlying API calls. let stubs: sinon.SinonStub[] = []; afterEach(() => { @@ -173,7 +173,7 @@ describe('TenantManager', () => { }); describe('listTenants()', () => { - const expectedError = new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR); + const expectedError = new FirebaseAuthError(authClientErrorCode.INTERNAL_ERROR); const pageToken = 'PAGE_TOKEN'; const maxResult = 500; const listTenantsResponse: any = { @@ -307,7 +307,7 @@ describe('TenantManager', () => { describe('deleteTenant()', () => { const tenantId = 'tenant-id'; - const expectedError = new FirebaseAuthError(AuthClientErrorCode.TENANT_NOT_FOUND); + const expectedError = new FirebaseAuthError(authClientErrorCode.TENANT_NOT_FOUND); // Stubs used to simulate underlying API calls. let stubs: sinon.SinonStub[] = []; afterEach(() => { @@ -390,7 +390,7 @@ describe('TenantManager', () => { }; const expectedTenant = new Tenant(GET_TENANT_RESPONSE); const expectedError = new FirebaseAuthError({ - code: AuthClientErrorCode.INTERNAL_ERROR.code, + code: authClientErrorCode.INTERNAL_ERROR.code, message: 'Unable to create the tenant provided.' }); // Stubs used to simulate underlying API calls. @@ -484,7 +484,7 @@ describe('TenantManager', () => { }; const expectedTenant = new Tenant(GET_TENANT_RESPONSE); const expectedError = new FirebaseAuthError({ - code: AuthClientErrorCode.INTERNAL_ERROR.code, + code: authClientErrorCode.INTERNAL_ERROR.code, message: 'Unable to update the tenant provided.' }); // Stubs used to simulate underlying API calls. diff --git a/test/unit/auth/token-generator.spec.ts b/test/unit/auth/token-generator.spec.ts index c9dce78088..3b2ac86c9a 100644 --- a/test/unit/auth/token-generator.spec.ts +++ b/test/unit/auth/token-generator.spec.ts @@ -31,7 +31,7 @@ import { import { CryptoSignerError, CryptoSignerErrorCode, ServiceAccountSigner } from '../../../src/utils/crypto-signer'; import { ServiceAccountCredential } from '../../../src/app/credential-internal'; -import { FirebaseAuthError } from '../../../src/utils/error'; +import { FirebaseAuthError } from '../../../src/auth/error'; import * as utils from '../utils'; chai.should(); diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 3dae6817cd..d90c055a2a 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -32,7 +32,7 @@ import { ServiceAccountSigner } from '../../../src/utils/crypto-signer'; import * as verifier from '../../../src/auth/token-verifier'; import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { AuthClientErrorCode } from '../../../src/utils/error'; +import { authClientErrorCode } from '../../../src/auth/error'; import { JwtError, JwtErrorCode, PublicKeySignatureVerifier } from '../../../src/utils/jwt'; chai.should(); @@ -104,7 +104,7 @@ describe('FirebaseTokenVerifier', () => { verifyApiName: 'verifyToken()', jwtName: 'Important Token', shortName: 'token', - expiredErrorCode: AuthClientErrorCode.INVALID_ARGUMENT, + expiredErrorCode: authClientErrorCode.INVALID_ARGUMENT, }, app, ); @@ -151,7 +151,7 @@ describe('FirebaseTokenVerifier', () => { verifyApiName: invalidVerifyApiName as any, jwtName: 'Important Token', shortName: 'token', - expiredErrorCode: AuthClientErrorCode.INVALID_ARGUMENT, + expiredErrorCode: authClientErrorCode.INVALID_ARGUMENT, }, app, ); @@ -171,7 +171,7 @@ describe('FirebaseTokenVerifier', () => { verifyApiName: 'verifyToken()', jwtName: invalidJwtName as any, shortName: 'token', - expiredErrorCode: AuthClientErrorCode.INVALID_ARGUMENT, + expiredErrorCode: authClientErrorCode.INVALID_ARGUMENT, }, app, ); @@ -191,7 +191,7 @@ describe('FirebaseTokenVerifier', () => { verifyApiName: 'verifyToken()', jwtName: 'Important Token', shortName: invalidShortName as any, - expiredErrorCode: AuthClientErrorCode.INVALID_ARGUMENT, + expiredErrorCode: authClientErrorCode.INVALID_ARGUMENT, }, app, ); diff --git a/test/unit/auth/user-import-builder.spec.ts b/test/unit/auth/user-import-builder.spec.ts index 6be38ebe28..d6988b7c9a 100644 --- a/test/unit/auth/user-import-builder.spec.ts +++ b/test/unit/auth/user-import-builder.spec.ts @@ -22,7 +22,7 @@ import { deepCopy } from '../../../src/utils/deep-copy'; import { UserImportBuilder, ValidatorFunction, UploadAccountRequest, } from '../../../src/auth/user-import-builder'; -import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error'; +import { authClientErrorCode, FirebaseAuthError } from '../../../src/auth/error'; import { toWebSafeBase64 } from '../../../src/utils'; import { UpdatePhoneMultiFactorInfoRequest, UserImportResult, UserImportRecord, @@ -55,7 +55,7 @@ describe('UserImportBuilder', () => { // Simulate a validation error is thrown for a specific user. if (request.localId === '5678') { throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_PHONE_NUMBER, + authClientErrorCode.INVALID_PHONE_NUMBER, ); } }; @@ -176,7 +176,7 @@ describe('UserImportBuilder', () => { invalidUserImportOptions.forEach((invalidOption) => { it(`should throw when non-object ${JSON.stringify(invalidOption)} UserImportOptions is provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, + authClientErrorCode.INVALID_ARGUMENT, '"UserImportOptions" are required when importing users with passwords.', ); expect(() => { @@ -187,7 +187,7 @@ describe('UserImportBuilder', () => { it('should throw when an empty hash algorithm is provided', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.MISSING_HASH_ALGORITHM, + authClientErrorCode.MISSING_HASH_ALGORITHM, '"hash.algorithm" is missing from the provided "UserImportOptions".', ); expect(() => { @@ -197,7 +197,7 @@ describe('UserImportBuilder', () => { it('should throw when an invalid hash algorithm is provided', () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_ALGORITHM, + authClientErrorCode.INVALID_HASH_ALGORITHM, 'Unsupported hash algorithm provider "invalid".', ); const invalidOptions = { @@ -226,7 +226,7 @@ describe('UserImportBuilder', () => { invalidKeys.forEach((key) => { it(`should throw when non-Buffer ${JSON.stringify(key)} hash key is provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_KEY, + authClientErrorCode.INVALID_HASH_KEY, 'A non-empty "hash.key" byte buffer must be provided for ' + `hash algorithm ${algorithm}.`, ); @@ -289,7 +289,7 @@ describe('UserImportBuilder', () => { invalidRounds.forEach((rounds) => { it(`should throw when ${JSON.stringify(rounds)} rounds provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_ROUNDS, + authClientErrorCode.INVALID_HASH_ROUNDS, `A valid "hash.rounds" number between ${minRounds} and ${maxRounds} must be provided for ` + `hash algorithm ${algorithm}.`, ); @@ -334,7 +334,7 @@ describe('UserImportBuilder', () => { invalidKeys.forEach((key) => { it(`should throw when ${JSON.stringify(key)} key provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_KEY, + authClientErrorCode.INVALID_HASH_KEY, 'A "hash.key" byte buffer must be provided for ' + `hash algorithm ${algorithm}.`, ); @@ -355,7 +355,7 @@ describe('UserImportBuilder', () => { invalidRounds.forEach((rounds) => { it(`should throw when ${JSON.stringify(rounds)} rounds provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_ROUNDS, + authClientErrorCode.INVALID_HASH_ROUNDS, 'A valid "hash.rounds" number between 1 and 8 must be provided for ' + `hash algorithm ${algorithm}.`, ); @@ -376,7 +376,7 @@ describe('UserImportBuilder', () => { invalidMemoryCost.forEach((memoryCost) => { it(`should throw when ${JSON.stringify(memoryCost)} memoryCost provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_MEMORY_COST, + authClientErrorCode.INVALID_HASH_MEMORY_COST, 'A valid "hash.memoryCost" number between 1 and 14 must be provided for ' + `hash algorithm ${algorithm}.`, ); @@ -397,7 +397,7 @@ describe('UserImportBuilder', () => { invalidSaltSeparator.forEach((saltSeparator) => { it(`should throw when ${JSON.stringify(saltSeparator)} saltSeparator provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_SALT_SEPARATOR, + authClientErrorCode.INVALID_HASH_SALT_SEPARATOR, '"hash.saltSeparator" must be a byte buffer.', ); const invalidOptions = { @@ -465,7 +465,7 @@ describe('UserImportBuilder', () => { invalidMemoryCost.forEach((memoryCost) => { it(`should throw when ${JSON.stringify(memoryCost)} memoryCost provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_MEMORY_COST, + authClientErrorCode.INVALID_HASH_MEMORY_COST, 'A valid "hash.memoryCost" number must be provided for ' + `hash algorithm ${algorithm}.`, ); @@ -487,7 +487,7 @@ describe('UserImportBuilder', () => { invalidParallelization.forEach((parallelization) => { it(`should throw when ${JSON.stringify(parallelization)} parallelization provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_MEMORY_COST, + authClientErrorCode.INVALID_HASH_MEMORY_COST, 'A valid "hash.parallelization" number must be provided for ' + `hash algorithm ${algorithm}.`, ); @@ -509,7 +509,7 @@ describe('UserImportBuilder', () => { invalidBlockSize.forEach((blockSize) => { it(`should throw when ${JSON.stringify(blockSize)} blockSize provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_BLOCK_SIZE, + authClientErrorCode.INVALID_HASH_BLOCK_SIZE, 'A valid "hash.blockSize" number must be provided for ' + `hash algorithm ${algorithm}.`, ); @@ -531,7 +531,7 @@ describe('UserImportBuilder', () => { invalidDerivedKeyLength.forEach((derivedKeyLength) => { it(`should throw when ${JSON.stringify(derivedKeyLength)} dkLen provided`, () => { const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_HASH_DERIVED_KEY_LENGTH, + authClientErrorCode.INVALID_HASH_DERIVED_KEY_LENGTH, 'A valid "hash.derivedKeyLength" number must be provided for ' + `hash algorithm ${algorithm}.`, ); @@ -741,7 +741,7 @@ describe('UserImportBuilder', () => { // Index should match server error index. index: 1, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_USER_IMPORT, + authClientErrorCode.INVALID_USER_IMPORT, 'Some error occurred!', ), }, @@ -760,7 +760,7 @@ describe('UserImportBuilder', () => { successCount: 3, failureCount: 1, errors: [ - { index: 2, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER) }, + { index: 2, error: new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER) }, ], }; // userRequestValidatorWithError will throw on the 3rd user (index = 2). @@ -780,9 +780,9 @@ describe('UserImportBuilder', () => { const userRequestValidatorWithMultipleErrors: ValidatorFunction = (request) => { // Simulate a validation error is thrown for specific users. if (request.localId === 'USER2') { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL); + throw new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL); } else if (request.localId === 'USER4') { - throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER); + throw new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER); } }; @@ -836,39 +836,39 @@ describe('UserImportBuilder', () => { failureCount: 8, errors: [ // Client side detected error. - { index: 1, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL) }, + { index: 1, error: new FirebaseAuthError(authClientErrorCode.INVALID_EMAIL) }, // Server side detected error. { index: 2, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_USER_IMPORT, + authClientErrorCode.INVALID_USER_IMPORT, 'Some error occurred in USER3!', ), }, // Client side detected error. - { index: 3, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER) }, + { index: 3, error: new FirebaseAuthError(authClientErrorCode.INVALID_PHONE_NUMBER) }, // Server side detected error. { index: 5, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_USER_IMPORT, + authClientErrorCode.INVALID_USER_IMPORT, 'Another error occurred in USER6!', ), }, // Client side errors. - { index: 6, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD_HASH) }, - { index: 7, error: new FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD_SALT) }, + { index: 6, error: new FirebaseAuthError(authClientErrorCode.INVALID_PASSWORD_HASH) }, + { index: 7, error: new FirebaseAuthError(authClientErrorCode.INVALID_PASSWORD_SALT) }, { index: 8, error: new FirebaseAuthError( - AuthClientErrorCode.INVALID_ENROLLMENT_TIME, + authClientErrorCode.INVALID_ENROLLMENT_TIME, 'The second factor "enrollmentTime" for "enrollmentId1" must be a valid ' + 'UTC date string.'), }, { index: 9, error: new FirebaseAuthError( - AuthClientErrorCode.UNSUPPORTED_SECOND_FACTOR, + authClientErrorCode.UNSUPPORTED_SECOND_FACTOR, `Unsupported second factor "${JSON.stringify(testUsers[9].multiFactor!.enrolledFactors[0])}" provided.`), }, ], diff --git a/test/unit/utils/error.spec.ts b/test/unit/utils/error.spec.ts index c494a0103e..be091dc5c2 100644 --- a/test/unit/utils/error.spec.ts +++ b/test/unit/utils/error.spec.ts @@ -22,8 +22,9 @@ import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; import { - FirebaseError, FirebaseAuthError, FirebaseMessagingError, MessagingClientErrorCode, + FirebaseError, FirebaseMessagingError, MessagingClientErrorCode, } from '../../../src/utils/error'; +import { FirebaseAuthError } from '../../../src/auth/error'; chai.should(); chai.use(sinonChai); @@ -85,7 +86,7 @@ describe('FirebaseError', () => { message: 'message', httpResponse: mockHttpResponse as any }); - + const json = error.toJSON() as any; expect(json.httpResponse).to.deep.equal({ status: 200, @@ -235,7 +236,7 @@ describe('FirebaseAuthError', () => { const mockError: any = { response: mockRequestResponse }; const error = FirebaseAuthError.fromServerError( 'USER_NOT_FOUND', 'Invalid uid', mockError); - + const json = error.toJSON() as any; expect(json.httpResponse).to.deep.equal({ status: 400, @@ -335,7 +336,7 @@ describe('FirebaseMessagingError', () => { const expectedError = MessagingClientErrorCode.UNKNOWN_ERROR; expect(error.code).to.equal('messaging/' + expectedError.code); expect(error.message).to.be.equal( - `${ expectedError.message } Raw server response: "${ JSON.stringify(mockHttpResponse.data) }"`, + `${expectedError.message} Raw server response: "${JSON.stringify(mockHttpResponse.data)}"`, ); }); }); From fa404c672588eb09b7a329ac209d3d1536c329c7 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Fri, 1 May 2026 15:31:13 -0400 Subject: [PATCH 02/22] chore(installations): Refactor installations and instance id error logic --- etc/firebase-admin.installations.api.md | 21 +----- etc/firebase-admin.instance-id.api.md | 25 +------ src/installations/error.ts | 74 +++++++++++++++++++ src/installations/index.ts | 5 +- .../installations-request-handler.ts | 9 ++- src/installations/installations.ts | 4 +- src/instance-id/error.ts | 72 ++++++++++++++++++ src/instance-id/index.ts | 5 +- src/instance-id/instance-id.ts | 12 ++- src/utils/error.ts | 72 ------------------ test/unit/installations/installations.spec.ts | 7 +- test/unit/instance-id/instance-id.spec.ts | 14 ++-- 12 files changed, 184 insertions(+), 136 deletions(-) create mode 100644 src/installations/error.ts create mode 100644 src/instance-id/error.ts diff --git a/etc/firebase-admin.installations.api.md b/etc/firebase-admin.installations.api.md index 2dc62e75ec..ba3a208046 100644 --- a/etc/firebase-admin.installations.api.md +++ b/etc/firebase-admin.installations.api.md @@ -25,24 +25,7 @@ export class Installations { deleteInstallation(fid: string): Promise; } -// @public (undocumented) -export const InstallationsClientErrorCode: { - INVALID_ARGUMENT: { - code: string; - message: string; - }; - INVALID_PROJECT_ID: { - code: string; - message: string; - }; - INVALID_INSTALLATION_ID: { - code: string; - message: string; - }; - API_ERROR: { - code: string; - message: string; - }; -}; +// @public +export type InstallationsErrorCode = 'invalid-argument' | 'invalid-project-id' | 'invalid-installation-id' | 'api-error'; ``` diff --git a/etc/firebase-admin.instance-id.api.md b/etc/firebase-admin.instance-id.api.md index 45de619267..333b388117 100644 --- a/etc/firebase-admin.instance-id.api.md +++ b/etc/firebase-admin.instance-id.api.md @@ -25,28 +25,7 @@ export class InstanceId { deleteInstanceId(instanceId: string): Promise; } -// @public (undocumented) -export const InstanceIdClientErrorCode: { - INVALID_INSTANCE_ID: { - code: string; - message: string; - }; - INVALID_ARGUMENT: { - code: string; - message: string; - }; - INVALID_PROJECT_ID: { - code: string; - message: string; - }; - INVALID_INSTALLATION_ID: { - code: string; - message: string; - }; - API_ERROR: { - code: string; - message: string; - }; -}; +// @public +export type InstanceIdErrorCode = 'invalid-argument' | 'invalid-project-id' | 'invalid-installation-id' | 'api-error' | 'invalid-instance-id'; ``` diff --git a/src/installations/error.ts b/src/installations/error.ts new file mode 100644 index 0000000000..9d036427cf --- /dev/null +++ b/src/installations/error.ts @@ -0,0 +1,74 @@ +/*! + * Copyright 2021 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. + */ + +import { FirebaseError, ErrorInfo } from '../utils/error'; + +/** + * Installations client error codes. + */ +export type InstallationsErrorCode = + | 'invalid-argument' + | 'invalid-project-id' + | 'invalid-installation-id' + | 'api-error'; + +/** + * Installations client error codes and their default messages. + */ +const installationsClientErrorMessages: Record = { + 'invalid-argument': 'Invalid argument provided.', + 'invalid-project-id': 'Invalid project ID provided.', + 'invalid-installation-id': 'Invalid installation ID provided.', + 'api-error': 'Installation ID API call failed.', +}; + +/** + * Internal Installations client error code mapping used to construct ErrorInfo. + */ +export const installationsClientErrorCode = { + INVALID_ARGUMENT: createInstallationsErrorInfo('invalid-argument'), + INVALID_PROJECT_ID: createInstallationsErrorInfo('invalid-project-id'), + INVALID_INSTALLATION_ID: createInstallationsErrorInfo('invalid-installation-id'), + API_ERROR: createInstallationsErrorInfo('api-error'), +}; + +function createInstallationsErrorInfo(code: InstallationsErrorCode): ErrorInfo { + return { + code, + message: installationsClientErrorMessages[code] || 'An unknown error occurred.', + }; +} + +/** + * Firebase Installations service error code structure. This extends `FirebaseError`. + */ +export class FirebaseInstallationsError extends FirebaseError { + /** + * + * @param info - The error code info. + * @param message - The error message. This will override the default + * message if provided. + */ + constructor(info: ErrorInfo, message?: string) { + // Override default message if custom message provided. + super({ + code: 'installations/' + info.code, + message: message || info.message, + httpResponse: info.httpResponse, + cause: info.cause + }); + } +} diff --git a/src/installations/index.ts b/src/installations/index.ts index e7fc00ab78..567d27f2be 100644 --- a/src/installations/index.ts +++ b/src/installations/index.ts @@ -62,4 +62,7 @@ export function getInstallations(app?: App): Installations { return firebaseApp.getOrInitService('installations', (app) => new Installations(app)); } -export { FirebaseInstallationsError, InstallationsClientErrorCode } from '../utils/error'; +export { + FirebaseInstallationsError, + InstallationsErrorCode, +} from './error'; diff --git a/src/installations/installations-request-handler.ts b/src/installations/installations-request-handler.ts index 8542971e6e..a5fceda6bc 100644 --- a/src/installations/installations-request-handler.ts +++ b/src/installations/installations-request-handler.ts @@ -17,7 +17,8 @@ import { App } from '../app/index'; import { FirebaseApp } from '../app/firebase-app'; -import { FirebaseInstallationsError, InstallationsClientErrorCode, toHttpResponse } from '../utils/error'; +import { installationsClientErrorCode, FirebaseInstallationsError } from './error'; +import { toHttpResponse } from '../utils/error'; import { ApiSettings, AuthorizedHttpClient, HttpRequestConfig, RequestResponseError, } from '../utils/api-request'; @@ -66,7 +67,7 @@ export class FirebaseInstallationsRequestHandler { public deleteInstallation(fid: string): Promise { if (!validator.isNonEmptyString(fid)) { return Promise.reject(new FirebaseInstallationsError( - InstallationsClientErrorCode.INVALID_INSTALLATION_ID, + installationsClientErrorCode.INVALID_INSTALLATION_ID, 'Installation ID must be a non-empty string.', )); } @@ -101,7 +102,7 @@ export class FirebaseInstallationsRequestHandler { const message: string = template ? `Installation ID "${apiSettings.getEndpoint()}": ${template}` : errorMessage; throw new FirebaseInstallationsError({ - ...InstallationsClientErrorCode.API_ERROR, + ...installationsClientErrorCode.API_ERROR, message, httpResponse: toHttpResponse(response), cause: err, @@ -123,7 +124,7 @@ export class FirebaseInstallationsRequestHandler { if (!validator.isNonEmptyString(projectId)) { // Assert for an explicit projct ID (either via AppOptions or the cert itself). throw new FirebaseInstallationsError( - InstallationsClientErrorCode.INVALID_PROJECT_ID, + installationsClientErrorCode.INVALID_PROJECT_ID, 'Failed to determine project ID for Installations. Initialize the ' + 'SDK with service account credentials or set project ID as an app option. ' + 'Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.', diff --git a/src/installations/installations.ts b/src/installations/installations.ts index fb2bb5fa15..f677c8dda3 100644 --- a/src/installations/installations.ts +++ b/src/installations/installations.ts @@ -15,7 +15,7 @@ */ import { App } from '../app/index'; -import { FirebaseInstallationsError, InstallationsClientErrorCode } from '../utils/error'; +import { installationsClientErrorCode, FirebaseInstallationsError } from './error'; import { FirebaseInstallationsRequestHandler } from './installations-request-handler'; import * as validator from '../utils/validator'; @@ -35,7 +35,7 @@ export class Installations { constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseInstallationsError( - InstallationsClientErrorCode.INVALID_ARGUMENT, + installationsClientErrorCode.INVALID_ARGUMENT, 'First argument passed to admin.installations() must be a valid Firebase app instance.', ); } diff --git a/src/instance-id/error.ts b/src/instance-id/error.ts new file mode 100644 index 0000000000..86be3bc5c1 --- /dev/null +++ b/src/instance-id/error.ts @@ -0,0 +1,72 @@ +/*! + * Copyright 2021 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. + */ + +import { FirebaseError, ErrorInfo } from '../utils/error'; +import { installationsClientErrorCode } from '../installations/error'; + +/** + * Instance ID client error codes. + */ +export type InstanceIdErrorCode = + | 'invalid-argument' + | 'invalid-project-id' + | 'invalid-installation-id' + | 'api-error' + | 'invalid-instance-id'; + + +/** + * Instance ID client error codes and their default messages. + */ +const instanceIdClientErrorMessages = { + 'invalid-instance-id': 'Invalid instance ID provided.', +}; + +function createInstanceIdErrorInfo(code: 'invalid-instance-id'): ErrorInfo { + return { + code, + message: instanceIdClientErrorMessages[code] || 'An unknown error occurred.', + }; +} + +/** + * Internal Instance ID client error code mapping used to construct ErrorInfo. + */ +export const instanceIdClientErrorCode = { + ...installationsClientErrorCode, + INVALID_INSTANCE_ID: createInstanceIdErrorInfo('invalid-instance-id'), +}; + +/** + * Firebase Instance ID service error code structure. This extends `FirebaseError`. + */ +export class FirebaseInstanceIdError extends FirebaseError { + /** + * + * @param info - The error code info. + * @param message - The error message. This will override the default + * message if provided. + */ + constructor(info: ErrorInfo, message?: string) { + // Override default message if custom message provided. + super({ + code: 'instance-id/' + info.code, + message: message || info.message, + httpResponse: info.httpResponse, + cause: info.cause + }); + } +} diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index 0486a927af..a774c0a75b 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -71,4 +71,7 @@ export function getInstanceId(app?: App): InstanceId { return firebaseApp.getOrInitService('instanceId', (app) => new InstanceId(app)); } -export { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error'; +export { + FirebaseInstanceIdError, + InstanceIdErrorCode, +} from './error'; diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index 3a5baac09f..4564cd56ed 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -15,11 +15,9 @@ */ import { getInstallations } from '../installations'; +import { FirebaseInstallationsError, installationsClientErrorCode } from '../installations/error'; import { App } from '../app/index'; -import { - FirebaseInstallationsError, FirebaseInstanceIdError, - InstallationsClientErrorCode, InstanceIdClientErrorCode, -} from '../utils/error'; +import { FirebaseInstanceIdError, instanceIdClientErrorCode } from './error'; import * as validator from '../utils/validator'; /** @@ -40,7 +38,7 @@ export class InstanceId { constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseInstanceIdError( - InstanceIdClientErrorCode.INVALID_ARGUMENT, + instanceIdClientErrorCode.INVALID_ARGUMENT, 'First argument passed to instanceId() must be a valid Firebase app instance.', ); } @@ -67,8 +65,8 @@ export class InstanceId { .catch((err) => { if (err instanceof FirebaseInstallationsError) { let code = err.code.replace('installations/', ''); - if (code === InstallationsClientErrorCode.INVALID_INSTALLATION_ID.code) { - code = InstanceIdClientErrorCode.INVALID_INSTANCE_ID.code; + if (code === installationsClientErrorCode.INVALID_INSTALLATION_ID.code) { + code = instanceIdClientErrorCode.INVALID_INSTANCE_ID.code; } throw new FirebaseInstanceIdError({ code, message: err.message }); diff --git a/src/utils/error.ts b/src/utils/error.ts index c7f4279ec9..257b1937db 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -256,49 +256,6 @@ export class FirebaseFirestoreError extends FirebaseError { } } -/** - * Firebase instance ID error code structure. This extends FirebaseError. - */ -export class FirebaseInstanceIdError extends FirebaseError { - /** - * - * @param info - The error code info. - * @param message - The error message. This will override the default - * message if provided. - */ - constructor(info: ErrorInfo, message?: string) { - // Override default message if custom message provided. - super({ - code: 'instance-id/' + info.code, - message: message || info.message, - httpResponse: info.httpResponse, - cause: info.cause - }); - } -} - -/** - * Firebase Installations service error code structure. This extends `FirebaseError`. - */ -export class FirebaseInstallationsError extends FirebaseError { - /** - * - * @param info - The error code info. - * @param message - The error message. This will override the default - * message if provided. - */ - constructor(info: ErrorInfo, message?: string) { - // Override default message if custom message provided. - super({ - code: 'installations/' + info.code, - message: message || info.message, - httpResponse: info.httpResponse, - cause: info.cause - }); - } -} - - /** * Firebase Messaging error code structure. This extends PrefixedFirebaseError. */ @@ -536,35 +493,6 @@ export const MessagingClientErrorCode = { }, } satisfies Record; -// eslint-disable-next-line @typescript-eslint/naming-convention -export const InstallationsClientErrorCode = { - INVALID_ARGUMENT: { - code: 'invalid-argument', - message: 'Invalid argument provided.', - }, - INVALID_PROJECT_ID: { - code: 'invalid-project-id', - message: 'Invalid project ID provided.', - }, - INVALID_INSTALLATION_ID: { - code: 'invalid-installation-id', - message: 'Invalid installation ID provided.', - }, - API_ERROR: { - code: 'api-error', - message: 'Installation ID API call failed.', - }, -} satisfies Record; - -// eslint-disable-next-line @typescript-eslint/naming-convention -export const InstanceIdClientErrorCode = { - ...InstallationsClientErrorCode, - INVALID_INSTANCE_ID: { - code: 'invalid-instance-id', - message: 'Invalid instance ID provided.', - }, -} satisfies Record; - export type ProjectManagementErrorCode = 'already-exists' | 'authentication-error' diff --git a/test/unit/installations/installations.spec.ts b/test/unit/installations/installations.spec.ts index 71d0eeae9d..1b78f70553 100644 --- a/test/unit/installations/installations.spec.ts +++ b/test/unit/installations/installations.spec.ts @@ -29,7 +29,10 @@ import * as mocks from '../../resources/mocks'; import { Installations } from '../../../src/installations/installations'; import { FirebaseInstallationsRequestHandler } from '../../../src/installations/installations-request-handler'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { FirebaseInstallationsError, InstallationsClientErrorCode } from '../../../src/utils/error'; +import { + FirebaseInstallationsError, + installationsClientErrorCode, +} from '../../../src/installations/error'; chai.should(); chai.use(sinonChai); @@ -127,7 +130,7 @@ describe('Installations', () => { // Stubs used to simulate underlying api calls. let stubs: sinon.SinonStub[] = []; - const expectedError = new FirebaseInstallationsError(InstallationsClientErrorCode.API_ERROR); + const expectedError = new FirebaseInstallationsError(installationsClientErrorCode.API_ERROR); const testInstallationId = 'test-iid'; afterEach(() => { diff --git a/test/unit/instance-id/instance-id.spec.ts b/test/unit/instance-id/instance-id.spec.ts index f43d19d9d0..34eea41101 100644 --- a/test/unit/instance-id/instance-id.spec.ts +++ b/test/unit/instance-id/instance-id.spec.ts @@ -30,9 +30,13 @@ import { InstanceId } from '../../../src/instance-id/index'; import { Installations } from '../../../src/installations/index'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { - FirebaseInstanceIdError, InstanceIdClientErrorCode, - FirebaseInstallationsError, InstallationsClientErrorCode, -} from '../../../src/utils/error'; + FirebaseInstanceIdError, + instanceIdClientErrorCode, +} from '../../../src/instance-id/error'; +import { + FirebaseInstallationsError, + installationsClientErrorCode, +} from '../../../src/installations/error'; chai.should(); chai.use(sinonChai); @@ -175,7 +179,7 @@ describe('InstanceId', () => { it('should throw a FirebaseInstanceIdError error when the backend returns an error', () => { // Stub deleteInstanceId to throw a backend error. - const originalError = new FirebaseInstallationsError(InstallationsClientErrorCode.API_ERROR); + const originalError = new FirebaseInstallationsError(installationsClientErrorCode.API_ERROR); const stub = sinon.stub(Installations.prototype, 'deleteInstallation') .rejects(originalError); stubs.push(stub); @@ -186,7 +190,7 @@ describe('InstanceId', () => { // Confirm underlying API called with expected parameters. expect(stub).to.have.been.calledOnce.and.calledWith(testInstanceId); // Confirm expected error returned. - const expectedError = new FirebaseInstanceIdError(InstanceIdClientErrorCode.API_ERROR); + const expectedError = new FirebaseInstanceIdError(instanceIdClientErrorCode.API_ERROR); expect(error).to.be.instanceOf(FirebaseInstanceIdError) expect(error).to.deep.include(expectedError); }); From d474a7b90d526f80b4b6aa92ab5a2cc773b3868e Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Fri, 1 May 2026 17:44:10 -0400 Subject: [PATCH 03/22] chore(fcm): Refactor fcm error logic --- etc/firebase-admin.messaging.api.md | 79 +---- src/messaging/error.ts | 285 ++++++++++++++++++ src/messaging/index.ts | 5 +- src/messaging/messaging-errors-internal.ts | 13 +- src/messaging/messaging-internal.ts | 94 +++--- src/messaging/messaging.ts | 31 +- src/utils/error.ts | 281 +---------------- .../messaging-errors-internal.spec.ts | 2 +- test/unit/messaging/messaging.spec.ts | 2 +- test/unit/utils/error.spec.ts | 20 +- 10 files changed, 374 insertions(+), 438 deletions(-) create mode 100644 src/messaging/error.ts diff --git a/etc/firebase-admin.messaging.api.md b/etc/firebase-admin.messaging.api.md index 1d81f2f84c..919e6f6282 100644 --- a/etc/firebase-admin.messaging.api.md +++ b/etc/firebase-admin.messaging.api.md @@ -204,84 +204,7 @@ export class Messaging { } // @public -export const MessagingClientErrorCode: { - INVALID_ARGUMENT: { - code: string; - message: string; - }; - INVALID_RECIPIENT: { - code: string; - message: string; - }; - INVALID_PAYLOAD: { - code: string; - message: string; - }; - INVALID_DATA_PAYLOAD_KEY: { - code: string; - message: string; - }; - PAYLOAD_SIZE_LIMIT_EXCEEDED: { - code: string; - message: string; - }; - INVALID_OPTIONS: { - code: string; - message: string; - }; - INVALID_REGISTRATION_TOKEN: { - code: string; - message: string; - }; - REGISTRATION_TOKEN_NOT_REGISTERED: { - code: string; - message: string; - }; - MISMATCHED_CREDENTIAL: { - code: string; - message: string; - }; - INVALID_PACKAGE_NAME: { - code: string; - message: string; - }; - DEVICE_MESSAGE_RATE_EXCEEDED: { - code: string; - message: string; - }; - TOPICS_MESSAGE_RATE_EXCEEDED: { - code: string; - message: string; - }; - MESSAGE_RATE_EXCEEDED: { - code: string; - message: string; - }; - THIRD_PARTY_AUTH_ERROR: { - code: string; - message: string; - }; - TOO_MANY_TOPICS: { - code: string; - message: string; - }; - AUTHENTICATION_ERROR: { - code: string; - message: string; - }; - SERVER_UNAVAILABLE: { - code: string; - message: string; - }; - INTERNAL_ERROR: { - code: string; - message: string; - }; - UNKNOWN_ERROR: { - code: string; - message: string; - }; -}; +export type MessagingErrorCode = 'invalid-argument' | 'invalid-recipient' | 'invalid-payload' | 'invalid-data-payload-key' | 'payload-size-limit-exceeded' | 'invalid-options' | 'invalid-registration-token' | 'registration-token-not-registered' | 'mismatched-credential' | 'invalid-package-name' | 'device-message-rate-exceeded' | 'topics-message-rate-exceeded' | 'message-rate-exceeded' | 'third-party-auth-error' | 'too-many-topics' | 'authentication-error' | 'server-unavailable' | 'internal-error' | 'unknown-error'; // @public export interface MessagingOptions { diff --git a/src/messaging/error.ts b/src/messaging/error.ts new file mode 100644 index 0000000000..59ea9578c4 --- /dev/null +++ b/src/messaging/error.ts @@ -0,0 +1,285 @@ +/*! + * Copyright 2021 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. + */ + +import { ErrorInfo, PrefixedFirebaseError, toHttpResponse } from '../utils/error'; +import { RequestResponseError } from '../utils/api-request'; +import { deepCopy } from '../utils/deep-copy'; +import { BatchResponse } from './messaging-api'; + +/** + * Messaging client error codes. + */ +export type MessagingErrorCode = + | 'invalid-argument' + | 'invalid-recipient' + | 'invalid-payload' + | 'invalid-data-payload-key' + | 'payload-size-limit-exceeded' + | 'invalid-options' + | 'invalid-registration-token' + | 'registration-token-not-registered' + | 'mismatched-credential' + | 'invalid-package-name' + | 'device-message-rate-exceeded' + | 'topics-message-rate-exceeded' + | 'message-rate-exceeded' + | 'third-party-auth-error' + | 'too-many-topics' + | 'authentication-error' + | 'server-unavailable' + | 'internal-error' + | 'unknown-error'; + +/** + * Messaging client error codes and their default messages. + */ +const messagingClientErrorMessages: Record = { + 'invalid-argument': 'Invalid argument provided.', + 'invalid-recipient': 'Invalid message recipient provided.', + 'invalid-payload': 'Invalid message payload provided.', + 'invalid-data-payload-key': 'The data message payload contains an invalid key. See the reference ' + + 'documentation for the DataMessagePayload type for restricted keys.', + 'payload-size-limit-exceeded': 'The provided message payload exceeds the FCM size limits. See the ' + + 'error documentation for more details.', + 'invalid-options': 'Invalid message options provided.', + 'invalid-registration-token': 'Invalid registration token provided. Make sure it matches the ' + + 'registration token the client app receives from registering with FCM.', + 'registration-token-not-registered': 'The provided registration token is not registered. A ' + + 'previously valid registration token can be unregistered for a variety of reasons. See the ' + + 'error documentation for more details. Remove this registration token and stop using it to ' + + 'send messages.', + 'mismatched-credential': 'The credential used to authenticate this SDK does not have permission ' + + 'to send messages to the device corresponding to the provided registration token. Make sure the ' + + 'credential and registration token both belong to the same Firebase project.', + 'invalid-package-name': 'The message was addressed to a registration token whose package name does ' + + 'not match the provided "restrictedPackageName" option.', + 'device-message-rate-exceeded': 'The rate of messages to a particular device is too high. Reduce ' + + 'the number of messages sent to this device and do not immediately retry sending to this device.', + 'topics-message-rate-exceeded': 'The rate of messages to subscribers to a particular topic is too ' + + 'high. Reduce the number of messages sent for this topic, and do not immediately retry sending ' + + 'to this topic.', + 'message-rate-exceeded': 'Sending limit exceeded for the message target.', + 'third-party-auth-error': 'A message targeted to an iOS device could not be sent because the ' + + 'required APNs SSL certificate was not uploaded or has expired. Check the validity of your ' + + 'development and production certificates.', + 'too-many-topics': 'The maximum number of topics the provided registration token can be ' + + 'subscribed to has been exceeded.', + 'authentication-error': 'An error occurred when trying to authenticate to the FCM servers. Make ' + + 'sure the credential used to authenticate this SDK has the proper permissions. See ' + + 'https://firebase.google.com/docs/admin/setup for setup instructions.', + 'server-unavailable': 'The FCM server could not process the request in time. See the error ' + + 'documentation for more details.', + 'internal-error': 'An internal error has occurred. Please retry the request.', + 'unknown-error': 'An unknown server error was returned.', +}; + +function createMessagingErrorInfo(code: MessagingErrorCode): ErrorInfo { + return { + code, + message: messagingClientErrorMessages[code] || 'An unknown error occurred.', + }; +} + +/** + * Internal Messaging client error code mapping used to construct ErrorInfo. + */ +export const messagingClientErrorCode = { + INVALID_ARGUMENT: createMessagingErrorInfo('invalid-argument'), + INVALID_RECIPIENT: createMessagingErrorInfo('invalid-recipient'), + INVALID_PAYLOAD: createMessagingErrorInfo('invalid-payload'), + INVALID_DATA_PAYLOAD_KEY: createMessagingErrorInfo('invalid-data-payload-key'), + PAYLOAD_SIZE_LIMIT_EXCEEDED: createMessagingErrorInfo('payload-size-limit-exceeded'), + INVALID_OPTIONS: createMessagingErrorInfo('invalid-options'), + INVALID_REGISTRATION_TOKEN: createMessagingErrorInfo('invalid-registration-token'), + REGISTRATION_TOKEN_NOT_REGISTERED: createMessagingErrorInfo('registration-token-not-registered'), + MISMATCHED_CREDENTIAL: createMessagingErrorInfo('mismatched-credential'), + INVALID_PACKAGE_NAME: createMessagingErrorInfo('invalid-package-name'), + DEVICE_MESSAGE_RATE_EXCEEDED: createMessagingErrorInfo('device-message-rate-exceeded'), + TOPICS_MESSAGE_RATE_EXCEEDED: createMessagingErrorInfo('topics-message-rate-exceeded'), + MESSAGE_RATE_EXCEEDED: createMessagingErrorInfo('message-rate-exceeded'), + THIRD_PARTY_AUTH_ERROR: createMessagingErrorInfo('third-party-auth-error'), + TOO_MANY_TOPICS: createMessagingErrorInfo('too-many-topics'), + AUTHENTICATION_ERROR: createMessagingErrorInfo('authentication-error'), + SERVER_UNAVAILABLE: createMessagingErrorInfo('server-unavailable'), + INTERNAL_ERROR: createMessagingErrorInfo('internal-error'), + UNKNOWN_ERROR: createMessagingErrorInfo('unknown-error'), +}; + +/** @const {Record} Messaging server to client enum error codes. */ +const MESSAGING_SERVER_TO_CLIENT_CODE: Record = { + /* GENERIC ERRORS */ + // Generic invalid message parameter provided. + InvalidParameters: 'INVALID_ARGUMENT', + // Mismatched sender ID. + MismatchSenderId: 'MISMATCHED_CREDENTIAL', + // FCM server unavailable. + Unavailable: 'SERVER_UNAVAILABLE', + // FCM server internal error. + InternalServerError: 'INTERNAL_ERROR', + + /* SEND ERRORS */ + // Invalid registration token format. + InvalidRegistration: 'INVALID_REGISTRATION_TOKEN', + // Registration token is not registered. + NotRegistered: 'REGISTRATION_TOKEN_NOT_REGISTERED', + // Registration token does not match restricted package name. + InvalidPackageName: 'INVALID_PACKAGE_NAME', + // Message payload size limit exceeded. + MessageTooBig: 'PAYLOAD_SIZE_LIMIT_EXCEEDED', + // Invalid key in the data message payload. + InvalidDataKey: 'INVALID_DATA_PAYLOAD_KEY', + // Invalid time to live option. + InvalidTtl: 'INVALID_OPTIONS', + // Device message rate exceeded. + DeviceMessageRateExceeded: 'DEVICE_MESSAGE_RATE_EXCEEDED', + // Topics message rate exceeded. + TopicsMessageRateExceeded: 'TOPICS_MESSAGE_RATE_EXCEEDED', + // Invalid APNs credentials. + InvalidApnsCredential: 'THIRD_PARTY_AUTH_ERROR', + + /* FCM v1 canonical error codes */ + NOT_FOUND: 'REGISTRATION_TOKEN_NOT_REGISTERED', + PERMISSION_DENIED: 'MISMATCHED_CREDENTIAL', + RESOURCE_EXHAUSTED: 'MESSAGE_RATE_EXCEEDED', + UNAUTHENTICATED: 'THIRD_PARTY_AUTH_ERROR', + + /* FCM v1 new error codes */ + APNS_AUTH_ERROR: 'THIRD_PARTY_AUTH_ERROR', + INTERNAL: 'INTERNAL_ERROR', + INVALID_ARGUMENT: 'INVALID_ARGUMENT', + QUOTA_EXCEEDED: 'MESSAGE_RATE_EXCEEDED', + SENDER_ID_MISMATCH: 'MISMATCHED_CREDENTIAL', + THIRD_PARTY_AUTH_ERROR: 'THIRD_PARTY_AUTH_ERROR', + UNAVAILABLE: 'SERVER_UNAVAILABLE', + UNREGISTERED: 'REGISTRATION_TOKEN_NOT_REGISTERED', + UNSPECIFIED_ERROR: 'UNKNOWN_ERROR', +}; + +/** @const {Record} Topic management (IID) server to client enum error codes. */ +const TOPIC_MGT_SERVER_TO_CLIENT_CODE: Record = { + /* TOPIC SUBSCRIPTION MANAGEMENT ERRORS */ + NOT_FOUND: 'REGISTRATION_TOKEN_NOT_REGISTERED', + INVALID_ARGUMENT: 'INVALID_REGISTRATION_TOKEN', + TOO_MANY_TOPICS: 'TOO_MANY_TOPICS', + RESOURCE_EXHAUSTED: 'TOO_MANY_TOPICS', + PERMISSION_DENIED: 'AUTHENTICATION_ERROR', + DEADLINE_EXCEEDED: 'SERVER_UNAVAILABLE', + INTERNAL: 'INTERNAL_ERROR', + UNKNOWN: 'UNKNOWN_ERROR', +}; + +/** + * Firebase Messaging error code structure. This extends `PrefixedFirebaseError`. + */ +export class FirebaseMessagingError extends PrefixedFirebaseError { + /** + * Creates the developer-facing error corresponding to the backend error code. + * + * @param serverErrorCode - The server error code. + * @param [message] The error message. The default message is used + * if not provided. + * @param [serverError] The error's raw server response. + * @returns The corresponding developer-facing error. + * @internal + */ + public static fromServerError( + serverErrorCode: string | null, + message?: string | null, + serverError?: RequestResponseError, + ): FirebaseMessagingError { + // If not found, default to unknown error. + let clientCodeKey = 'UNKNOWN_ERROR'; + if (serverErrorCode && serverErrorCode in MESSAGING_SERVER_TO_CLIENT_CODE) { + clientCodeKey = MESSAGING_SERVER_TO_CLIENT_CODE[serverErrorCode]; + } + const error: ErrorInfo = deepCopy((messagingClientErrorCode as any)[clientCodeKey]); + error.message = message || error.message; + + const rawData = serverError?.response?.data; + if (clientCodeKey === 'UNKNOWN_ERROR' && typeof rawData !== 'undefined') { + try { + error.message += ` Raw server response: "${typeof rawData === 'string' ? rawData : JSON.stringify(rawData)}"`; + } catch (e) { + // Ignore JSON parsing error. + } + } + + error.cause = serverError; + error.httpResponse = serverError?.response ? toHttpResponse(serverError.response) : undefined; + return new FirebaseMessagingError(error); + } + + /** + * @internal + */ + public static fromTopicManagementServerError( + serverErrorCode: string, + message?: string, + serverError?: RequestResponseError, + ): FirebaseMessagingError { + // If not found, default to unknown error. + const clientCodeKey = TOPIC_MGT_SERVER_TO_CLIENT_CODE[serverErrorCode] || 'UNKNOWN_ERROR'; + const error: ErrorInfo = deepCopy((messagingClientErrorCode as any)[clientCodeKey]); + error.message = message || error.message; + + const rawData = serverError?.response?.data; + if (clientCodeKey === 'UNKNOWN_ERROR' && typeof rawData !== 'undefined') { + try { + error.message += ` Raw server response: "${typeof rawData === 'string' ? rawData : JSON.stringify(rawData)}"`; + } catch (e) { + // Ignore JSON parsing error. + } + } + + error.cause = serverError; + error.httpResponse = serverError?.response ? toHttpResponse(serverError.response) : undefined; + return new FirebaseMessagingError(error); + } + + /** + * + * @param info - The error code info. + * @param message - The error message. This will override the default message if provided. + */ + constructor(info: ErrorInfo, message?: string) { + // Override default message if custom message provided. + super('messaging', info.code, message || info.message, info.httpResponse, info.cause); + } +} + +export class FirebaseMessagingSessionError extends FirebaseMessagingError { + public pendingBatchResponse?: Promise; + /** + * + * @param info - The error code info. + * @param message - The error message. This will override the default message if provided. + * @param pendingBatchResponse - BatchResponse for pending messages when session error occured. + */ + constructor(info: ErrorInfo, message?: string, pendingBatchResponse?: Promise) { + // Override default message if custom message provided. + super(info, message || info.message); + this.pendingBatchResponse = pendingBatchResponse; + } + + /** @returns The object representation of the error. */ + public toJSON(): object { + return { + code: this.code, + message: this.message, + pendingBatchResponse: this.pendingBatchResponse, + }; + } +} diff --git a/src/messaging/index.ts b/src/messaging/index.ts index 16054c2c38..48ddbd0333 100644 --- a/src/messaging/index.ts +++ b/src/messaging/index.ts @@ -96,4 +96,7 @@ export function getMessaging(app?: App): Messaging { return firebaseApp.getOrInitService('messaging', (app) => new Messaging(app)); } -export { FirebaseMessagingError, MessagingClientErrorCode } from '../utils/error'; +export { + FirebaseMessagingError, + MessagingErrorCode, +} from './error'; diff --git a/src/messaging/messaging-errors-internal.ts b/src/messaging/messaging-errors-internal.ts index 86d5ee09b3..d759b14876 100644 --- a/src/messaging/messaging-errors-internal.ts +++ b/src/messaging/messaging-errors-internal.ts @@ -15,7 +15,8 @@ */ import { RequestResponseError } from '../utils/api-request'; -import { FirebaseMessagingError, MessagingClientErrorCode, toHttpResponse } from '../utils/error'; +import { FirebaseMessagingError, messagingClientErrorCode } from './error'; +import { toHttpResponse } from '../utils/error'; import * as validator from '../utils/validator'; /** @@ -38,21 +39,21 @@ export function createFirebaseError(err: RequestResponseError): FirebaseMessagin let error: {code: string; message: string}; switch (err.response.status) { case 400: - error = MessagingClientErrorCode.INVALID_ARGUMENT; + error = messagingClientErrorCode.INVALID_ARGUMENT; break; case 401: case 403: - error = MessagingClientErrorCode.AUTHENTICATION_ERROR; + error = messagingClientErrorCode.AUTHENTICATION_ERROR; break; case 500: - error = MessagingClientErrorCode.INTERNAL_ERROR; + error = messagingClientErrorCode.INTERNAL_ERROR; break; case 503: - error = MessagingClientErrorCode.SERVER_UNAVAILABLE; + error = messagingClientErrorCode.SERVER_UNAVAILABLE; break; default: // Treat non-JSON responses with unexpected status codes as unknown errors. - error = MessagingClientErrorCode.UNKNOWN_ERROR; + error = messagingClientErrorCode.UNKNOWN_ERROR; } return new FirebaseMessagingError({ code: error.code, diff --git a/src/messaging/messaging-internal.ts b/src/messaging/messaging-internal.ts index 6427b98e0a..8139c87b94 100644 --- a/src/messaging/messaging-internal.ts +++ b/src/messaging/messaging-internal.ts @@ -15,7 +15,7 @@ */ import { renameProperties, transformMillisecondsToSecondsString } from '../utils/index'; -import { MessagingClientErrorCode, FirebaseMessagingError, } from '../utils/error'; +import { messagingClientErrorCode, FirebaseMessagingError } from './error'; import * as validator from '../utils/validator'; import { @@ -42,7 +42,7 @@ export const BLACKLISTED_OPTIONS_KEYS = [ export function validateMessage(message: Message): void { if (!validator.isNonNullObject(message)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'Message must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'Message must be a non-null object'); } const anyMessage = message as any; @@ -54,14 +54,14 @@ export function validateMessage(message: Message): void { // Checks for illegal characters and empty string. if (!/^[a-zA-Z0-9-_.~%]+$/.test(anyMessage.topic)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'Malformed topic name'); + messagingClientErrorCode.INVALID_PAYLOAD, 'Malformed topic name'); } } const targets = [anyMessage.token, anyMessage.topic, anyMessage.condition]; if (targets.filter((v) => validator.isNonEmptyString(v)).length !== 1) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'Exactly one of topic, token or condition is required'); } @@ -84,12 +84,12 @@ function validateStringMap(map: { [key: string]: any } | undefined, label: strin return; } else if (!validator.isNonNullObject(map)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, `${label} must be a non-null object`); + messagingClientErrorCode.INVALID_PAYLOAD, `${label} must be a non-null object`); } Object.keys(map).forEach((key) => { if (!validator.isString(map[key])) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, `${label} must only contain string values`); + messagingClientErrorCode.INVALID_PAYLOAD, `${label} must only contain string values`); } }); } @@ -104,7 +104,7 @@ function validateWebpushConfig(config: WebpushConfig | undefined): void { return; } else if (!validator.isNonNullObject(config)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'webpush must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'webpush must be a non-null object'); } validateStringMap(config.headers, 'webpush.headers'); validateStringMap(config.data, 'webpush.data'); @@ -121,7 +121,7 @@ function validateApnsConfig(config: ApnsConfig | undefined): void { return; } else if (!validator.isNonNullObject(config)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'apns must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'apns must be a non-null object'); } validateApnsLiveActivityToken(config.liveActivityToken); validateStringMap(config.headers, 'apns.headers'); @@ -140,12 +140,12 @@ function validateApnsLiveActivityToken(liveActivityToken: ApnsConfig['liveActivi return; } else if (!validator.isString(liveActivityToken)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.liveActivityToken must be a string value', ); } else if (!validator.isNonEmptyString(liveActivityToken)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.liveActivityToken must be a non-empty string', ); } @@ -161,19 +161,19 @@ function validateApnsFcmOptions(fcmOptions: ApnsFcmOptions | undefined): void { return; } else if (!validator.isNonNullObject(fcmOptions)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object'); } if (typeof fcmOptions.imageUrl !== 'undefined' && !validator.isURL(fcmOptions.imageUrl)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'imageUrl must be a valid URL string'); } if (typeof fcmOptions.analyticsLabel !== 'undefined' && !validator.isString(fcmOptions.analyticsLabel)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value'); + messagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value'); } const propertyMappings: { [key: string]: string } = { @@ -182,7 +182,7 @@ function validateApnsFcmOptions(fcmOptions: ApnsFcmOptions | undefined): void { Object.keys(propertyMappings).forEach((key) => { if (key in fcmOptions && propertyMappings[key] in fcmOptions) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, `Multiple specifications for ${key} in ApnsFcmOptions`); } }); @@ -199,12 +199,12 @@ function validateFcmOptions(fcmOptions: FcmOptions | undefined): void { return; } else if (!validator.isNonNullObject(fcmOptions)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object'); } if (typeof fcmOptions.analyticsLabel !== 'undefined' && !validator.isString(fcmOptions.analyticsLabel)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value'); + messagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value'); } } @@ -218,12 +218,12 @@ function validateNotification(notification: Notification | undefined): void { return; } else if (!validator.isNonNullObject(notification)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'notification must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'notification must be a non-null object'); } if (typeof notification.imageUrl !== 'undefined' && !validator.isURL(notification.imageUrl)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'notification.imageUrl must be a valid URL string'); + messagingClientErrorCode.INVALID_PAYLOAD, 'notification.imageUrl must be a valid URL string'); } const propertyMappings: { [key: string]: string } = { @@ -232,7 +232,7 @@ function validateNotification(notification: Notification | undefined): void { Object.keys(propertyMappings).forEach((key) => { if (key in notification && propertyMappings[key] in notification) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, `Multiple specifications for ${key} in Notification`); } }); @@ -249,7 +249,7 @@ function validateApnsPayload(payload: ApnsPayload | undefined): void { return; } else if (!validator.isNonNullObject(payload)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload must be a non-null object'); } validateAps(payload.aps); } @@ -265,7 +265,7 @@ function validateAps(aps: Aps): void { return; } else if (!validator.isNonNullObject(aps)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps must be a non-null object'); } validateApsAlert(aps.alert); validateApsSound(aps.sound); @@ -278,7 +278,7 @@ function validateAps(aps: Aps): void { Object.keys(propertyMappings).forEach((key) => { if (key in aps && propertyMappings[key] in aps) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, `Multiple specifications for ${key} in Aps`); + messagingClientErrorCode.INVALID_PAYLOAD, `Multiple specifications for ${key} in Aps`); } }); renameProperties(aps, propertyMappings); @@ -307,25 +307,25 @@ function validateApsSound(sound: string | CriticalSound | undefined): void { return; } else if (!validator.isNonNullObject(sound)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.sound must be a non-empty string or a non-null object'); } if (!validator.isNonEmptyString(sound.name)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.sound.name must be a non-empty string'); } const volume = sound.volume; if (typeof volume !== 'undefined') { if (!validator.isNumber(volume)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.sound.volume must be a number'); } if (volume < 0 || volume > 1) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.sound.volume must be in the interval [0, 1]'); } } @@ -353,7 +353,7 @@ function validateApsAlert(alert: string | ApsAlert | undefined): void { return; } else if (!validator.isNonNullObject(alert)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.alert must be a string or a non-null object'); } @@ -361,19 +361,19 @@ function validateApsAlert(alert: string | ApsAlert | undefined): void { if (validator.isNonEmptyArray(apsAlert.locArgs) && !validator.isNonEmptyString(apsAlert.locKey)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.alert.locKey is required when specifying locArgs'); } if (validator.isNonEmptyArray(apsAlert.titleLocArgs) && !validator.isNonEmptyString(apsAlert.titleLocKey)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.alert.titleLocKey is required when specifying titleLocArgs'); } if (validator.isNonEmptyArray(apsAlert.subtitleLocArgs) && !validator.isNonEmptyString(apsAlert.subtitleLocKey)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.alert.subtitleLocKey is required when specifying subtitleLocArgs'); } @@ -402,13 +402,13 @@ function validateAndroidConfig(config: AndroidConfig | undefined): void { return; } else if (!validator.isNonNullObject(config)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'android must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'android must be a non-null object'); } if (typeof config.ttl !== 'undefined') { if (!validator.isNumber(config.ttl) || config.ttl < 0) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'TTL must be a non-negative duration in milliseconds'); } const duration: string = transformMillisecondsToSecondsString(config.ttl); @@ -440,36 +440,36 @@ function validateAndroidNotification(notification: AndroidNotification | undefin return; } else if (!validator.isNonNullObject(notification)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification must be a non-null object'); } if (typeof notification.color !== 'undefined' && !/^#[0-9a-fA-F]{6}$/.test(notification.color)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.color must be in the form #RRGGBB'); + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.color must be in the form #RRGGBB'); } if (validator.isNonEmptyArray(notification.bodyLocArgs) && !validator.isNonEmptyString(notification.bodyLocKey)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.bodyLocKey is required when specifying bodyLocArgs'); } if (validator.isNonEmptyArray(notification.titleLocArgs) && !validator.isNonEmptyString(notification.titleLocKey)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.titleLocKey is required when specifying titleLocArgs'); } if (typeof notification.imageUrl !== 'undefined' && !validator.isURL(notification.imageUrl)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.imageUrl must be a valid URL string'); } if (typeof notification.eventTimestamp !== 'undefined') { if (!(notification.eventTimestamp instanceof Date)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.eventTimestamp must be a valid `Date` object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.eventTimestamp must be a valid `Date` object'); } // Convert timestamp to RFC3339 UTC "Zulu" format, example "2014-10-02T15:01:23.045123456Z" const zuluTimestamp = notification.eventTimestamp.toISOString(); @@ -479,14 +479,14 @@ function validateAndroidNotification(notification: AndroidNotification | undefin if (typeof notification.vibrateTimingsMillis !== 'undefined') { if (!validator.isNonEmptyArray(notification.vibrateTimingsMillis)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.vibrateTimingsMillis must be a non-empty array of numbers'); } const vibrateTimings: string[] = []; notification.vibrateTimingsMillis.forEach((value) => { if (!validator.isNumber(value) || value < 0) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.vibrateTimingsMillis must be non-negative durations in milliseconds'); } const duration = transformMillisecondsToSecondsString(value); @@ -545,12 +545,12 @@ function validateLightSettings(lightSettings?: LightSettings): void { return; } else if (!validator.isNonNullObject(lightSettings)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.lightSettings must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.lightSettings must be a non-null object'); } if (!validator.isNumber(lightSettings.lightOnDurationMillis) || lightSettings.lightOnDurationMillis < 0) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.lightSettings.lightOnDurationMillis must be a non-negative duration in milliseconds'); } const durationOn = transformMillisecondsToSecondsString(lightSettings.lightOnDurationMillis); @@ -558,7 +558,7 @@ function validateLightSettings(lightSettings?: LightSettings): void { if (!validator.isNumber(lightSettings.lightOffDurationMillis) || lightSettings.lightOffDurationMillis < 0) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.lightSettings.lightOffDurationMillis must be a non-negative duration in milliseconds'); } const durationOff = transformMillisecondsToSecondsString(lightSettings.lightOffDurationMillis); @@ -567,14 +567,14 @@ function validateLightSettings(lightSettings?: LightSettings): void { if (!validator.isString(lightSettings.color) || (!/^#[0-9a-fA-F]{6}$/.test(lightSettings.color) && !/^#[0-9a-fA-F]{8}$/.test(lightSettings.color))) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, + messagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.lightSettings.color must be in the form #RRGGBB or #RRGGBBAA format'); } const colorString = lightSettings.color.length === 7 ? lightSettings.color + 'FF' : lightSettings.color; const rgb = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/i.exec(colorString); if (!rgb || rgb.length < 4) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INTERNAL_ERROR, + messagingClientErrorCode.INTERNAL_ERROR, 'regex to extract rgba values from ' + colorString + ' failed.'); } const color = { @@ -602,11 +602,11 @@ function validateAndroidFcmOptions(fcmOptions: AndroidFcmOptions | undefined): v return; } else if (!validator.isNonNullObject(fcmOptions)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object'); + messagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object'); } if (typeof fcmOptions.analyticsLabel !== 'undefined' && !validator.isString(fcmOptions.analyticsLabel)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value'); + messagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value'); } } diff --git a/src/messaging/messaging.ts b/src/messaging/messaging.ts index 05367128f4..3835a931c8 100644 --- a/src/messaging/messaging.ts +++ b/src/messaging/messaging.ts @@ -18,8 +18,9 @@ import { App } from '../app'; import { deepCopy } from '../utils/deep-copy'; import { - ErrorInfo, MessagingClientErrorCode, FirebaseMessagingError, FirebaseMessagingSessionError -} from '../utils/error'; + messagingClientErrorCode, FirebaseMessagingError, FirebaseMessagingSessionError +} from './error'; +import { ErrorInfo } from '../utils/error'; import * as utils from '../utils'; import * as validator from '../utils/validator'; import { validateMessage } from './messaging-internal'; @@ -98,7 +99,7 @@ export class Messaging { constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_ARGUMENT, + messagingClientErrorCode.INVALID_ARGUMENT, 'First argument passed to admin.messaging() must be a valid Firebase app instance.', ); } @@ -152,7 +153,7 @@ export class Messaging { validateMessage(copy); if (typeof dryRun !== 'undefined' && !validator.isBoolean(dryRun)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_ARGUMENT, 'dryRun must be a boolean'); + messagingClientErrorCode.INVALID_ARGUMENT, 'dryRun must be a boolean'); } return this.getUrlPath() .then((urlPath) => { @@ -196,16 +197,16 @@ export class Messaging { const copy: Message[] = deepCopy(messages); if (!validator.isNonEmptyArray(copy)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_ARGUMENT, 'messages must be a non-empty array'); + messagingClientErrorCode.INVALID_ARGUMENT, 'messages must be a non-empty array'); } if (copy.length > FCM_MAX_BATCH_SIZE) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_ARGUMENT, + messagingClientErrorCode.INVALID_ARGUMENT, `messages list must not contain more than ${FCM_MAX_BATCH_SIZE} items`); } if (typeof dryRun !== 'undefined' && !validator.isBoolean(dryRun)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_ARGUMENT, 'dryRun must be a boolean'); + messagingClientErrorCode.INVALID_ARGUMENT, 'dryRun must be a boolean'); } const http2SessionHandler = this.useLegacyTransport ? undefined : new Http2SessionHandler(`https://${FCM_SEND_HOST}`); @@ -295,15 +296,15 @@ export class Messaging { const copy: MulticastMessage = deepCopy(message); if (!validator.isNonNullObject(copy)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_ARGUMENT, 'MulticastMessage must be a non-null object'); + messagingClientErrorCode.INVALID_ARGUMENT, 'MulticastMessage must be a non-null object'); } if (!validator.isNonEmptyArray(copy.tokens)) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_ARGUMENT, 'tokens must be a non-empty array'); + messagingClientErrorCode.INVALID_ARGUMENT, 'tokens must be a non-empty array'); } if (copy.tokens.length > FCM_MAX_BATCH_SIZE) { throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_ARGUMENT, + messagingClientErrorCode.INVALID_ARGUMENT, `tokens list must not contain more than ${FCM_MAX_BATCH_SIZE} items`); } @@ -385,7 +386,7 @@ export class Messaging { if (!validator.isNonEmptyString(projectId)) { // Assert for an explicit project ID (either via AppOptions or the cert itself). throw new FirebaseMessagingError( - MessagingClientErrorCode.INVALID_ARGUMENT, + messagingClientErrorCode.INVALID_ARGUMENT, 'Failed to determine project ID for Messaging. Initialize the ' + 'SDK with service account credentials or set project ID as an app option. ' + 'Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.', @@ -458,7 +459,7 @@ export class Messaging { private validateRegistrationTokensType( registrationTokenOrTokens: string | string[], methodName: string, - errorInfo: ErrorInfo = MessagingClientErrorCode.INVALID_ARGUMENT, + errorInfo: ErrorInfo = messagingClientErrorCode.INVALID_ARGUMENT, ): void { if (!validator.isNonEmptyArray(registrationTokenOrTokens) && !validator.isNonEmptyString(registrationTokenOrTokens)) { @@ -481,7 +482,7 @@ export class Messaging { private validateRegistrationTokens( registrationTokenOrTokens: string | string[], methodName: string, - errorInfo: ErrorInfo = MessagingClientErrorCode.INVALID_ARGUMENT, + errorInfo: ErrorInfo = messagingClientErrorCode.INVALID_ARGUMENT, ): void { if (validator.isArray(registrationTokenOrTokens)) { // Validate the array contains no more than 1,000 registration tokens. @@ -516,7 +517,7 @@ export class Messaging { private validateTopicType( topic: string | string[], methodName: string, - errorInfo: ErrorInfo = MessagingClientErrorCode.INVALID_ARGUMENT, + errorInfo: ErrorInfo = messagingClientErrorCode.INVALID_ARGUMENT, ): void { if (!validator.isNonEmptyString(topic)) { throw new FirebaseMessagingError( @@ -537,7 +538,7 @@ export class Messaging { private validateTopic( topic: string, methodName: string, - errorInfo: ErrorInfo = MessagingClientErrorCode.INVALID_ARGUMENT, + errorInfo: ErrorInfo = messagingClientErrorCode.INVALID_ARGUMENT, ): void { if (!validator.isTopic(topic)) { throw new FirebaseMessagingError( diff --git a/src/utils/error.ts b/src/utils/error.ts index 257b1937db..849bc6254e 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -15,9 +15,7 @@ * limitations under the License. */ -import { BatchResponse } from '../messaging/messaging-api'; -import { deepCopy } from '../utils/deep-copy'; -import { RequestResponseError, RequestResponse } from './api-request'; +import { RequestResponse } from './api-request'; /** * Represents the raw HTTP response object. @@ -60,13 +58,6 @@ export interface ErrorInfo { cause?: Error; } -/** - * Defines a type that stores all server to client codes (string enum). - */ -interface ServerToClientCode { - [code: string]: string; -} - /** * `FirebaseError` is a subclass of the standard JavaScript `Error` object. In * addition to a message string and stack trace, it contains a string code. @@ -215,7 +206,6 @@ export class FirebaseAppError extends PrefixedFirebaseError { } } - /** * Firebase Database error code structure. This extends FirebaseError. */ @@ -256,111 +246,6 @@ export class FirebaseFirestoreError extends FirebaseError { } } -/** - * Firebase Messaging error code structure. This extends PrefixedFirebaseError. - */ -export class FirebaseMessagingError extends PrefixedFirebaseError { - /** - * Creates the developer-facing error corresponding to the backend error code. - * - * @param serverErrorCode - The server error code. - * @param [message] The error message. The default message is used - * if not provided. - * @param [serverError] The error's raw server response. - * @returns The corresponding developer-facing error. - * @internal - */ - public static fromServerError( - serverErrorCode: string | null, - message?: string | null, - serverError?: RequestResponseError, - ): FirebaseMessagingError { - // If not found, default to unknown error. - let clientCodeKey = 'UNKNOWN_ERROR'; - if (serverErrorCode && serverErrorCode in MESSAGING_SERVER_TO_CLIENT_CODE) { - clientCodeKey = MESSAGING_SERVER_TO_CLIENT_CODE[serverErrorCode]; - } - const error: ErrorInfo = deepCopy((MessagingClientErrorCode as any)[clientCodeKey]); - error.message = message || error.message; - - const rawData = serverError?.response?.data; - if (clientCodeKey === 'UNKNOWN_ERROR' && typeof rawData !== 'undefined') { - try { - error.message += ` Raw server response: "${typeof rawData === 'string' ? rawData : JSON.stringify(rawData)}"`; - } catch (e) { - // Ignore JSON parsing error. - } - } - - error.cause = serverError; - error.httpResponse = serverError?.response ? toHttpResponse(serverError.response) : undefined; - return new FirebaseMessagingError(error); - } - - /** - * @internal - */ - public static fromTopicManagementServerError( - serverErrorCode: string, - message?: string, - serverError?: RequestResponseError, - ): FirebaseMessagingError { - // If not found, default to unknown error. - const clientCodeKey = TOPIC_MGT_SERVER_TO_CLIENT_CODE[serverErrorCode] || 'UNKNOWN_ERROR'; - const error: ErrorInfo = deepCopy((MessagingClientErrorCode as any)[clientCodeKey]); - error.message = message || error.message; - - const rawData = serverError?.response?.data; - if (clientCodeKey === 'UNKNOWN_ERROR' && typeof rawData !== 'undefined') { - try { - error.message += ` Raw server response: "${typeof rawData === 'string' ? rawData : JSON.stringify(rawData)}"`; - } catch (e) { - // Ignore JSON parsing error. - } - } - - error.cause = serverError; - error.httpResponse = serverError?.response ? toHttpResponse(serverError.response) : undefined; - return new FirebaseMessagingError(error); - } - - /** - * - * @param info - The error code info. - * @param message - The error message. This will override the default message if provided. - */ - constructor(info: ErrorInfo, message?: string) { - // Override default message if custom message provided. - super('messaging', info.code, message || info.message, info.httpResponse, info.cause); - - } -} - -export class FirebaseMessagingSessionError extends FirebaseMessagingError { - public pendingBatchResponse?: Promise; - /** - * - * @param info - The error code info. - * @param message - The error message. This will override the default message if provided. - * @param pendingBatchResponse - BatchResponse for pending messages when session error occured. - */ - constructor(info: ErrorInfo, message?: string, pendingBatchResponse?: Promise) { - // Override default message if custom message provided. - super(info, message || info.message); - this.pendingBatchResponse = pendingBatchResponse; - - } - - /** @returns The object representation of the error. */ - public toJSON(): object { - return { - code: this.code, - message: this.message, - pendingBatchResponse: this.pendingBatchResponse, - }; - } -} - /** * Firebase project management error code structure. This extends PrefixedFirebaseError. */ @@ -393,106 +278,6 @@ export class AppErrorCodes { public static UNABLE_TO_PARSE_RESPONSE = 'unable-to-parse-response'; } - -/** - * Messaging client error codes and their default messages. - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export const MessagingClientErrorCode = { - INVALID_ARGUMENT: { - code: 'invalid-argument', - message: 'Invalid argument provided.', - }, - INVALID_RECIPIENT: { - code: 'invalid-recipient', - message: 'Invalid message recipient provided.', - }, - INVALID_PAYLOAD: { - code: 'invalid-payload', - message: 'Invalid message payload provided.', - }, - INVALID_DATA_PAYLOAD_KEY: { - code: 'invalid-data-payload-key', - message: 'The data message payload contains an invalid key. See the reference documentation ' + - 'for the DataMessagePayload type for restricted keys.', - }, - PAYLOAD_SIZE_LIMIT_EXCEEDED: { - code: 'payload-size-limit-exceeded', - message: 'The provided message payload exceeds the FCM size limits. See the error documentation ' + - 'for more details.', - }, - INVALID_OPTIONS: { - code: 'invalid-options', - message: 'Invalid message options provided.', - }, - INVALID_REGISTRATION_TOKEN: { - code: 'invalid-registration-token', - message: 'Invalid registration token provided. Make sure it matches the registration token ' + - 'the client app receives from registering with FCM.', - }, - REGISTRATION_TOKEN_NOT_REGISTERED: { - code: 'registration-token-not-registered', - message: 'The provided registration token is not registered. A previously valid registration ' + - 'token can be unregistered for a variety of reasons. See the error documentation for more ' + - 'details. Remove this registration token and stop using it to send messages.', - }, - MISMATCHED_CREDENTIAL: { - code: 'mismatched-credential', - message: 'The credential used to authenticate this SDK does not have permission to send ' + - 'messages to the device corresponding to the provided registration token. Make sure the ' + - 'credential and registration token both belong to the same Firebase project.', - }, - INVALID_PACKAGE_NAME: { - code: 'invalid-package-name', - message: 'The message was addressed to a registration token whose package name does not match ' + - 'the provided "restrictedPackageName" option.', - }, - DEVICE_MESSAGE_RATE_EXCEEDED: { - code: 'device-message-rate-exceeded', - message: 'The rate of messages to a particular device is too high. Reduce the number of ' + - 'messages sent to this device and do not immediately retry sending to this device.', - }, - TOPICS_MESSAGE_RATE_EXCEEDED: { - code: 'topics-message-rate-exceeded', - message: 'The rate of messages to subscribers to a particular topic is too high. Reduce the ' + - 'number of messages sent for this topic, and do not immediately retry sending to this topic.', - }, - MESSAGE_RATE_EXCEEDED: { - code: 'message-rate-exceeded', - message: 'Sending limit exceeded for the message target.', - }, - THIRD_PARTY_AUTH_ERROR: { - code: 'third-party-auth-error', - message: 'A message targeted to an iOS device could not be sent because the required APNs ' + - 'SSL certificate was not uploaded or has expired. Check the validity of your development ' + - 'and production certificates.', - }, - TOO_MANY_TOPICS: { - code: 'too-many-topics', - message: 'The maximum number of topics the provided registration token can be subscribed to ' + - 'has been exceeded.', - }, - AUTHENTICATION_ERROR: { - code: 'authentication-error', - message: 'An error occurred when trying to authenticate to the FCM servers. Make sure the ' + - 'credential used to authenticate this SDK has the proper permissions. See ' + - 'https://firebase.google.com/docs/admin/setup for setup instructions.', - }, - SERVER_UNAVAILABLE: { - code: 'server-unavailable', - message: 'The FCM server could not process the request in time. See the error documentation ' + - 'for more details.', - }, - INTERNAL_ERROR: { - code: 'internal-error', - message: 'An internal error has occurred. Please retry the request.', - }, - UNKNOWN_ERROR: { - code: 'unknown-error', - message: 'An unknown server error was returned.', - }, -} satisfies Record; - export type ProjectManagementErrorCode = 'already-exists' | 'authentication-error' @@ -503,67 +288,3 @@ export type ProjectManagementErrorCode = | 'not-found' | 'service-unavailable' | 'unknown-error'; - - -/** @const {ServerToClientCode} Messaging server to client enum error codes. */ -const MESSAGING_SERVER_TO_CLIENT_CODE: ServerToClientCode = { - /* GENERIC ERRORS */ - // Generic invalid message parameter provided. - InvalidParameters: 'INVALID_ARGUMENT', - // Mismatched sender ID. - MismatchSenderId: 'MISMATCHED_CREDENTIAL', - // FCM server unavailable. - Unavailable: 'SERVER_UNAVAILABLE', - // FCM server internal error. - InternalServerError: 'INTERNAL_ERROR', - - /* SEND ERRORS */ - // Invalid registration token format. - InvalidRegistration: 'INVALID_REGISTRATION_TOKEN', - // Registration token is not registered. - NotRegistered: 'REGISTRATION_TOKEN_NOT_REGISTERED', - // Registration token does not match restricted package name. - InvalidPackageName: 'INVALID_PACKAGE_NAME', - // Message payload size limit exceeded. - MessageTooBig: 'PAYLOAD_SIZE_LIMIT_EXCEEDED', - // Invalid key in the data message payload. - InvalidDataKey: 'INVALID_DATA_PAYLOAD_KEY', - // Invalid time to live option. - InvalidTtl: 'INVALID_OPTIONS', - // Device message rate exceeded. - DeviceMessageRateExceeded: 'DEVICE_MESSAGE_RATE_EXCEEDED', - // Topics message rate exceeded. - TopicsMessageRateExceeded: 'TOPICS_MESSAGE_RATE_EXCEEDED', - // Invalid APNs credentials. - InvalidApnsCredential: 'THIRD_PARTY_AUTH_ERROR', - - /* FCM v1 canonical error codes */ - NOT_FOUND: 'REGISTRATION_TOKEN_NOT_REGISTERED', - PERMISSION_DENIED: 'MISMATCHED_CREDENTIAL', - RESOURCE_EXHAUSTED: 'MESSAGE_RATE_EXCEEDED', - UNAUTHENTICATED: 'THIRD_PARTY_AUTH_ERROR', - - /* FCM v1 new error codes */ - APNS_AUTH_ERROR: 'THIRD_PARTY_AUTH_ERROR', - INTERNAL: 'INTERNAL_ERROR', - INVALID_ARGUMENT: 'INVALID_ARGUMENT', - QUOTA_EXCEEDED: 'MESSAGE_RATE_EXCEEDED', - SENDER_ID_MISMATCH: 'MISMATCHED_CREDENTIAL', - THIRD_PARTY_AUTH_ERROR: 'THIRD_PARTY_AUTH_ERROR', - UNAVAILABLE: 'SERVER_UNAVAILABLE', - UNREGISTERED: 'REGISTRATION_TOKEN_NOT_REGISTERED', - UNSPECIFIED_ERROR: 'UNKNOWN_ERROR', -}; - -/** @const {ServerToClientCode} Topic management (IID) server to client enum error codes. */ -const TOPIC_MGT_SERVER_TO_CLIENT_CODE: ServerToClientCode = { - /* TOPIC SUBSCRIPTION MANAGEMENT ERRORS */ - NOT_FOUND: 'REGISTRATION_TOKEN_NOT_REGISTERED', - INVALID_ARGUMENT: 'INVALID_REGISTRATION_TOKEN', - TOO_MANY_TOPICS: 'TOO_MANY_TOPICS', - RESOURCE_EXHAUSTED: 'TOO_MANY_TOPICS', - PERMISSION_DENIED: 'AUTHENTICATION_ERROR', - DEADLINE_EXCEEDED: 'SERVER_UNAVAILABLE', - INTERNAL: 'INTERNAL_ERROR', - UNKNOWN: 'UNKNOWN_ERROR', -}; diff --git a/test/unit/messaging/messaging-errors-internal.spec.ts b/test/unit/messaging/messaging-errors-internal.spec.ts index 0ea48023e1..360fe0716b 100644 --- a/test/unit/messaging/messaging-errors-internal.spec.ts +++ b/test/unit/messaging/messaging-errors-internal.spec.ts @@ -22,7 +22,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import { createFirebaseError } from '../../../src/messaging/messaging-errors-internal'; import { RequestResponseError, RequestResponse } from '../../../src/utils/api-request'; -import { FirebaseMessagingError } from '../../../src/utils/error'; +import { FirebaseMessagingError } from '../../../src/messaging/error'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index af4dad1d4d..e3daa656e6 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -34,7 +34,7 @@ import { import { HttpClient } from '../../../src/utils/api-request'; import { getMetricsHeader, getSdkVersion } from '../../../src/utils/index'; import * as utils from '../utils'; -import { FirebaseMessagingSessionError } from '../../../src/utils/error'; +import { FirebaseMessagingSessionError } from '../../../src/messaging/error'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/utils/error.spec.ts b/test/unit/utils/error.spec.ts index be091dc5c2..72b65392e1 100644 --- a/test/unit/utils/error.spec.ts +++ b/test/unit/utils/error.spec.ts @@ -21,10 +21,12 @@ import * as chai from 'chai'; import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { - FirebaseError, FirebaseMessagingError, MessagingClientErrorCode, -} from '../../../src/utils/error'; +import { FirebaseError } from '../../../src/utils/error'; import { FirebaseAuthError } from '../../../src/auth/error'; +import { + FirebaseMessagingError, + messagingClientErrorCode, +} from '../../../src/messaging/error'; chai.should(); chai.use(sinonChai); @@ -277,14 +279,14 @@ describe('FirebaseMessagingError', () => { describe('without message specified', () => { it('should initialize an error from an expected server code', () => { const error = FirebaseMessagingError.fromServerError('InvalidRegistration'); - const expectedError = MessagingClientErrorCode.INVALID_REGISTRATION_TOKEN; + const expectedError = messagingClientErrorCode.INVALID_REGISTRATION_TOKEN; expect(error.code).to.equal('messaging/' + expectedError.code); expect(error.message).to.equal(expectedError.message); }); it('should initialize an error from an unexpected server code', () => { const error = FirebaseMessagingError.fromServerError('UNEXPECTED_ERROR'); - const expectedError = MessagingClientErrorCode.UNKNOWN_ERROR; + const expectedError = messagingClientErrorCode.UNKNOWN_ERROR; expect(error.code).to.equal('messaging/' + expectedError.code); expect(error.message).to.equal(expectedError.message); }); @@ -293,14 +295,14 @@ describe('FirebaseMessagingError', () => { describe('with message specified', () => { it('should initialize an error from an expected server code', () => { const error = FirebaseMessagingError.fromServerError('InvalidRegistration', 'Message override.'); - const expectedError = MessagingClientErrorCode.INVALID_REGISTRATION_TOKEN; + const expectedError = messagingClientErrorCode.INVALID_REGISTRATION_TOKEN; expect(error.code).to.equal('messaging/' + expectedError.code); expect(error.message).to.equal('Message override.'); }); it('should initialize an error from an unexpected server code', () => { const error = FirebaseMessagingError.fromServerError('UNEXPECTED_ERROR', 'Message override.'); - const expectedError = MessagingClientErrorCode.UNKNOWN_ERROR; + const expectedError = messagingClientErrorCode.UNKNOWN_ERROR; expect(error.code).to.equal('messaging/' + expectedError.code); expect(error.message).to.equal('Message override.'); }); @@ -324,7 +326,7 @@ describe('FirebaseMessagingError', () => { const error = FirebaseMessagingError.fromServerError( 'InvalidRegistration', /* message */ undefined, mockError, ); - const expectedError = MessagingClientErrorCode.INVALID_REGISTRATION_TOKEN; + const expectedError = messagingClientErrorCode.INVALID_REGISTRATION_TOKEN; expect(error.code).to.equal('messaging/' + expectedError.code); expect(error.message).to.equal(expectedError.message); }); @@ -333,7 +335,7 @@ describe('FirebaseMessagingError', () => { const error = FirebaseMessagingError.fromServerError( 'UNEXPECTED_ERROR', /* message */ undefined, mockError, ); - const expectedError = MessagingClientErrorCode.UNKNOWN_ERROR; + const expectedError = messagingClientErrorCode.UNKNOWN_ERROR; expect(error.code).to.equal('messaging/' + expectedError.code); expect(error.message).to.be.equal( `${expectedError.message} Raw server response: "${JSON.stringify(mockHttpResponse.data)}"`, From a2f7e6262547f60b116939c479ad7bdc0b50514f Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Fri, 1 May 2026 18:06:54 -0400 Subject: [PATCH 04/22] fix(auth): Fix auth error mapping that were not copied correctly --- src/auth/error.ts | 151 +++++++++++++++++++++++++++++++--------------- 1 file changed, 103 insertions(+), 48 deletions(-) diff --git a/src/auth/error.ts b/src/auth/error.ts index f4fa39c1c4..61615dc5c5 100644 --- a/src/auth/error.ts +++ b/src/auth/error.ts @@ -232,81 +232,136 @@ const authClientErrorMessages: Record = { 'racaptcha-not-enabled': 'reCAPTCHA enterprise is not enabled.', }; -/** @const {ServerToClientCode} Auth server to client enum error codes. */ +/** @const {Record} Auth server to client enum error codes. */ const AUTH_SERVER_TO_CLIENT_CODE: Record = { + // Feature being configured or used requires a billing account. BILLING_NOT_ENABLED: 'BILLING_NOT_ENABLED', + // Claims payload is too large. CLAIMS_TOO_LARGE: 'CLAIMS_TOO_LARGE', + // Configuration being added already exists. CONFIGURATION_EXISTS: 'CONFIGURATION_EXISTS', + // Configuration not found. CONFIGURATION_NOT_FOUND: 'CONFIGURATION_NOT_FOUND', + // Provided credential has insufficient permissions. INSUFFICIENT_PERMISSION: 'INSUFFICIENT_PERMISSION', - INVALID_CLAIMS: 'INVALID_CLAIMS', + // Provided configuration has invalid fields. INVALID_CONFIG: 'INVALID_CONFIG', + // Provided configuration identifier is invalid. + INVALID_CONFIG_ID: 'INVALID_PROVIDER_ID', + // ActionCodeSettings missing continue URL. INVALID_CONTINUE_URI: 'INVALID_CONTINUE_URI', - INVALID_CREATION_TIME: 'INVALID_CREATION_TIME', + // Dynamic link domain in provided ActionCodeSettings is not authorized. INVALID_DYNAMIC_LINK_DOMAIN: 'INVALID_DYNAMIC_LINK_DOMAIN', - INVALID_EMAIL_VERIFIED: 'INVALID_EMAIL_VERIFIED', + // Hosting link domain in provided ActionCodeSettings is not owned by the current project. + INVALID_HOSTING_LINK_DOMAIN: 'INVALID_HOSTING_LINK_DOMAIN', + // uploadAccount provides an email that already exists. + DUPLICATE_EMAIL: 'EMAIL_ALREADY_EXISTS', + // uploadAccount provides a localId that already exists. + DUPLICATE_LOCAL_ID: 'UID_ALREADY_EXISTS', + // Request specified a multi-factor enrollment ID that already exists. + DUPLICATE_MFA_ENROLLMENT_ID: 'SECOND_FACTOR_UID_ALREADY_EXISTS', + // setAccountInfo email already exists. + EMAIL_EXISTS: 'EMAIL_ALREADY_EXISTS', + // /accounts:sendOobCode for password reset when user is not found. + EMAIL_NOT_FOUND: 'EMAIL_NOT_FOUND', + // Reserved claim name. + FORBIDDEN_CLAIM: 'FORBIDDEN_CLAIM', + // Invalid claims provided. + INVALID_CLAIMS: 'INVALID_CLAIMS', + // Invalid session cookie duration. + INVALID_DURATION: 'INVALID_SESSION_COOKIE_DURATION', + // Invalid email provided. INVALID_EMAIL: 'INVALID_EMAIL', - INVALID_ENROLLED_FACTORS: 'INVALID_ENROLLED_FACTORS', - INVALID_ENROLLMENT_TIME: 'INVALID_ENROLLMENT_TIME', - INVALID_HASH_ALGORITHM: 'INVALID_HASH_ALGORITHM', - INVALID_HASH_BLOCK_SIZE: 'INVALID_HASH_BLOCK_SIZE', - INVALID_HASH_DERIVED_KEY_LENGTH: 'INVALID_HASH_DERIVED_KEY_LENGTH', - INVALID_HASH_KEY: 'INVALID_HASH_KEY', - INVALID_HASH_MEMORY_COST: 'INVALID_HASH_MEMORY_COST', - INVALID_HASH_PARALLELIZATION: 'INVALID_HASH_PARALLELIZATION', - INVALID_HASH_ROUNDS: 'INVALID_HASH_ROUNDS', - INVALID_HASH_SALT_SEPARATOR: 'INVALID_HASH_SALT_SEPARATOR', - INVALID_LAST_SIGN_IN_TIME: 'INVALID_LAST_SIGN_IN_TIME', - INVALID_MFA_REQUIRED_KEY: 'INVALID_ARGUMENT', + // Invalid new email provided. + INVALID_NEW_EMAIL: 'INVALID_NEW_EMAIL', + // Invalid tenant display name. This can be thrown on CreateTenant and UpdateTenant. + INVALID_DISPLAY_NAME: 'INVALID_DISPLAY_NAME', + // Invalid ID token provided. + INVALID_ID_TOKEN: 'INVALID_ID_TOKEN', + // Invalid tenant/parent resource name. + INVALID_NAME: 'INVALID_NAME', + // OIDC configuration has an invalid OAuth client ID. INVALID_OAUTH_CLIENT_ID: 'INVALID_OAUTH_CLIENT_ID', - INVALID_PAGE_TOKEN: 'INVALID_PAGE_TOKEN', - INVALID_PASSWORD: 'INVALID_PASSWORD', - INVALID_PASSWORD_HASH: 'INVALID_PASSWORD_HASH', - INVALID_PASSWORD_SALT: 'INVALID_PASSWORD_SALT', + // Invalid page token. + INVALID_PAGE_SELECTION: 'INVALID_PAGE_TOKEN', + // Invalid phone number. INVALID_PHONE_NUMBER: 'INVALID_PHONE_NUMBER', - INVALID_PHOTO_URL: 'INVALID_PHOTO_URL', + // Invalid agent project. Either agent project doesn't exist or didn't enable multi-tenancy. INVALID_PROJECT_ID: 'INVALID_PROJECT_ID', - INVALID_PROVIDER_DATA: 'INVALID_PROVIDER_DATA', + // Invalid provider ID. INVALID_PROVIDER_ID: 'INVALID_PROVIDER_ID', - INVALID_PROVIDER_UID: 'INVALID_PROVIDER_UID', - INVALID_RECAPTCHA_ACTION: 'INVALID_RECAPTCHA_ACTION', - INVALID_RECAPTCHA_ENFORCEMENT_STATE: 'INVALID_RECAPTCHA_ENFORCEMENT_STATE', - INVALID_SESSION_COOKIE_DURATION: 'INVALID_SESSION_COOKIE_DURATION', - INVALID_TENANT_ID: 'INVALID_TENANT_ID', - INVALID_TENANT_TYPE: 'INVALID_TENANT_TYPE', + // Invalid service account. + INVALID_SERVICE_ACCOUNT: 'INVALID_SERVICE_ACCOUNT', + // Invalid testing phone number. INVALID_TESTING_PHONE_NUMBER: 'INVALID_TESTING_PHONE_NUMBER', + // Invalid tenant type. + INVALID_TENANT_TYPE: 'INVALID_TENANT_TYPE', + // Missing Android package name. MISSING_ANDROID_PACKAGE_NAME: 'MISSING_ANDROID_PACKAGE_NAME', - MISSING_CONTINUE_URI: 'MISSING_CONTINUE_URI', - MISSING_HASH_ALGORITHM: 'MISSING_HASH_ALGORITHM', + // Missing configuration. + MISSING_CONFIG: 'MISSING_CONFIG', + // Missing configuration identifier. + MISSING_CONFIG_ID: 'MISSING_PROVIDER_ID', + // Missing tenant display name: This can be thrown on CreateTenant and UpdateTenant. + MISSING_DISPLAY_NAME: 'MISSING_DISPLAY_NAME', + // Email is required for the specified action. For example a multi-factor user requires + // a verified email. + MISSING_EMAIL: 'MISSING_EMAIL', + // Missing iOS bundle ID. MISSING_IOS_BUNDLE_ID: 'MISSING_IOS_BUNDLE_ID', - MISSING_MFA_REQUIRED_KEY: 'INVALID_ARGUMENT', - MISSING_UID: 'MISSING_UID', + // Missing OIDC issuer. + MISSING_ISSUER: 'MISSING_ISSUER', + // No localId provided (deleteAccount missing localId). + MISSING_LOCAL_ID: 'MISSING_UID', + // OIDC configuration is missing an OAuth client ID. + MISSING_OAUTH_CLIENT_ID: 'MISSING_OAUTH_CLIENT_ID', + // Missing provider ID. + MISSING_PROVIDER_ID: 'MISSING_PROVIDER_ID', + // Missing SAML RP config. + MISSING_SAML_RELYING_PARTY_CONFIG: 'MISSING_SAML_RELYING_PARTY_CONFIG', + // Empty user list in uploadAccount. + MISSING_USER_ACCOUNT: 'MISSING_UID', + // Password auth disabled in console. OPERATION_NOT_ALLOWED: 'OPERATION_NOT_ALLOWED', - RECAPTCHA_NOT_ENABLED: 'RECAPTCHA_NOT_ENABLED', + // Provided credential has insufficient permissions. + PERMISSION_DENIED: 'INSUFFICIENT_PERMISSION', + // Phone number already exists. + PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_ALREADY_EXISTS', + // Project not found. + PROJECT_NOT_FOUND: 'PROJECT_NOT_FOUND', + // In multi-tenancy context: project creation quota exceeded. + QUOTA_EXCEEDED: 'QUOTA_EXCEEDED', + // Currently only 5 second factors can be set on the same user. SECOND_FACTOR_LIMIT_EXCEEDED: 'SECOND_FACTOR_LIMIT_EXCEEDED', - SECOND_FACTOR_UID_ALREADY_EXISTS: 'SECOND_FACTOR_UID_ALREADY_EXISTS', + // Tenant not found. TENANT_NOT_FOUND: 'TENANT_NOT_FOUND', + // Tenant ID mismatch. + TENANT_ID_MISMATCH: 'MISMATCHING_TENANT_ID', + // Token expired error. + TOKEN_EXPIRED: 'ID_TOKEN_EXPIRED', + // Continue URL provided in ActionCodeSettings has a domain that is not whitelisted. UNAUTHORIZED_DOMAIN: 'UNAUTHORIZED_DOMAIN', + // A multi-factor user requires a supported first factor. UNSUPPORTED_FIRST_FACTOR: 'UNSUPPORTED_FIRST_FACTOR', + // The request specified an unsupported type of second factor. UNSUPPORTED_SECOND_FACTOR: 'UNSUPPORTED_SECOND_FACTOR', + // Operation is not supported in a multi-tenant context. UNSUPPORTED_TENANT_OPERATION: 'UNSUPPORTED_TENANT_OPERATION', + // A verified email is required for the specified action. For example a multi-factor user + // requires a verified email. UNVERIFIED_EMAIL: 'UNVERIFIED_EMAIL', - USER_NOT_DISABLED: 'USER_NOT_DISABLED', - DUPLICATE_EMAIL: 'EMAIL_ALREADY_EXISTS', - DUPLICATE_LOCAL_ID: 'UID_ALREADY_EXISTS', - EMAIL_EXISTS: 'EMAIL_ALREADY_EXISTS', - EMAIL_NOT_FOUND: 'EMAIL_NOT_FOUND', - INVALID_TENANT_TYPE_CONVERSION: 'INVALID_ARGUMENT', - MISSING_EMAIL: 'INVALID_EMAIL', - MISSING_LOCAL_ID: 'MISSING_UID', - MISSING_PASSWORD: 'INVALID_PASSWORD', - PASSWORD_LOGIN_DISABLED: 'OPERATION_NOT_ALLOWED', - PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_ALREADY_EXISTS', - PROJECT_NOT_FOUND: 'PROJECT_NOT_FOUND', + // User on which action is to be performed is not found. USER_NOT_FOUND: 'USER_NOT_FOUND', + // User record is disabled. + USER_DISABLED: 'USER_DISABLED', + // Password provided is too weak. WEAK_PASSWORD: 'INVALID_PASSWORD', - TOKEN_EXPIRED: 'TOKEN_EXPIRED', - INVALID_ID_TOKEN: 'INVALID_ID_TOKEN', + // Unrecognized reCAPTCHA action. + INVALID_RECAPTCHA_ACTION: 'INVALID_RECAPTCHA_ACTION', + // Unrecognized reCAPTCHA enforcement state. + INVALID_RECAPTCHA_ENFORCEMENT_STATE: 'INVALID_RECAPTCHA_ENFORCEMENT_STATE', + // reCAPTCHA is not enabled for account defender. + RECAPTCHA_NOT_ENABLED: 'RECAPTCHA_NOT_ENABLED' }; /** From c34bcff7365b1e81ebc64c284918fe62b4264d37 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Fri, 1 May 2026 18:10:21 -0400 Subject: [PATCH 05/22] fix(auth): Fixed `INVALID_SERVICE_ACCOUNT` to map to a valid client code --- src/auth/error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/error.ts b/src/auth/error.ts index 61615dc5c5..79a787e9bb 100644 --- a/src/auth/error.ts +++ b/src/auth/error.ts @@ -291,7 +291,7 @@ const AUTH_SERVER_TO_CLIENT_CODE: Record = { // Invalid provider ID. INVALID_PROVIDER_ID: 'INVALID_PROVIDER_ID', // Invalid service account. - INVALID_SERVICE_ACCOUNT: 'INVALID_SERVICE_ACCOUNT', + INVALID_SERVICE_ACCOUNT: 'INVALID_CREDENTIAL', // Invalid testing phone number. INVALID_TESTING_PHONE_NUMBER: 'INVALID_TESTING_PHONE_NUMBER', // Invalid tenant type. From 47820b94f5fd73730f1464ff6b28e917e353f550 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 11:41:32 -0400 Subject: [PATCH 06/22] chore(pm): Refactor project management error logic --- etc/firebase-admin.project-management.api.md | 2 +- src/project-management/android-app.ts | 2 +- src/project-management/error.ts | 45 +++++++++++++++++++ src/project-management/index.ts | 2 +- src/project-management/ios-app.ts | 2 +- ...project-management-api-request-internal.ts | 5 ++- src/project-management/project-management.ts | 2 +- src/utils/error.ts | 26 ----------- .../project-management/android-app.spec.ts | 2 +- test/unit/project-management/ios-app.spec.ts | 2 +- .../project-management.spec.ts | 2 +- 11 files changed, 56 insertions(+), 36 deletions(-) create mode 100644 src/project-management/error.ts diff --git a/etc/firebase-admin.project-management.api.md b/etc/firebase-admin.project-management.api.md index d48dfc706e..8b36425b23 100644 --- a/etc/firebase-admin.project-management.api.md +++ b/etc/firebase-admin.project-management.api.md @@ -85,7 +85,7 @@ export class ProjectManagement { shaCertificate(shaHash: string): ShaCertificate; } -// @public (undocumented) +// @public export type ProjectManagementErrorCode = 'already-exists' | 'authentication-error' | 'internal-error' | 'invalid-argument' | 'invalid-project-id' | 'invalid-server-response' | 'not-found' | 'service-unavailable' | 'unknown-error'; // @public diff --git a/src/project-management/android-app.ts b/src/project-management/android-app.ts index c2013fb297..0d28d0afd5 100644 --- a/src/project-management/android-app.ts +++ b/src/project-management/android-app.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseProjectManagementError } from '../utils/error'; +import { FirebaseProjectManagementError } from './error'; import * as validator from '../utils/validator'; import { ProjectManagementRequestHandler, assertServerResponse } from './project-management-api-request-internal'; import { AppMetadata, AppPlatform } from './app-metadata'; diff --git a/src/project-management/error.ts b/src/project-management/error.ts new file mode 100644 index 0000000000..bc4052499c --- /dev/null +++ b/src/project-management/error.ts @@ -0,0 +1,45 @@ +/*! + * Copyright 2026 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. + */ + +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; + +/** + * Project Management client error codes. + */ +export type ProjectManagementErrorCode = + | 'already-exists' + | 'authentication-error' + | 'internal-error' + | 'invalid-argument' + | 'invalid-project-id' + | 'invalid-server-response' + | 'not-found' + | 'service-unavailable' + | 'unknown-error'; + +/** + * Firebase project management error code structure. This extends PrefixedFirebaseError. + */ +export class FirebaseProjectManagementError extends PrefixedFirebaseError { + /** + * @param info - The error code info. + * @param message - The error message. This will override the default message if provided. + */ + constructor(info: ErrorInfo, message?: string) { + // Override default message if custom message provided. + super('project-management', info.code, message || info.message, info.httpResponse, info.cause); + } +} diff --git a/src/project-management/index.ts b/src/project-management/index.ts index 87a6d570ce..d60da77f53 100644 --- a/src/project-management/index.ts +++ b/src/project-management/index.ts @@ -63,4 +63,4 @@ export function getProjectManagement(app?: App): ProjectManagement { return firebaseApp.getOrInitService('projectManagement', (app) => new ProjectManagement(app)); } -export { FirebaseProjectManagementError, ProjectManagementErrorCode } from '../utils/error'; +export { FirebaseProjectManagementError, ProjectManagementErrorCode } from './error'; diff --git a/src/project-management/ios-app.ts b/src/project-management/ios-app.ts index 082c05932a..b969110b09 100644 --- a/src/project-management/ios-app.ts +++ b/src/project-management/ios-app.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseProjectManagementError } from '../utils/error'; +import { FirebaseProjectManagementError } from './error'; import * as validator from '../utils/validator'; import { ProjectManagementRequestHandler, assertServerResponse } from './project-management-api-request-internal'; import { AppMetadata, AppPlatform } from './app-metadata'; diff --git a/src/project-management/project-management-api-request-internal.ts b/src/project-management/project-management-api-request-internal.ts index 0395ca4b56..588586a94e 100644 --- a/src/project-management/project-management-api-request-internal.ts +++ b/src/project-management/project-management-api-request-internal.ts @@ -20,8 +20,9 @@ import { AuthorizedHttpClient, RequestResponseError, HttpMethod, HttpRequestConfig, ExponentialBackoffPoller, } from '../utils/api-request'; import { - FirebaseProjectManagementError, ProjectManagementErrorCode, HttpResponse, toHttpResponse, -} from '../utils/error'; + FirebaseProjectManagementError, ProjectManagementErrorCode, +} from './error'; +import { HttpResponse, toHttpResponse } from '../utils/error'; import { getSdkVersion } from '../utils/index'; import * as validator from '../utils/validator'; import { ShaCertificate } from './android-app'; diff --git a/src/project-management/project-management.ts b/src/project-management/project-management.ts index b3517dc944..e3380b6527 100644 --- a/src/project-management/project-management.ts +++ b/src/project-management/project-management.ts @@ -15,7 +15,7 @@ */ import { App } from '../app'; -import { FirebaseProjectManagementError } from '../utils/error'; +import { FirebaseProjectManagementError } from './error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { AndroidApp, ShaCertificate } from './android-app'; diff --git a/src/utils/error.ts b/src/utils/error.ts index 849bc6254e..66885628f5 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -246,21 +246,6 @@ export class FirebaseFirestoreError extends FirebaseError { } } -/** - * Firebase project management error code structure. This extends PrefixedFirebaseError. - */ -export class FirebaseProjectManagementError extends PrefixedFirebaseError { - /** - * @param info - The error code info. - * @param message - The error message. This will override the default message if provided. - */ - constructor(info: ErrorInfo, message?: string) { - // Override default message if custom message provided. - super('project-management', info.code, message || info.message, info.httpResponse, info.cause); - - } -} - /** * App client error codes and their default messages. */ @@ -277,14 +262,3 @@ export class AppErrorCodes { public static NO_APP = 'no-app'; public static UNABLE_TO_PARSE_RESPONSE = 'unable-to-parse-response'; } - -export type ProjectManagementErrorCode = - 'already-exists' - | 'authentication-error' - | 'internal-error' - | 'invalid-argument' - | 'invalid-project-id' - | 'invalid-server-response' - | 'not-found' - | 'service-unavailable' - | 'unknown-error'; diff --git a/test/unit/project-management/android-app.spec.ts b/test/unit/project-management/android-app.spec.ts index 76e9df01df..1b8898258d 100644 --- a/test/unit/project-management/android-app.spec.ts +++ b/test/unit/project-management/android-app.spec.ts @@ -24,7 +24,7 @@ import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; import { deepCopy } from '../../../src/utils/deep-copy'; -import { FirebaseProjectManagementError } from '../../../src/utils/error'; +import { FirebaseProjectManagementError } from '../../../src/project-management/error'; import * as mocks from '../../resources/mocks'; import { AndroidApp, AndroidAppMetadata, AppPlatform, ShaCertificate, diff --git a/test/unit/project-management/ios-app.spec.ts b/test/unit/project-management/ios-app.spec.ts index e3a090b766..09e214a52b 100644 --- a/test/unit/project-management/ios-app.spec.ts +++ b/test/unit/project-management/ios-app.spec.ts @@ -24,7 +24,7 @@ import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; import { deepCopy } from '../../../src/utils/deep-copy'; -import { FirebaseProjectManagementError } from '../../../src/utils/error'; +import { FirebaseProjectManagementError } from '../../../src/project-management/error'; import * as mocks from '../../resources/mocks'; import { AppPlatform, IosApp, IosAppMetadata } from '../../../src/project-management/index'; diff --git a/test/unit/project-management/project-management.spec.ts b/test/unit/project-management/project-management.spec.ts index 23ec2b8584..25d1283f9b 100644 --- a/test/unit/project-management/project-management.spec.ts +++ b/test/unit/project-management/project-management.spec.ts @@ -23,7 +23,7 @@ import { FirebaseApp } from '../../../src/app/firebase-app'; import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; -import { FirebaseProjectManagementError } from '../../../src/utils/error'; +import { FirebaseProjectManagementError } from '../../../src/project-management/error'; import * as mocks from '../../resources/mocks'; import { AndroidApp, AppMetadata, AppPlatform, IosApp, ProjectManagement, From e00ebd3534ae1a9424d5f3bf0f335d2e7ad1c3d8 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 11:42:30 -0400 Subject: [PATCH 07/22] chore: Fix license year for new files --- src/auth/error.ts | 2 +- src/installations/error.ts | 2 +- src/instance-id/error.ts | 2 +- src/messaging/error.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/auth/error.ts b/src/auth/error.ts index 79a787e9bb..403bab5611 100644 --- a/src/auth/error.ts +++ b/src/auth/error.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2017 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/installations/error.ts b/src/installations/error.ts index 9d036427cf..a51258f3bd 100644 --- a/src/installations/error.ts +++ b/src/installations/error.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2021 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/instance-id/error.ts b/src/instance-id/error.ts index 86be3bc5c1..66329f322c 100644 --- a/src/instance-id/error.ts +++ b/src/instance-id/error.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2021 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/messaging/error.ts b/src/messaging/error.ts index 59ea9578c4..863e3c7a1b 100644 --- a/src/messaging/error.ts +++ b/src/messaging/error.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2021 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 9da4435b1f3ba9606ff9859ce854d3e47cbe7909 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 11:44:34 -0400 Subject: [PATCH 08/22] chore(rtdb): Refactor rtdb error logic --- src/database/database.ts | 3 ++- src/database/error.ts | 37 +++++++++++++++++++++++++++++++++++++ src/database/index.ts | 2 +- src/utils/error.ts | 20 -------------------- 4 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 src/database/error.ts diff --git a/src/database/database.ts b/src/database/database.ts index 34f1327c7a..d3e80aefeb 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -18,7 +18,8 @@ import { URL } from 'url'; import * as path from 'path'; import { FirebaseDatabase } from '@firebase/database-types'; -import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError, toHttpResponse } from '../utils/error'; +import { FirebaseDatabaseError } from './error'; +import { AppErrorCodes, FirebaseAppError, toHttpResponse } from '../utils/error'; import { Database as DatabaseImpl } from '@firebase/database-compat/standalone'; import { App } from '../app'; diff --git a/src/database/error.ts b/src/database/error.ts new file mode 100644 index 0000000000..c5babe3871 --- /dev/null +++ b/src/database/error.ts @@ -0,0 +1,37 @@ +/*! + * Copyright 2026 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. + */ + +import { FirebaseError, ErrorInfo } from '../utils/error'; + +/** + * Firebase Database error code structure. This extends FirebaseError. + */ +export class FirebaseDatabaseError extends FirebaseError { + /** + * @param info - The error code info. + * @param message - The error message. This will override the default + * message if provided. + */ + constructor(info: ErrorInfo, message?: string) { + // Override default message if custom message provided. + super({ + code: 'database/' + info.code, + message: message || info.message, + httpResponse: info.httpResponse, + cause: info.cause + }); + } +} diff --git a/src/database/index.ts b/src/database/index.ts index c078893d2c..747633f819 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -126,4 +126,4 @@ function getDatabaseInstance(options: { url?: string; app?: App }): Database { return dbService.getDatabase(options.url); } -export { FirebaseDatabaseError } from '../utils/error'; +export { FirebaseDatabaseError } from './error'; diff --git a/src/utils/error.ts b/src/utils/error.ts index 66885628f5..e9abceca8d 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -206,26 +206,6 @@ export class FirebaseAppError extends PrefixedFirebaseError { } } -/** - * Firebase Database error code structure. This extends FirebaseError. - */ -export class FirebaseDatabaseError extends FirebaseError { - /** - * @param info - The error code info. - * @param message - The error message. This will override the default - * message if provided. - */ - constructor(info: ErrorInfo, message?: string) { - // Override default message if custom message provided. - super({ - code: 'database/' + info.code, - message: message || info.message, - httpResponse: info.httpResponse, - cause: info.cause - }); - } -} - /** * Firebase Firestore error code structure. This extends FirebaseError. */ From e63d6f45834d603889b67669e6cbc8b2e0defd5e Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 12:08:36 -0400 Subject: [PATCH 09/22] chore(fs): Refactor firestore error logic --- src/firestore/error.ts | 37 +++++++++++++++++++++++++++++ src/firestore/firestore-internal.ts | 2 +- src/firestore/index.ts | 2 +- src/utils/error.ts | 20 ---------------- 4 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 src/firestore/error.ts diff --git a/src/firestore/error.ts b/src/firestore/error.ts new file mode 100644 index 0000000000..a9340877ea --- /dev/null +++ b/src/firestore/error.ts @@ -0,0 +1,37 @@ +/*! + * Copyright 2026 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. + */ + +import { FirebaseError, ErrorInfo } from '../utils/error'; + +/** + * Firebase Firestore error code structure. This extends FirebaseError. + */ +export class FirebaseFirestoreError extends FirebaseError { + /** + * @param info - The error code info. + * @param message - The error message. This will override the default + * message if provided. + */ + constructor(info: ErrorInfo, message?: string) { + // Override default message if custom message provided. + super({ + code: 'firestore/' + info.code, + message: message || info.message, + httpResponse: info.httpResponse, + cause: info.cause + }); + } +} diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts index e6b6544833..3642521943 100644 --- a/src/firestore/firestore-internal.ts +++ b/src/firestore/firestore-internal.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseFirestoreError } from '../utils/error'; +import { FirebaseFirestoreError } from './error'; import { ServiceAccountCredential, isApplicationDefault } from '../app/credential-internal'; import { Firestore, Settings } from '@google-cloud/firestore'; diff --git a/src/firestore/index.ts b/src/firestore/index.ts index c41fd5ea1a..5fe0c3f803 100644 --- a/src/firestore/index.ts +++ b/src/firestore/index.ts @@ -221,4 +221,4 @@ export function initializeFirestore( return firestoreService.initializeDatabase(databaseId, settings); } -export { FirebaseFirestoreError } from '../utils/error'; +export { FirebaseFirestoreError } from './error'; diff --git a/src/utils/error.ts b/src/utils/error.ts index e9abceca8d..e1bb341ed3 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -206,26 +206,6 @@ export class FirebaseAppError extends PrefixedFirebaseError { } } -/** - * Firebase Firestore error code structure. This extends FirebaseError. - */ -export class FirebaseFirestoreError extends FirebaseError { - /** - * @param info - The error code info. - * @param message - The error message. This will override the default - * message if provided. - */ - constructor(info: ErrorInfo, message?: string) { - // Override default message if custom message provided. - super({ - code: 'firestore/' + info.code, - message: message || info.message, - httpResponse: info.httpResponse, - cause: info.cause - }); - } -} - /** * App client error codes and their default messages. */ From 79b0b641bf53d765dccf57b1dc41f1b29e5509c1 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 13:09:03 -0400 Subject: [PATCH 10/22] chore(app): Refactor app error logic --- src/app/credential-internal.ts | 2 +- src/app/error.ts | 48 +++++++++++++++++++ src/app/firebase-app.ts | 2 +- src/app/index.ts | 3 +- src/app/lifecycle.ts | 2 +- src/database/database.ts | 3 +- .../extensions-api-client-internal.ts | 3 +- src/utils/api-request.ts | 3 +- src/utils/error.ts | 32 ------------- .../app-check-api-client-internal.spec.ts | 3 +- test/unit/app/credential-internal.spec.ts | 2 +- test/unit/app/firebase-app.spec.ts | 2 +- test/unit/extensions/extensions.spec.ts | 2 +- .../functions-api-client-internal.spec.ts | 3 +- .../machine-learning-api-client.spec.ts | 3 +- .../remote-config-api-client.spec.ts | 3 +- .../security-rules-api-client.spec.ts | 3 +- 17 files changed, 72 insertions(+), 47 deletions(-) create mode 100644 src/app/error.ts diff --git a/src/app/credential-internal.ts b/src/app/credential-internal.ts index a37b44ffaf..c6cb1b6fd6 100644 --- a/src/app/credential-internal.ts +++ b/src/app/credential-internal.ts @@ -20,7 +20,7 @@ import fs = require('fs'); import { Credentials as GoogleAuthCredentials, GoogleAuth, Compute, AnyAuthClient } from 'google-auth-library' import { Agent } from 'http'; import { Credential, GoogleOAuthAccessToken } from './credential'; -import { AppErrorCodes, FirebaseAppError } from '../utils/error'; +import { AppErrorCodes, FirebaseAppError } from './error'; import * as util from '../utils/validator'; const SCOPES = [ diff --git a/src/app/error.ts b/src/app/error.ts new file mode 100644 index 0000000000..6c7a361063 --- /dev/null +++ b/src/app/error.ts @@ -0,0 +1,48 @@ +/*! + * Copyright 2026 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. + */ + +import { ErrorInfo, PrefixedFirebaseError } from '../utils/error'; + +/** + * Firebase App error code structure. This extends PrefixedFirebaseError. + */ +export class FirebaseAppError extends PrefixedFirebaseError { + /** + * @param info - The error code info. + * @param message - The error message. This will override the default message if provided. + */ + constructor(info: ErrorInfo, message?: string) { + // Override default message if custom message provided. + super('app', info.code, message || info.message, info.httpResponse, info.cause); + } +} + +/** + * App client error codes and their default messages. + */ +export class AppErrorCodes { + public static APP_DELETED = 'app-deleted'; + public static DUPLICATE_APP = 'duplicate-app'; + public static INVALID_ARGUMENT = 'invalid-argument'; + public static INTERNAL_ERROR = 'internal-error'; + public static INVALID_APP_NAME = 'invalid-app-name'; + public static INVALID_APP_OPTIONS = 'invalid-app-options'; + public static INVALID_CREDENTIAL = 'invalid-credential'; + public static NETWORK_ERROR = 'network-error'; + public static NETWORK_TIMEOUT = 'network-timeout'; + public static NO_APP = 'no-app'; + public static UNABLE_TO_PARSE_RESPONSE = 'unable-to-parse-response'; +} diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index e89087bdbc..158aec31ab 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -21,7 +21,7 @@ import { Credential } from './credential'; import { getApplicationDefault } from './credential-internal'; import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; -import { AppErrorCodes, FirebaseAppError } from '../utils/error'; +import { AppErrorCodes, FirebaseAppError } from './error'; const TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1000; diff --git a/src/app/index.ts b/src/app/index.ts index 7c479cfee8..f441777fff 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -29,6 +29,7 @@ export { initializeApp, getApp, getApps, deleteApp } from './lifecycle'; export { Credential, ServiceAccount, GoogleOAuthAccessToken } from './credential'; export { applicationDefault, cert, refreshToken } from './credential-factory'; -export { FirebaseAppError, AppErrorCodes, FirebaseError, ErrorInfo, HttpResponse } from '../utils/error'; +export { FirebaseError, ErrorInfo, HttpResponse } from '../utils/error'; +export { FirebaseAppError, AppErrorCodes } from './error'; export const SDK_VERSION = getSdkVersion(); diff --git a/src/app/lifecycle.ts b/src/app/lifecycle.ts index e660e84bb8..606a8f5825 100644 --- a/src/app/lifecycle.ts +++ b/src/app/lifecycle.ts @@ -18,7 +18,7 @@ import fs = require('fs'); import * as validator from '../utils/validator'; -import { AppErrorCodes, FirebaseAppError } from '../utils/error'; +import { AppErrorCodes, FirebaseAppError } from './error'; import { App, AppOptions } from './core'; import { getApplicationDefault } from './credential-internal'; import { FirebaseApp } from './firebase-app'; diff --git a/src/database/database.ts b/src/database/database.ts index d3e80aefeb..9b3d0defd6 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -19,7 +19,8 @@ import * as path from 'path'; import { FirebaseDatabase } from '@firebase/database-types'; import { FirebaseDatabaseError } from './error'; -import { AppErrorCodes, FirebaseAppError, toHttpResponse } from '../utils/error'; +import { AppErrorCodes, FirebaseAppError } from '../app/error'; +import { toHttpResponse } from '../utils/error'; import { Database as DatabaseImpl } from '@firebase/database-compat/standalone'; import { App } from '../app'; diff --git a/src/extensions/extensions-api-client-internal.ts b/src/extensions/extensions-api-client-internal.ts index 5b33d0448e..d997a4334d 100644 --- a/src/extensions/extensions-api-client-internal.ts +++ b/src/extensions/extensions-api-client-internal.ts @@ -18,7 +18,8 @@ import { App } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { AuthorizedHttpClient, HttpClient, RequestResponseError, HttpRequestConfig } from '../utils/api-request'; -import { FirebaseAppError, PrefixedFirebaseError, ErrorInfo, toHttpResponse } from '../utils/error'; +import { PrefixedFirebaseError, ErrorInfo, toHttpResponse } from '../utils/error'; +import { FirebaseAppError } from '../app/error'; import * as validator from '../utils/validator'; import * as utils from '../utils'; diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index ffb8528209..e37891d5d2 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -16,7 +16,8 @@ */ import { FirebaseApp } from '../app/firebase-app'; -import { AppErrorCodes, FirebaseAppError, toHttpResponse } from './error'; +import { toHttpResponse } from './error'; +import { AppErrorCodes, FirebaseAppError } from '../app/error'; import * as validator from './validator'; import http = require('http'); diff --git a/src/utils/error.ts b/src/utils/error.ts index e1bb341ed3..de4d65c4be 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -190,35 +190,3 @@ export class PrefixedFirebaseError extends FirebaseError { return `${this.codePrefix}/${code}` === this.code; } } - -/** - * Firebase App error code structure. This extends PrefixedFirebaseError. - */ -export class FirebaseAppError extends PrefixedFirebaseError { - /** - * @param info - The error code info. - * @param message - The error message. This will override the default message if provided. - */ - constructor(info: ErrorInfo, message?: string) { - // Override default message if custom message provided. - super('app', info.code, message || info.message, info.httpResponse, info.cause); - - } -} - -/** - * App client error codes and their default messages. - */ -export class AppErrorCodes { - public static APP_DELETED = 'app-deleted'; - public static DUPLICATE_APP = 'duplicate-app'; - public static INVALID_ARGUMENT = 'invalid-argument'; - public static INTERNAL_ERROR = 'internal-error'; - public static INVALID_APP_NAME = 'invalid-app-name'; - public static INVALID_APP_OPTIONS = 'invalid-app-options'; - public static INVALID_CREDENTIAL = 'invalid-credential'; - public static NETWORK_ERROR = 'network-error'; - public static NETWORK_TIMEOUT = 'network-timeout'; - public static NO_APP = 'no-app'; - public static UNABLE_TO_PARSE_RESPONSE = 'unable-to-parse-response'; -} diff --git a/test/unit/app-check/app-check-api-client-internal.spec.ts b/test/unit/app-check/app-check-api-client-internal.spec.ts index d25c87d0a9..dcdec83ecc 100644 --- a/test/unit/app-check/app-check-api-client-internal.spec.ts +++ b/test/unit/app-check/app-check-api-client-internal.spec.ts @@ -27,7 +27,8 @@ import { getMetricsHeader, getSdkVersion } from '../../../src/utils'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { AppCheckApiClient, FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; -import { FirebaseAppError, toHttpResponse } from '../../../src/utils/error'; +import { toHttpResponse } from '../../../src/utils/error'; +import { FirebaseAppError } from '../../../src/app/error'; import { deepCopy } from '../../../src/utils/deep-copy'; const expect = chai.expect; diff --git a/test/unit/app/credential-internal.spec.ts b/test/unit/app/credential-internal.spec.ts index d1d7d89549..2d56a74dd0 100644 --- a/test/unit/app/credential-internal.spec.ts +++ b/test/unit/app/credential-internal.spec.ts @@ -37,7 +37,7 @@ import { getApplicationDefault, isApplicationDefault, ImpersonatedServiceAccountCredential, ApplicationDefaultCredential } from '../../../src/app/credential-internal'; import { deepCopy } from '../../../src/utils/deep-copy'; -import { FirebaseAppError } from '../../../src/utils/error'; +import { FirebaseAppError } from '../../../src/app/error'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/app/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts index fa55b91fa9..2192e84962 100644 --- a/test/unit/app/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -35,7 +35,7 @@ import { auth, messaging, machineLearning, storage, firestore, database, instanceId, installations, projectManagement, securityRules, remoteConfig, appCheck, } from '../../../src/firebase-namespace-api'; -import { FirebaseAppError, AppErrorCodes } from '../../../src/utils/error'; +import { FirebaseAppError, AppErrorCodes } from '../../../src/app/error'; import Auth = auth.Auth; import Database = database.Database; diff --git a/test/unit/extensions/extensions.spec.ts b/test/unit/extensions/extensions.spec.ts index 6f2d75a2de..d17d2da09b 100644 --- a/test/unit/extensions/extensions.spec.ts +++ b/test/unit/extensions/extensions.spec.ts @@ -22,7 +22,7 @@ import * as mocks from '../../resources/mocks'; import * as utils from '../utils'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { Extensions } from '../../../src/extensions/extensions'; -import { FirebaseAppError } from '../../../src/utils/error'; +import { FirebaseAppError } from '../../../src/app/error'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; import { SettableProcessingState } from '../../../src/extensions/extensions-api'; import { FirebaseExtensionsError } from '../../../src/extensions/extensions-api-client-internal'; diff --git a/test/unit/functions/functions-api-client-internal.spec.ts b/test/unit/functions/functions-api-client-internal.spec.ts index 1193615330..9804d66c42 100644 --- a/test/unit/functions/functions-api-client-internal.spec.ts +++ b/test/unit/functions/functions-api-client-internal.spec.ts @@ -27,7 +27,8 @@ import { getSdkVersion, getMetricsHeader } from '../../../src/utils'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { FirebaseFunctionsError, FunctionsApiClient, Task } from '../../../src/functions/functions-api-client-internal'; import { HttpClient } from '../../../src/utils/api-request'; -import { FirebaseAppError, toHttpResponse } from '../../../src/utils/error'; +import { toHttpResponse } from '../../../src/utils/error'; +import { FirebaseAppError } from '../../../src/app/error'; import { deepCopy } from '../../../src/utils/deep-copy'; import { EMULATED_SERVICE_ACCOUNT_DEFAULT } from '../../../src/functions/functions-api-client-internal'; diff --git a/test/unit/machine-learning/machine-learning-api-client.spec.ts b/test/unit/machine-learning/machine-learning-api-client.spec.ts index 462a2a3087..ccc49179bd 100644 --- a/test/unit/machine-learning/machine-learning-api-client.spec.ts +++ b/test/unit/machine-learning/machine-learning-api-client.spec.ts @@ -23,7 +23,8 @@ import { FirebaseMachineLearningError } from '../../../src/machine-learning/mach import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { FirebaseAppError, toHttpResponse } from '../../../src/utils/error'; +import { toHttpResponse } from '../../../src/utils/error'; +import { FirebaseAppError } from '../../../src/app/error'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { getMetricsHeader, getSdkVersion } from '../../../src/utils/index'; import { MachineLearningApiClient } from '../../../src/machine-learning/machine-learning-api-client'; diff --git a/test/unit/remote-config/remote-config-api-client.spec.ts b/test/unit/remote-config/remote-config-api-client.spec.ts index f87c7f22f2..20de3d4da1 100644 --- a/test/unit/remote-config/remote-config-api-client.spec.ts +++ b/test/unit/remote-config/remote-config-api-client.spec.ts @@ -26,7 +26,8 @@ import { import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { FirebaseAppError, toHttpResponse } from '../../../src/utils/error'; +import { toHttpResponse } from '../../../src/utils/error'; +import { FirebaseAppError } from '../../../src/app/error'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { deepCopy } from '../../../src/utils/deep-copy'; import { getMetricsHeader, getSdkVersion } from '../../../src/utils/index'; diff --git a/test/unit/security-rules/security-rules-api-client.spec.ts b/test/unit/security-rules/security-rules-api-client.spec.ts index f8299ae31f..304f18070e 100644 --- a/test/unit/security-rules/security-rules-api-client.spec.ts +++ b/test/unit/security-rules/security-rules-api-client.spec.ts @@ -24,7 +24,8 @@ import { FirebaseSecurityRulesError } from '../../../src/security-rules/security import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { FirebaseAppError, toHttpResponse } from '../../../src/utils/error'; +import { toHttpResponse } from '../../../src/utils/error'; +import { FirebaseAppError } from '../../../src/app/error'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { getSdkVersion, getMetricsHeader } from '../../../src/utils/index'; From ce65c3fd3a603378423e626d6f9636d290871b91 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 13:18:12 -0400 Subject: [PATCH 11/22] chore(security-rules): Refactor security rules error logic --- src/security-rules/{security-rules-internal.ts => error.ts} | 0 src/security-rules/index.ts | 2 +- src/security-rules/security-rules-api-client-internal.ts | 2 +- src/security-rules/security-rules.ts | 2 +- test/unit/security-rules/security-rules-api-client.spec.ts | 2 +- test/unit/security-rules/security-rules.spec.ts | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename src/security-rules/{security-rules-internal.ts => error.ts} (100%) diff --git a/src/security-rules/security-rules-internal.ts b/src/security-rules/error.ts similarity index 100% rename from src/security-rules/security-rules-internal.ts rename to src/security-rules/error.ts diff --git a/src/security-rules/index.ts b/src/security-rules/index.ts index c7b6f59171..4da1c7ea9d 100644 --- a/src/security-rules/index.ts +++ b/src/security-rules/index.ts @@ -66,4 +66,4 @@ export function getSecurityRules(app?: App): SecurityRules { return firebaseApp.getOrInitService('securityRules', (app) => new SecurityRules(app)); } -export { FirebaseSecurityRulesError, SecurityRulesErrorCode } from './security-rules-internal'; \ No newline at end of file +export { FirebaseSecurityRulesError, SecurityRulesErrorCode } from './error'; \ No newline at end of file diff --git a/src/security-rules/security-rules-api-client-internal.ts b/src/security-rules/security-rules-api-client-internal.ts index e95c6480d5..8f447a9593 100644 --- a/src/security-rules/security-rules-api-client-internal.ts +++ b/src/security-rules/security-rules-api-client-internal.ts @@ -16,7 +16,7 @@ import { HttpRequestConfig, HttpClient, RequestResponseError, AuthorizedHttpClient } from '../utils/api-request'; import { PrefixedFirebaseError, toHttpResponse } from '../utils/error'; -import { FirebaseSecurityRulesError, SecurityRulesErrorCode } from './security-rules-internal'; +import { FirebaseSecurityRulesError, SecurityRulesErrorCode } from './error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { FirebaseApp } from '../app/firebase-app'; diff --git a/src/security-rules/security-rules.ts b/src/security-rules/security-rules.ts index c7dfbfc134..9f30732299 100644 --- a/src/security-rules/security-rules.ts +++ b/src/security-rules/security-rules.ts @@ -19,7 +19,7 @@ import * as validator from '../utils/validator'; import { SecurityRulesApiClient, RulesetResponse, RulesetContent, ListRulesetsResponse, } from './security-rules-api-client-internal'; -import { FirebaseSecurityRulesError } from './security-rules-internal'; +import { FirebaseSecurityRulesError } from './error'; /** * A source file containing some Firebase security rules. The content includes raw diff --git a/test/unit/security-rules/security-rules-api-client.spec.ts b/test/unit/security-rules/security-rules-api-client.spec.ts index 304f18070e..d0dcf69b09 100644 --- a/test/unit/security-rules/security-rules-api-client.spec.ts +++ b/test/unit/security-rules/security-rules-api-client.spec.ts @@ -20,7 +20,7 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; import { SecurityRulesApiClient, RulesetContent } from '../../../src/security-rules/security-rules-api-client-internal'; -import { FirebaseSecurityRulesError } from '../../../src/security-rules/security-rules-internal'; +import { FirebaseSecurityRulesError } from '../../../src/security-rules/error'; import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; diff --git a/test/unit/security-rules/security-rules.spec.ts b/test/unit/security-rules/security-rules.spec.ts index 06aaf1d970..7b683ce96d 100644 --- a/test/unit/security-rules/security-rules.spec.ts +++ b/test/unit/security-rules/security-rules.spec.ts @@ -23,7 +23,7 @@ import { SecurityRules } from '../../../src/security-rules/index'; import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; import { SecurityRulesApiClient, RulesetContent } from '../../../src/security-rules/security-rules-api-client-internal'; -import { FirebaseSecurityRulesError } from '../../../src/security-rules/security-rules-internal'; +import { FirebaseSecurityRulesError } from '../../../src/security-rules/error'; import { deepCopy } from '../../../src/utils/deep-copy'; const expect = chai.expect; From 00e52d14afe6c8a4c30397ce3ee5bbde338899b2 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 14:53:57 -0400 Subject: [PATCH 12/22] chore(app-check): Refactor app check error logic --- .../app-check-api-client-internal.ts | 42 +------------- src/app-check/app-check.ts | 3 +- src/app-check/error.ts | 56 +++++++++++++++++++ src/app-check/index.ts | 2 +- src/app-check/token-generator.ts | 2 +- src/app-check/token-verifier.ts | 2 +- .../app-check-api-client-internal.spec.ts | 3 +- test/unit/app-check/app-check.spec.ts | 3 +- test/unit/app-check/token-generator.spec.ts | 2 +- 9 files changed, 68 insertions(+), 47 deletions(-) create mode 100644 src/app-check/error.ts diff --git a/src/app-check/app-check-api-client-internal.ts b/src/app-check/app-check-api-client-internal.ts index 246f65dc93..5dd2f17333 100644 --- a/src/app-check/app-check-api-client-internal.ts +++ b/src/app-check/app-check-api-client-internal.ts @@ -20,7 +20,8 @@ import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, RequestResponseError, AuthorizedHttpClient, RequestResponse } from '../utils/api-request'; -import { PrefixedFirebaseError, ErrorInfo, toHttpResponse } from '../utils/error'; +import { PrefixedFirebaseError, toHttpResponse } from '../utils/error'; +import { FirebaseAppCheckError, AppCheckErrorCode, APP_CHECK_ERROR_CODE_MAPPING } from './error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { AppCheckToken } from './app-check-api'; @@ -237,42 +238,3 @@ interface AppCheckApiError { message?: string; status?: string; } - -export const APP_CHECK_ERROR_CODE_MAPPING: { [key: string]: AppCheckErrorCode; } = { - ABORTED: 'aborted', - INVALID_ARGUMENT: 'invalid-argument', - INVALID_CREDENTIAL: 'invalid-credential', - INTERNAL: 'internal-error', - PERMISSION_DENIED: 'permission-denied', - UNAUTHENTICATED: 'unauthenticated', - NOT_FOUND: 'not-found', - UNKNOWN: 'unknown-error', -}; - -/** - * App Check client error codes and their default messages. - */ -export type AppCheckErrorCode = - 'aborted' - | 'invalid-argument' - | 'invalid-credential' - | 'internal-error' - | 'permission-denied' - | 'unauthenticated' - | 'not-found' - | 'app-check-token-expired' - | 'unknown-error'; - -/** - * Firebase App Check error type. This extends PrefixedFirebaseError. - */ -export class FirebaseAppCheckError extends PrefixedFirebaseError { - /** - * @param info - The error code info. - * @param message - The error message. If provided, this will override the default message. - */ - constructor(info: ErrorInfo, message?: string) { - super('app-check', info.code, message || info.message, info.httpResponse, info.cause); - - } -} diff --git a/src/app-check/app-check.ts b/src/app-check/app-check.ts index ed5aa57c73..99a2b010d3 100644 --- a/src/app-check/app-check.ts +++ b/src/app-check/app-check.ts @@ -18,7 +18,8 @@ import * as validator from '../utils/validator'; import { App } from '../app'; -import { AppCheckApiClient, FirebaseAppCheckError } from './app-check-api-client-internal'; +import { AppCheckApiClient } from './app-check-api-client-internal'; +import { FirebaseAppCheckError } from './error'; import { appCheckErrorFromCryptoSignerError, AppCheckTokenGenerator, } from './token-generator'; diff --git a/src/app-check/error.ts b/src/app-check/error.ts new file mode 100644 index 0000000000..eedea862a4 --- /dev/null +++ b/src/app-check/error.ts @@ -0,0 +1,56 @@ +/*! + * Copyright 2026 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. + */ + +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; + +/** @const {Record} App Check server to client error code mapping. */ +export const APP_CHECK_ERROR_CODE_MAPPING: Record = { + ABORTED: 'aborted', + INVALID_ARGUMENT: 'invalid-argument', + INVALID_CREDENTIAL: 'invalid-credential', + INTERNAL: 'internal-error', + PERMISSION_DENIED: 'permission-denied', + UNAUTHENTICATED: 'unauthenticated', + NOT_FOUND: 'not-found', + UNKNOWN: 'unknown-error', +}; + +/** + * App Check client error codes and their default messages. + */ +export type AppCheckErrorCode = + | 'aborted' + | 'invalid-argument' + | 'invalid-credential' + | 'internal-error' + | 'permission-denied' + | 'unauthenticated' + | 'not-found' + | 'app-check-token-expired' + | 'unknown-error'; + +/** + * Firebase App Check error type. This extends PrefixedFirebaseError. + */ +export class FirebaseAppCheckError extends PrefixedFirebaseError { + /** + * @param info - The error code info. + * @param message - The error message. If provided, this will override the default message. + */ + constructor(info: ErrorInfo, message?: string) { + super('app-check', info.code, message || info.message, info.httpResponse, info.cause); + } +} diff --git a/src/app-check/index.ts b/src/app-check/index.ts index c505bebafd..4d9605f132 100644 --- a/src/app-check/index.ts +++ b/src/app-check/index.ts @@ -69,4 +69,4 @@ export function getAppCheck(app?: App): AppCheck { return firebaseApp.getOrInitService('appCheck', (app) => new AppCheck(app)); } -export { FirebaseAppCheckError, AppCheckErrorCode } from './app-check-api-client-internal'; \ No newline at end of file +export { FirebaseAppCheckError, AppCheckErrorCode } from './error'; \ No newline at end of file diff --git a/src/app-check/token-generator.ts b/src/app-check/token-generator.ts index b608dd459d..08bf9a7579 100644 --- a/src/app-check/token-generator.ts +++ b/src/app-check/token-generator.ts @@ -22,7 +22,7 @@ import { FirebaseAppCheckError, AppCheckErrorCode, APP_CHECK_ERROR_CODE_MAPPING, -} from './app-check-api-client-internal'; +} from './error'; import { AppCheckTokenOptions } from './app-check-api'; import { RequestResponseError } from '../utils/api-request'; import { toHttpResponse } from '../utils/error'; diff --git a/src/app-check/token-verifier.ts b/src/app-check/token-verifier.ts index 270ba154bf..e1c5d58504 100644 --- a/src/app-check/token-verifier.ts +++ b/src/app-check/token-verifier.ts @@ -16,7 +16,7 @@ import * as validator from '../utils/validator'; import * as util from '../utils/index'; -import { FirebaseAppCheckError } from './app-check-api-client-internal'; +import { FirebaseAppCheckError } from './error'; import { ALGORITHM_RS256, DecodedToken, decodeJwt, JwtError, JwtErrorCode, PublicKeySignatureVerifier, SignatureVerifier diff --git a/test/unit/app-check/app-check-api-client-internal.spec.ts b/test/unit/app-check/app-check-api-client-internal.spec.ts index dcdec83ecc..e9e2bfad8b 100644 --- a/test/unit/app-check/app-check-api-client-internal.spec.ts +++ b/test/unit/app-check/app-check-api-client-internal.spec.ts @@ -26,7 +26,8 @@ import * as mocks from '../../resources/mocks'; import { getMetricsHeader, getSdkVersion } from '../../../src/utils'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { AppCheckApiClient, FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; +import { AppCheckApiClient } from '../../../src/app-check/app-check-api-client-internal'; +import { FirebaseAppCheckError } from '../../../src/app-check/error'; import { toHttpResponse } from '../../../src/utils/error'; import { FirebaseAppError } from '../../../src/app/error'; import { deepCopy } from '../../../src/utils/deep-copy'; diff --git a/test/unit/app-check/app-check.spec.ts b/test/unit/app-check/app-check.spec.ts index 51f2f8b016..0c42fd61d2 100644 --- a/test/unit/app-check/app-check.spec.ts +++ b/test/unit/app-check/app-check.spec.ts @@ -24,7 +24,8 @@ import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { AppCheck } from '../../../src/app-check/index'; -import { AppCheckApiClient, FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; +import { AppCheckApiClient } from '../../../src/app-check/app-check-api-client-internal'; +import { FirebaseAppCheckError } from '../../../src/app-check/error'; import { AppCheckTokenGenerator } from '../../../src/app-check/token-generator'; import { HttpClient } from '../../../src/utils/api-request'; import { ServiceAccountSigner } from '../../../src/utils/crypto-signer'; diff --git a/test/unit/app-check/token-generator.spec.ts b/test/unit/app-check/token-generator.spec.ts index d629e62e90..edfc57a3e0 100644 --- a/test/unit/app-check/token-generator.spec.ts +++ b/test/unit/app-check/token-generator.spec.ts @@ -33,7 +33,7 @@ import { CryptoSignerError, CryptoSignerErrorCode, ServiceAccountSigner } from '../../../src/utils/crypto-signer'; import { ServiceAccountCredential } from '../../../src/app/credential-internal'; -import { FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; +import { FirebaseAppCheckError } from '../../../src/app-check/error'; import * as utils from '../utils'; chai.should(); From bdfd6a27c9e197c07760b7d56537724bab67509b Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 15:03:24 -0400 Subject: [PATCH 13/22] chore(remote-config): Refactor remote config error logic --- src/remote-config/error.ts | 61 +++++++++++++++++++ src/remote-config/index.ts | 2 +- .../remote-config-api-client-internal.ts | 46 +------------- src/remote-config/remote-config.ts | 3 +- .../remote-config-api-client.spec.ts | 6 +- test/unit/remote-config/remote-config.spec.ts | 6 +- 6 files changed, 70 insertions(+), 54 deletions(-) create mode 100644 src/remote-config/error.ts diff --git a/src/remote-config/error.ts b/src/remote-config/error.ts new file mode 100644 index 0000000000..17c303dea0 --- /dev/null +++ b/src/remote-config/error.ts @@ -0,0 +1,61 @@ +/*! + * Copyright 2026 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. + */ + +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; + +/** @const {Record} Remote Config server to client error code mapping. */ +export const ERROR_CODE_MAPPING: Record = { + ABORTED: 'aborted', + ALREADY_EXISTS: 'already-exists', + INVALID_ARGUMENT: 'invalid-argument', + INTERNAL: 'internal-error', + FAILED_PRECONDITION: 'failed-precondition', + NOT_FOUND: 'not-found', + OUT_OF_RANGE: 'out-of-range', + PERMISSION_DENIED: 'permission-denied', + RESOURCE_EXHAUSTED: 'resource-exhausted', + UNAUTHENTICATED: 'unauthenticated', + UNKNOWN: 'unknown-error', +}; + +/** + * Remote Config client error codes and their default messages. + */ +export type RemoteConfigErrorCode = + | 'aborted' + | 'already-exists' + | 'failed-precondition' + | 'internal-error' + | 'invalid-argument' + | 'not-found' + | 'out-of-range' + | 'permission-denied' + | 'resource-exhausted' + | 'unauthenticated' + | 'unknown-error'; + +/** + * Firebase Remote Config error code structure. This extends PrefixedFirebaseError. + */ +export class FirebaseRemoteConfigError extends PrefixedFirebaseError { + /** + * @param info - The error code info. + * @param message - The error message. If provided, this will override the default message. + */ + constructor(info: ErrorInfo, message?: string) { + super('remote-config', info.code, message || info.message, info.httpResponse, info.cause); + } +} diff --git a/src/remote-config/index.ts b/src/remote-config/index.ts index 61d903e70c..94a74bbffe 100644 --- a/src/remote-config/index.ts +++ b/src/remote-config/index.ts @@ -107,4 +107,4 @@ export function getRemoteConfig(app?: App): RemoteConfig { return firebaseApp.getOrInitService('remoteConfig', (app) => new RemoteConfig(app)); } -export { FirebaseRemoteConfigError, RemoteConfigErrorCode } from './remote-config-api-client-internal'; \ No newline at end of file +export { FirebaseRemoteConfigError, RemoteConfigErrorCode } from './error'; \ No newline at end of file diff --git a/src/remote-config/remote-config-api-client-internal.ts b/src/remote-config/remote-config-api-client-internal.ts index 7b88dea1f6..4898491096 100644 --- a/src/remote-config/remote-config-api-client-internal.ts +++ b/src/remote-config/remote-config-api-client-internal.ts @@ -19,7 +19,8 @@ import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, RequestResponseError, AuthorizedHttpClient, RequestResponse } from '../utils/api-request'; -import { PrefixedFirebaseError, ErrorInfo, toHttpResponse } from '../utils/error'; +import { PrefixedFirebaseError, toHttpResponse } from '../utils/error'; +import { FirebaseRemoteConfigError, RemoteConfigErrorCode, ERROR_CODE_MAPPING } from './error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; @@ -479,46 +480,3 @@ interface RemoteConfigApiError { message?: string; status?: string; } - -const ERROR_CODE_MAPPING: { [key: string]: RemoteConfigErrorCode; } = { - ABORTED: 'aborted', - ALREADY_EXISTS: 'already-exists', - INVALID_ARGUMENT: 'invalid-argument', - INTERNAL: 'internal-error', - FAILED_PRECONDITION: 'failed-precondition', - NOT_FOUND: 'not-found', - OUT_OF_RANGE: 'out-of-range', - PERMISSION_DENIED: 'permission-denied', - RESOURCE_EXHAUSTED: 'resource-exhausted', - UNAUTHENTICATED: 'unauthenticated', - UNKNOWN: 'unknown-error', -}; - -/** - * Remote Config client error codes and their default messages. - */ -export type RemoteConfigErrorCode = - 'aborted' - | 'already-exists' - | 'failed-precondition' - | 'internal-error' - | 'invalid-argument' - | 'not-found' - | 'out-of-range' - | 'permission-denied' - | 'resource-exhausted' - | 'unauthenticated' - | 'unknown-error'; - -/** - * Firebase Remote Config error code structure. This extends PrefixedFirebaseError. - */ -export class FirebaseRemoteConfigError extends PrefixedFirebaseError { - /** - * @param info - The error code info. - * @param message - The error message. If provided, this will override the default message. - */ - constructor(info: ErrorInfo, message?: string) { - super('remote-config', info.code, message || info.message, info.httpResponse, info.cause); - } -} diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index 47e5bc64d2..a90913e100 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -17,7 +17,8 @@ import { App } from '../app'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; -import { FirebaseRemoteConfigError, RemoteConfigApiClient } from './remote-config-api-client-internal'; +import { RemoteConfigApiClient } from './remote-config-api-client-internal'; +import { FirebaseRemoteConfigError } from './error'; import { ConditionEvaluator } from './condition-evaluator-internal'; import { ValueImpl } from './internal/value-impl'; import { diff --git a/test/unit/remote-config/remote-config-api-client.spec.ts b/test/unit/remote-config/remote-config-api-client.spec.ts index 20de3d4da1..f515a76130 100644 --- a/test/unit/remote-config/remote-config-api-client.spec.ts +++ b/test/unit/remote-config/remote-config-api-client.spec.ts @@ -19,10 +19,8 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { - FirebaseRemoteConfigError, - RemoteConfigApiClient -} from '../../../src/remote-config/remote-config-api-client-internal'; +import { RemoteConfigApiClient } from '../../../src/remote-config/remote-config-api-client-internal'; +import { FirebaseRemoteConfigError } from '../../../src/remote-config/error'; import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; diff --git a/test/unit/remote-config/remote-config.spec.ts b/test/unit/remote-config/remote-config.spec.ts index 34145c2bdc..7c923d8ec1 100644 --- a/test/unit/remote-config/remote-config.spec.ts +++ b/test/unit/remote-config/remote-config.spec.ts @@ -33,10 +33,8 @@ import { } from '../../../src/remote-config/index'; import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; -import { - FirebaseRemoteConfigError, - RemoteConfigApiClient -} from '../../../src/remote-config/remote-config-api-client-internal'; +import { RemoteConfigApiClient } from '../../../src/remote-config/remote-config-api-client-internal'; +import { FirebaseRemoteConfigError } from '../../../src/remote-config/error'; import { deepCopy } from '../../../src/utils/deep-copy'; import { NamedCondition, ServerTemplate, ServerTemplateData, Version From 6fdb6e40c301b2aaab5e4f243813416c27ad90f5 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 15:33:48 -0400 Subject: [PATCH 14/22] chore(functions): Refactor functions error logic --- src/functions/error.ts | 58 +++++++++++++++++++ .../functions-api-client-internal.ts | 44 +------------- src/functions/functions.ts | 3 +- src/functions/index.ts | 2 +- .../functions-api-client-internal.spec.ts | 3 +- test/unit/functions/functions.spec.ts | 3 +- 6 files changed, 67 insertions(+), 46 deletions(-) create mode 100644 src/functions/error.ts diff --git a/src/functions/error.ts b/src/functions/error.ts new file mode 100644 index 0000000000..6c81172dfe --- /dev/null +++ b/src/functions/error.ts @@ -0,0 +1,58 @@ +/*! + * Copyright 2026 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. + */ + +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; + +/** @const {Record} Functions server to client error code mapping. */ +export const FUNCTIONS_ERROR_CODE_MAPPING: Record = { + ABORTED: 'aborted', + INVALID_ARGUMENT: 'invalid-argument', + INVALID_CREDENTIAL: 'invalid-credential', + INTERNAL: 'internal-error', + FAILED_PRECONDITION: 'failed-precondition', + PERMISSION_DENIED: 'permission-denied', + UNAUTHENTICATED: 'unauthenticated', + NOT_FOUND: 'not-found', + UNKNOWN: 'unknown-error', +}; + +/** + * Functions client error codes and their default messages. + */ +export type FunctionsErrorCode = + | 'aborted' + | 'invalid-argument' + | 'invalid-credential' + | 'internal-error' + | 'failed-precondition' + | 'permission-denied' + | 'unauthenticated' + | 'not-found' + | 'unknown-error' + | 'task-already-exists'; + +/** + * Firebase Functions error code structure. This extends PrefixedFirebaseError. + */ +export class FirebaseFunctionsError extends PrefixedFirebaseError { + /** + * @param info - The error code info. + * @param message - The error message. If provided, this will override the default message. + */ + constructor(info: ErrorInfo, message?: string) { + super('functions', info.code, message || info.message, info.httpResponse, info.cause); + } +} diff --git a/src/functions/functions-api-client-internal.ts b/src/functions/functions-api-client-internal.ts index 473eb2602a..34e55e5ddc 100644 --- a/src/functions/functions-api-client-internal.ts +++ b/src/functions/functions-api-client-internal.ts @@ -20,7 +20,8 @@ import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, RequestResponseError, AuthorizedHttpClient } from '../utils/api-request'; -import { PrefixedFirebaseError, ErrorInfo, toHttpResponse } from '../utils/error'; +import { PrefixedFirebaseError, toHttpResponse } from '../utils/error'; +import { FirebaseFunctionsError, FunctionsErrorCode, FUNCTIONS_ERROR_CODE_MAPPING } from './error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { TaskOptions } from './functions-api'; @@ -447,47 +448,6 @@ export interface Task { }; } -export const FUNCTIONS_ERROR_CODE_MAPPING: { [key: string]: FunctionsErrorCode } = { - ABORTED: 'aborted', - INVALID_ARGUMENT: 'invalid-argument', - INVALID_CREDENTIAL: 'invalid-credential', - INTERNAL: 'internal-error', - FAILED_PRECONDITION: 'failed-precondition', - PERMISSION_DENIED: 'permission-denied', - UNAUTHENTICATED: 'unauthenticated', - NOT_FOUND: 'not-found', - UNKNOWN: 'unknown-error', -}; - -/** - * Functions client error codes and their default messages. - */ -export type FunctionsErrorCode = - 'aborted' - | 'invalid-argument' - | 'invalid-credential' - | 'internal-error' - | 'failed-precondition' - | 'permission-denied' - | 'unauthenticated' - | 'not-found' - | 'unknown-error' - | 'task-already-exists'; - -/** - * Firebase Functions error code structure. This extends PrefixedFirebaseError. - */ -export class FirebaseFunctionsError extends PrefixedFirebaseError { - /** - * @param info - The error code info. - * @param message - The error message. If provided, this will override the default message. - */ - constructor(info: ErrorInfo, message?: string) { - super('functions', info.code, message || info.message, info.httpResponse, info.cause); - - } -} - function tasksEmulatorUrl(resources: utils.ParsedResource): string | undefined { if (process.env.CLOUD_TASKS_EMULATOR_HOST) { return `http://${process.env.CLOUD_TASKS_EMULATOR_HOST}/projects/${resources.projectId}/locations/${resources.locationId}/queues/${resources.resourceId}/tasks`; diff --git a/src/functions/functions.ts b/src/functions/functions.ts index 0adc0fdac3..805654aaa6 100644 --- a/src/functions/functions.ts +++ b/src/functions/functions.ts @@ -16,7 +16,8 @@ */ import { App } from '../app'; -import { FirebaseFunctionsError, FunctionsApiClient } from './functions-api-client-internal'; +import { FunctionsApiClient } from './functions-api-client-internal'; +import { FirebaseFunctionsError } from './error'; import { TaskOptions } from './functions-api'; import * as validator from '../utils/validator'; diff --git a/src/functions/index.ts b/src/functions/index.ts index 5520f14f5a..0894d244a6 100644 --- a/src/functions/index.ts +++ b/src/functions/index.ts @@ -72,4 +72,4 @@ export function getFunctions(app?: App): Functions { return firebaseApp.getOrInitService('functions', (app) => new Functions(app)); } -export { FirebaseFunctionsError, FunctionsErrorCode } from './functions-api-client-internal'; \ No newline at end of file +export { FirebaseFunctionsError, FunctionsErrorCode } from './error'; \ No newline at end of file diff --git a/test/unit/functions/functions-api-client-internal.spec.ts b/test/unit/functions/functions-api-client-internal.spec.ts index 9804d66c42..8a6a42080f 100644 --- a/test/unit/functions/functions-api-client-internal.spec.ts +++ b/test/unit/functions/functions-api-client-internal.spec.ts @@ -25,7 +25,8 @@ import * as mocks from '../../resources/mocks'; import { getSdkVersion, getMetricsHeader } from '../../../src/utils'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { FirebaseFunctionsError, FunctionsApiClient, Task } from '../../../src/functions/functions-api-client-internal'; +import { FunctionsApiClient, Task } from '../../../src/functions/functions-api-client-internal'; +import { FirebaseFunctionsError } from '../../../src/functions/error'; import { HttpClient } from '../../../src/utils/api-request'; import { toHttpResponse } from '../../../src/utils/error'; import { FirebaseAppError } from '../../../src/app/error'; diff --git a/test/unit/functions/functions.spec.ts b/test/unit/functions/functions.spec.ts index 9d650e51a0..210a4905e8 100644 --- a/test/unit/functions/functions.spec.ts +++ b/test/unit/functions/functions.spec.ts @@ -23,7 +23,8 @@ import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { FunctionsApiClient, FirebaseFunctionsError } from '../../../src/functions/functions-api-client-internal'; +import { FunctionsApiClient } from '../../../src/functions/functions-api-client-internal'; +import { FirebaseFunctionsError } from '../../../src/functions/error'; import { HttpClient } from '../../../src/utils/api-request'; import { Functions, TaskQueue } from '../../../src/functions/functions'; From 663b62e2b335d2bcbac8b38cf502153325e79265 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 15:58:52 -0400 Subject: [PATCH 15/22] chore(extensions): Refactor extensions error logic --- src/extensions/error.ts | 35 +++++++++++++++++++ .../extensions-api-client-internal.ts | 21 ++--------- src/extensions/extensions.ts | 3 +- src/extensions/index.ts | 2 +- .../extensions-api-client-internal.spec.ts | 13 +++++-- test/unit/extensions/extensions.spec.ts | 12 +++++-- 6 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 src/extensions/error.ts diff --git a/src/extensions/error.ts b/src/extensions/error.ts new file mode 100644 index 0000000000..5127ecd402 --- /dev/null +++ b/src/extensions/error.ts @@ -0,0 +1,35 @@ +/*! + * Copyright 2026 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. + */ + +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; + +/** + * Extensions client error codes and their default messages. + */ +export type ExtensionsErrorCode = 'invalid-argument' | 'not-found' | 'forbidden' | 'internal-error' | 'unknown-error'; + +/** + * Firebase Extensions error code structure. This extends PrefixedFirebaseError. + */ +export class FirebaseExtensionsError extends PrefixedFirebaseError { + /** + * @param info - The error code info. + * @param message - The error message. If provided, this will override the default message. + */ + constructor(info: ErrorInfo, message?: string) { + super('Extensions', info.code, message || info.message, info.httpResponse, info.cause); + } +} diff --git a/src/extensions/extensions-api-client-internal.ts b/src/extensions/extensions-api-client-internal.ts index d997a4334d..e89915f0bb 100644 --- a/src/extensions/extensions-api-client-internal.ts +++ b/src/extensions/extensions-api-client-internal.ts @@ -18,7 +18,8 @@ import { App } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { AuthorizedHttpClient, HttpClient, RequestResponseError, HttpRequestConfig } from '../utils/api-request'; -import { PrefixedFirebaseError, ErrorInfo, toHttpResponse } from '../utils/error'; +import { PrefixedFirebaseError, toHttpResponse } from '../utils/error'; +import { FirebaseExtensionsError } from './error'; import { FirebaseAppError } from '../app/error'; import * as validator from '../utils/validator'; import * as utils from '../utils'; @@ -153,21 +154,3 @@ type State = 'STATE_UNSPECIFIED' | 'PROCESSING_COMPLETE' | 'PROCESSING_WARNING' | 'PROCESSING_FAILED'; - -/** - * Extensions client error codes and their default messages. - */ -export type ExtensionsErrorCode = 'invalid-argument' | 'not-found' | 'forbidden' | 'internal-error' | 'unknown-error'; -/** - * Firebase Extensions error code structure. This extends PrefixedFirebaseError. - */ -export class FirebaseExtensionsError extends PrefixedFirebaseError { - /** - * @param info - The error code info. - * @param message - The error message. If provided, this will override the default message. - */ - constructor(info: ErrorInfo, message?: string) { - super('Extensions', info.code, message || info.message, info.httpResponse, info.cause); - - } -} diff --git a/src/extensions/extensions.ts b/src/extensions/extensions.ts index 63e090f0ca..3c99bc62a5 100644 --- a/src/extensions/extensions.ts +++ b/src/extensions/extensions.ts @@ -17,7 +17,8 @@ import { App } from '../app'; import { SettableProcessingState } from './extensions-api'; -import { ExtensionsApiClient, FirebaseExtensionsError } from './extensions-api-client-internal'; +import { ExtensionsApiClient } from './extensions-api-client-internal'; +import { FirebaseExtensionsError } from './error'; import * as validator from '../utils/validator'; /** diff --git a/src/extensions/index.ts b/src/extensions/index.ts index 645f0e22e1..04955071db 100644 --- a/src/extensions/index.ts +++ b/src/extensions/index.ts @@ -63,4 +63,4 @@ export function getExtensions(app?: App): Extensions { return firebaseApp.getOrInitService('extensions', (app) => new Extensions(app)); } -export { FirebaseExtensionsError, ExtensionsErrorCode } from './extensions-api-client-internal'; \ No newline at end of file +export { FirebaseExtensionsError, ExtensionsErrorCode } from './error'; \ No newline at end of file diff --git a/test/unit/extensions/extensions-api-client-internal.spec.ts b/test/unit/extensions/extensions-api-client-internal.spec.ts index 62cb8eafca..f331f6651b 100644 --- a/test/unit/extensions/extensions-api-client-internal.spec.ts +++ b/test/unit/extensions/extensions-api-client-internal.spec.ts @@ -15,13 +15,22 @@ * limitations under the License. */ -import { expect } from 'chai'; +import * as chai from 'chai'; import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { ExtensionsApiClient, FirebaseExtensionsError } from '../../../src/extensions/extensions-api-client-internal'; +import { ExtensionsApiClient } from '../../../src/extensions/extensions-api-client-internal'; +import { FirebaseExtensionsError } from '../../../src/extensions/error'; import { HttpClient } from '../../../src/utils/api-request'; import { SettableProcessingState } from '../../../src/extensions/extensions-api'; import { getMetricsHeader, getSdkVersion } from '../../../src/utils'; diff --git a/test/unit/extensions/extensions.spec.ts b/test/unit/extensions/extensions.spec.ts index d17d2da09b..6e9f4f5fbc 100644 --- a/test/unit/extensions/extensions.spec.ts +++ b/test/unit/extensions/extensions.spec.ts @@ -15,8 +15,16 @@ * limitations under the License. */ +import * as chai from 'chai'; import * as sinon from 'sinon'; -import { expect } from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; import * as mocks from '../../resources/mocks'; import * as utils from '../utils'; @@ -25,7 +33,7 @@ import { Extensions } from '../../../src/extensions/extensions'; import { FirebaseAppError } from '../../../src/app/error'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; import { SettableProcessingState } from '../../../src/extensions/extensions-api'; -import { FirebaseExtensionsError } from '../../../src/extensions/extensions-api-client-internal'; +import { FirebaseExtensionsError } from '../../../src/extensions/error'; describe('Extensions', () => { const mockOptions = { From 8c4721d3d9b46fc21df230777d6a06230fe31c52 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 17:07:56 -0400 Subject: [PATCH 16/22] chore(fdc): Refactor data connect error logic --- .../data-connect-api-client-internal.ts | 43 +------------- src/data-connect/error.ts | 57 +++++++++++++++++++ src/data-connect/index.ts | 2 +- src/data-connect/validate-admin-args.ts | 2 +- .../data-connect-api-client-internal.spec.ts | 7 ++- .../data-connect/validate-admin-args.spec.ts | 2 +- 6 files changed, 67 insertions(+), 46 deletions(-) create mode 100644 src/data-connect/error.ts diff --git a/src/data-connect/data-connect-api-client-internal.ts b/src/data-connect/data-connect-api-client-internal.ts index 37df364207..cc96f3b4fd 100644 --- a/src/data-connect/data-connect-api-client-internal.ts +++ b/src/data-connect/data-connect-api-client-internal.ts @@ -20,7 +20,8 @@ import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, RequestResponseError, AuthorizedHttpClient } from '../utils/api-request'; -import { PrefixedFirebaseError, ErrorInfo, toHttpResponse } from '../utils/error'; +import { PrefixedFirebaseError, toHttpResponse } from '../utils/error'; +import { FirebaseDataConnectError, DataConnectErrorCode, DATA_CONNECT_ERROR_CODE_MAPPING } from './error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { ConnectorConfig, ExecuteGraphqlResponse, GraphqlOptions, OperationOptions } from './data-connect-api'; @@ -678,43 +679,3 @@ interface ServerError { message?: string; status?: string; } - -export const DATA_CONNECT_ERROR_CODE_MAPPING: { [key: string]: DataConnectErrorCode } = { - ABORTED: 'aborted', - INVALID_ARGUMENT: 'invalid-argument', - INVALID_CREDENTIAL: 'invalid-credential', - INTERNAL: 'internal-error', - PERMISSION_DENIED: 'permission-denied', - UNAUTHENTICATED: 'unauthenticated', - NOT_FOUND: 'not-found', - UNKNOWN: 'unknown-error', - QUERY_ERROR: 'query-error', -}; - -/** - * Data Connect client error codes and their default messages. - */ -export type DataConnectErrorCode = - 'aborted' - | 'invalid-argument' - | 'invalid-credential' - | 'internal-error' - | 'permission-denied' - | 'unauthenticated' - | 'not-found' - | 'unknown-error' - | 'query-error'; - -/** - * Firebase Data Connect error type. This extends PrefixedFirebaseError. - */ -export class FirebaseDataConnectError extends PrefixedFirebaseError { - /** - * @param info - The error code info. - * @param message - The error message. If provided, this will override the default message. - */ - constructor(info: ErrorInfo, message?: string) { - super('data-connect', info.code, message || info.message, info.httpResponse, info.cause); - - } -} diff --git a/src/data-connect/error.ts b/src/data-connect/error.ts new file mode 100644 index 0000000000..825b96ecd7 --- /dev/null +++ b/src/data-connect/error.ts @@ -0,0 +1,57 @@ +/*! + * Copyright 2026 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. + */ + +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; + +/** @const {Record} Data Connect server to client error code mapping. */ +export const DATA_CONNECT_ERROR_CODE_MAPPING: Record = { + ABORTED: 'aborted', + INVALID_ARGUMENT: 'invalid-argument', + INVALID_CREDENTIAL: 'invalid-credential', + INTERNAL: 'internal-error', + PERMISSION_DENIED: 'permission-denied', + UNAUTHENTICATED: 'unauthenticated', + NOT_FOUND: 'not-found', + UNKNOWN: 'unknown-error', + QUERY_ERROR: 'query-error', +}; + +/** + * Data Connect client error codes and their default messages. + */ +export type DataConnectErrorCode = + | 'aborted' + | 'invalid-argument' + | 'invalid-credential' + | 'internal-error' + | 'permission-denied' + | 'unauthenticated' + | 'not-found' + | 'unknown-error' + | 'query-error'; + +/** + * Firebase Data Connect error type. This extends PrefixedFirebaseError. + */ +export class FirebaseDataConnectError extends PrefixedFirebaseError { + /** + * @param info - The error code info. + * @param message - The error message. If provided, this will override the default message. + */ + constructor(info: ErrorInfo, message?: string) { + super('data-connect', info.code, message || info.message, info.httpResponse, info.cause); + } +} diff --git a/src/data-connect/index.ts b/src/data-connect/index.ts index b2abe7aaa1..a8adac447f 100644 --- a/src/data-connect/index.ts +++ b/src/data-connect/index.ts @@ -90,4 +90,4 @@ export function getDataConnect(connectorConfig: ConnectorConfig, app?: App): Dat */ export const validateAdminArgs = _validateAdminArgs; -export { FirebaseDataConnectError, DataConnectErrorCode } from './data-connect-api-client-internal'; \ No newline at end of file +export { FirebaseDataConnectError, DataConnectErrorCode } from './error'; \ No newline at end of file diff --git a/src/data-connect/validate-admin-args.ts b/src/data-connect/validate-admin-args.ts index d8afa8a58d..8c4a210e0e 100644 --- a/src/data-connect/validate-admin-args.ts +++ b/src/data-connect/validate-admin-args.ts @@ -21,7 +21,7 @@ import { ConnectorConfig, OperationOptions } from './data-connect-api'; import { DATA_CONNECT_ERROR_CODE_MAPPING, FirebaseDataConnectError, -} from './data-connect-api-client-internal'; +} from './error'; /** * @internal diff --git a/test/unit/data-connect/data-connect-api-client-internal.spec.ts b/test/unit/data-connect/data-connect-api-client-internal.spec.ts index a50c7e30be..5951c29264 100644 --- a/test/unit/data-connect/data-connect-api-client-internal.spec.ts +++ b/test/unit/data-connect/data-connect-api-client-internal.spec.ts @@ -24,8 +24,11 @@ import { } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { DATA_CONNECT_ERROR_CODE_MAPPING, DataConnectApiClient, FirebaseDataConnectError } - from '../../../src/data-connect/data-connect-api-client-internal'; +import { DataConnectApiClient } from '../../../src/data-connect/data-connect-api-client-internal'; +import { + FirebaseDataConnectError, + DATA_CONNECT_ERROR_CODE_MAPPING, +} from '../../../src/data-connect/error'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { ConnectorConfig } from '../../../src/data-connect'; import { getMetricsHeader, getSdkVersion } from '../../../src/utils'; diff --git a/test/unit/data-connect/validate-admin-args.spec.ts b/test/unit/data-connect/validate-admin-args.spec.ts index 79a0d931b3..54248aa846 100644 --- a/test/unit/data-connect/validate-admin-args.spec.ts +++ b/test/unit/data-connect/validate-admin-args.spec.ts @@ -22,7 +22,7 @@ import { OperationOptions } from '../../../src/data-connect'; import { DATA_CONNECT_ERROR_CODE_MAPPING, FirebaseDataConnectError -} from '../../../src/data-connect/data-connect-api-client-internal'; +} from '../../../src/data-connect/error'; import * as dataConnectIndex from '../../../src/data-connect/index'; interface IdVars { From a6d0ade5281684189416ef9ea5f4bc6ba8b02758 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 17:43:03 -0400 Subject: [PATCH 17/22] chore(ml): Refactor ml error logic --- src/machine-learning/{machine-learning-utils.ts => error.ts} | 5 ++--- src/machine-learning/index.ts | 2 +- src/machine-learning/machine-learning-api-client.ts | 2 +- src/machine-learning/machine-learning.ts | 2 +- .../machine-learning/machine-learning-api-client.spec.ts | 2 +- test/unit/machine-learning/machine-learning.spec.ts | 2 +- 6 files changed, 7 insertions(+), 8 deletions(-) rename src/machine-learning/{machine-learning-utils.ts => error.ts} (97%) diff --git a/src/machine-learning/machine-learning-utils.ts b/src/machine-learning/error.ts similarity index 97% rename from src/machine-learning/machine-learning-utils.ts rename to src/machine-learning/error.ts index 6c59a56f0c..0028b5ee74 100644 --- a/src/machine-learning/machine-learning-utils.ts +++ b/src/machine-learning/error.ts @@ -17,10 +17,10 @@ import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; /** - * Machine Learning client error codes and their default messages. + * Machine Learning client error codes. */ export type MachineLearningErrorCode = - 'already-exists' + | 'already-exists' | 'authentication-error' | 'internal-error' | 'invalid-argument' @@ -68,6 +68,5 @@ export class FirebaseMachineLearningError extends PrefixedFirebaseError { */ constructor(info: ErrorInfo, message?: string) { super('machine-learning', info.code, message || info.message, info.httpResponse, info.cause); - } } diff --git a/src/machine-learning/index.ts b/src/machine-learning/index.ts index 82c4263796..0ebb351a5d 100644 --- a/src/machine-learning/index.ts +++ b/src/machine-learning/index.ts @@ -72,4 +72,4 @@ export function getMachineLearning(app?: App): MachineLearning { return firebaseApp.getOrInitService('machineLearning', (app) => new MachineLearning(app)); } -export { FirebaseMachineLearningError, MachineLearningErrorCode } from './machine-learning-utils'; \ No newline at end of file +export { FirebaseMachineLearningError, MachineLearningErrorCode } from './error'; \ No newline at end of file diff --git a/src/machine-learning/machine-learning-api-client.ts b/src/machine-learning/machine-learning-api-client.ts index b287e6490f..1e8576d577 100644 --- a/src/machine-learning/machine-learning-api-client.ts +++ b/src/machine-learning/machine-learning-api-client.ts @@ -22,7 +22,7 @@ import { import { PrefixedFirebaseError, toHttpResponse } from '../utils/error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; -import { FirebaseMachineLearningError, MachineLearningErrorCode } from './machine-learning-utils'; +import { FirebaseMachineLearningError, MachineLearningErrorCode } from './error'; /** * Firebase ML Model input objects diff --git a/src/machine-learning/machine-learning.ts b/src/machine-learning/machine-learning.ts index 1586d79289..ca080643dd 100644 --- a/src/machine-learning/machine-learning.ts +++ b/src/machine-learning/machine-learning.ts @@ -24,7 +24,7 @@ import { MachineLearningApiClient, ModelResponse, ModelUpdateOptions, isGcsTfliteModelOptions, ListModelsOptions, ModelOptions, } from './machine-learning-api-client'; -import { FirebaseMachineLearningError } from './machine-learning-utils'; +import { FirebaseMachineLearningError } from './error'; /** Response object for a listModels operation. */ export interface ListModelsResult { diff --git a/test/unit/machine-learning/machine-learning-api-client.spec.ts b/test/unit/machine-learning/machine-learning-api-client.spec.ts index ccc49179bd..619f98edc2 100644 --- a/test/unit/machine-learning/machine-learning-api-client.spec.ts +++ b/test/unit/machine-learning/machine-learning-api-client.spec.ts @@ -19,7 +19,7 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { FirebaseMachineLearningError } from '../../../src/machine-learning/machine-learning-utils'; +import { FirebaseMachineLearningError } from '../../../src/machine-learning/error'; import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; diff --git a/test/unit/machine-learning/machine-learning.spec.ts b/test/unit/machine-learning/machine-learning.spec.ts index 5666cfbd39..9394882dfa 100644 --- a/test/unit/machine-learning/machine-learning.spec.ts +++ b/test/unit/machine-learning/machine-learning.spec.ts @@ -27,7 +27,7 @@ import { ModelResponse, OperationResponse } from '../../../src/machine-learning/machine-learning-api-client'; -import { FirebaseMachineLearningError } from '../../../src/machine-learning/machine-learning-utils'; +import { FirebaseMachineLearningError } from '../../../src/machine-learning/error'; import { deepCopy } from '../../../src/utils/deep-copy'; import { MachineLearning, Model, ModelOptions } from '../../../src/machine-learning/index'; From c07414a9d20792b1327c0b0a6ac47cd7bfeb449d Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 17:53:16 -0400 Subject: [PATCH 18/22] chore(eventarc): Refactor eventarc error logic --- src/eventarc/error.ts | 35 ++++++++++++++++++++++++ src/eventarc/eventarc-client-internal.ts | 3 +- src/eventarc/eventarc-utils.ts | 21 +------------- src/eventarc/eventarc.ts | 2 +- src/eventarc/index.ts | 2 +- test/unit/eventarc/eventarc.spec.ts | 9 +++++- 6 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 src/eventarc/error.ts diff --git a/src/eventarc/error.ts b/src/eventarc/error.ts new file mode 100644 index 0000000000..8ab09cc9a0 --- /dev/null +++ b/src/eventarc/error.ts @@ -0,0 +1,35 @@ +/*! + * Copyright 2026 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. + */ + +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; + +/** + * Eventarc client error codes and their default messages. + */ +export type EventarcErrorCode = 'unknown-error' | 'invalid-argument'; + +/** + * Firebase Eventarc error code structure. This extends PrefixedFirebaseError. + */ +export class FirebaseEventarcError extends PrefixedFirebaseError { + /** + * @param info - The error code info. + * @param message - The error message. If provided, this will override the default message. + */ + constructor(info: ErrorInfo, message?: string) { + super('eventarc', info.code, message || info.message, info.httpResponse, info.cause); + } +} diff --git a/src/eventarc/eventarc-client-internal.ts b/src/eventarc/eventarc-client-internal.ts index 2ae25611d8..4c517343b1 100644 --- a/src/eventarc/eventarc-client-internal.ts +++ b/src/eventarc/eventarc-client-internal.ts @@ -16,7 +16,8 @@ */ import * as validator from '../utils/validator'; -import { FirebaseEventarcError, toCloudEventProtoFormat } from './eventarc-utils'; +import { toCloudEventProtoFormat } from './eventarc-utils'; +import { FirebaseEventarcError } from './error'; import { App } from '../app'; import { Channel } from './eventarc'; import { diff --git a/src/eventarc/eventarc-utils.ts b/src/eventarc/eventarc-utils.ts index 2338b8660f..a57894157a 100644 --- a/src/eventarc/eventarc-utils.ts +++ b/src/eventarc/eventarc-utils.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; +import { FirebaseEventarcError } from './error'; import { CloudEvent } from './cloudevent'; import { v4 as uuid } from 'uuid'; import * as validator from '../utils/validator'; @@ -25,25 +25,6 @@ import * as validator from '../utils/validator'; const TOP_LEVEL_CE_ATTRS: string[] = ['id', 'type', 'specversion', 'source', 'data', 'time', 'datacontenttype', 'subject']; -/** - * Eventarc client error codes and their default messages. - */ -export type EventarcErrorCode = 'unknown-error' | 'invalid-argument' - -/** - * Firebase Eventarc error code structure. This extends PrefixedFirebaseError. - */ -export class FirebaseEventarcError extends PrefixedFirebaseError { - /** - * @param info - The error code info. - * @param message - The error message. If provided, this will override the default message. - */ - constructor(info: ErrorInfo, message?: string) { - super('eventarc', info.code, message || info.message, info.httpResponse, info.cause); - - } -} - export function toCloudEventProtoFormat(ce: CloudEvent): any { const source = ce.source ?? process.env.EVENTARC_CLOUD_EVENT_SOURCE; if (typeof source === 'undefined' || !validator.isNonEmptyString(source)) { diff --git a/src/eventarc/eventarc.ts b/src/eventarc/eventarc.ts index 36419175c2..6276248ccb 100644 --- a/src/eventarc/eventarc.ts +++ b/src/eventarc/eventarc.ts @@ -17,7 +17,7 @@ import { App } from '../app'; import * as validator from '../utils/validator'; -import { FirebaseEventarcError } from './eventarc-utils'; +import { FirebaseEventarcError } from './error'; import { CloudEvent } from './cloudevent'; import { EventarcApiClient } from './eventarc-client-internal'; diff --git a/src/eventarc/index.ts b/src/eventarc/index.ts index a3a49cde74..6e8a18720a 100644 --- a/src/eventarc/index.ts +++ b/src/eventarc/index.ts @@ -64,4 +64,4 @@ export function getEventarc(app?: App): Eventarc { return firebaseApp.getOrInitService('eventarc', (app) => new Eventarc(app)); } -export { FirebaseEventarcError, EventarcErrorCode } from './eventarc-utils'; \ No newline at end of file +export { FirebaseEventarcError, EventarcErrorCode } from './error'; \ No newline at end of file diff --git a/test/unit/eventarc/eventarc.spec.ts b/test/unit/eventarc/eventarc.spec.ts index 93ca71d28a..55cc853b4a 100644 --- a/test/unit/eventarc/eventarc.spec.ts +++ b/test/unit/eventarc/eventarc.spec.ts @@ -27,10 +27,17 @@ import * as mocks from '../../resources/mocks'; import * as utils from '../utils'; import * as chai from 'chai'; import chaiExclude from 'chai-exclude'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); +chai.use(chaiExclude); + import { getMetricsHeader, getSdkVersion } from '../../../src/utils/index'; const expect = chai.expect; -chai.use(chaiExclude); const TEST_EVENT1 : CloudEvent = { type: 'some.custom.event1', From 32968737ed12f70ffa7c30677c558574dcc525f4 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 4 May 2026 18:30:16 -0400 Subject: [PATCH 19/22] chore(fpnv): Refactor pnv error logic --- src/phone-number-verification/error.ts | 46 +++++++++++++++++++ src/phone-number-verification/index.ts | 2 +- ...number-verification-api-client-internal.ts | 32 ------------- .../phone-number-verification-api.ts | 2 +- .../token-verifier.ts | 3 +- ...r-verification-api-client-internal.spec.ts | 8 ++-- .../token-verifier.spec.ts | 2 +- 7 files changed, 56 insertions(+), 39 deletions(-) create mode 100644 src/phone-number-verification/error.ts diff --git a/src/phone-number-verification/error.ts b/src/phone-number-verification/error.ts new file mode 100644 index 0000000000..efd9647adc --- /dev/null +++ b/src/phone-number-verification/error.ts @@ -0,0 +1,46 @@ +/*! + * Copyright 2026 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. + */ + +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; + +export const FPNV_ERROR_CODE_MAPPING = { + INVALID_ARGUMENT: 'invalid-argument', + INVALID_TOKEN: 'invalid-token', + EXPIRED_TOKEN: 'expired-token', +} satisfies Record; + +export type PhoneNumberVerificationErrorCode = + | 'invalid-argument' + | 'invalid-token' + | 'expired-token'; + +/** + * Firebase Phone Number Verification error code structure. This extends `PrefixedFirebaseError`. + * + * @param info - The error code info. + * @param message - The error message. If provided, this will override the default message. + */ +export class FirebasePhoneNumberVerificationError extends PrefixedFirebaseError { + constructor(info: ErrorInfo, message?: string) { + super('phone-number-verification', info.code, message || info.message, info.httpResponse, info.cause); + + /* tslint:disable:max-line-length */ + // Set the prototype explicitly. See the following link for more details: + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + /* tslint:enable:max-line-length */ + (this as any).__proto__ = FirebasePhoneNumberVerificationError.prototype; + } +} diff --git a/src/phone-number-verification/index.ts b/src/phone-number-verification/index.ts index 0de332c976..f27db5700c 100644 --- a/src/phone-number-verification/index.ts +++ b/src/phone-number-verification/index.ts @@ -68,4 +68,4 @@ export function getPhoneNumberVerification(app?: App): PhoneNumberVerification { export { FirebasePhoneNumberVerificationError, PhoneNumberVerificationErrorCode, -} from './phone-number-verification-api-client-internal'; +} from './error'; diff --git a/src/phone-number-verification/phone-number-verification-api-client-internal.ts b/src/phone-number-verification/phone-number-verification-api-client-internal.ts index 0ad11d5128..484f8e8a33 100644 --- a/src/phone-number-verification/phone-number-verification-api-client-internal.ts +++ b/src/phone-number-verification/phone-number-verification-api-client-internal.ts @@ -15,8 +15,6 @@ * limitations under the License. */ -import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; - export interface FirebasePhoneNumberTokenInfo { /** Documentation URL. */ url: string; @@ -39,33 +37,3 @@ export const FPNV_TOKEN_INFO: FirebasePhoneNumberTokenInfo = { shortName: 'FPNV token', typ: 'JWT', }; - -export const FPNV_ERROR_CODE_MAPPING = { - INVALID_ARGUMENT: 'invalid-argument', - INVALID_TOKEN: 'invalid-token', - EXPIRED_TOKEN: 'expired-token', -} satisfies Record; - -export type PhoneNumberVerificationErrorCode = - | 'invalid-argument' - | 'invalid-token' - | 'expired-token' - -/** - * Firebase Phone Number Verification error code structure. This extends `PrefixedFirebaseError`. - * - * @param info - The error code info. - * @param message - The error message. If provided, this will override the default message. - */ -export class FirebasePhoneNumberVerificationError extends PrefixedFirebaseError { - constructor(info: ErrorInfo, message?: string) { - super('phone-number-verification', info.code, message || info.message, info.httpResponse, info.cause); - - /* tslint:disable:max-line-length */ - // Set the prototype explicitly. See the following link for more details: - // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - /* tslint:enable:max-line-length */ - (this as any).__proto__ = FirebasePhoneNumberVerificationError.prototype; - } -} - diff --git a/src/phone-number-verification/phone-number-verification-api.ts b/src/phone-number-verification/phone-number-verification-api.ts index e3c937b728..a3527d78d6 100644 --- a/src/phone-number-verification/phone-number-verification-api.ts +++ b/src/phone-number-verification/phone-number-verification-api.ts @@ -82,5 +82,5 @@ export interface PhoneNumberVerificationToken { export { PhoneNumberVerificationErrorCode, FirebasePhoneNumberVerificationError, -} from './phone-number-verification-api-client-internal'; +} from './error'; diff --git a/src/phone-number-verification/token-verifier.ts b/src/phone-number-verification/token-verifier.ts index 9bc4e7dd1a..3eda268599 100644 --- a/src/phone-number-verification/token-verifier.ts +++ b/src/phone-number-verification/token-verifier.ts @@ -23,7 +23,8 @@ import { DecodedToken, decodeJwt, JwtError, JwtErrorCode, PublicKeySignatureVerifier, ALGORITHM_ES256, SignatureVerifier, } from '../utils/jwt'; -import { FirebasePhoneNumberTokenInfo, FPNV_ERROR_CODE_MAPPING } from './phone-number-verification-api-client-internal'; +import { FirebasePhoneNumberTokenInfo } from './phone-number-verification-api-client-internal'; +import { FPNV_ERROR_CODE_MAPPING } from './error'; export class PhoneNumberTokenVerifier { private readonly shortNameArticle: string; diff --git a/test/unit/phone-number-verification/phone-number-verification-api-client-internal.spec.ts b/test/unit/phone-number-verification/phone-number-verification-api-client-internal.spec.ts index 123f13f9ef..021da9524b 100644 --- a/test/unit/phone-number-verification/phone-number-verification-api-client-internal.spec.ts +++ b/test/unit/phone-number-verification/phone-number-verification-api-client-internal.spec.ts @@ -19,11 +19,13 @@ import { expect } from 'chai'; import { - FirebasePhoneNumberVerificationError, JWKS_URL, - FPNV_TOKEN_INFO, - FPNV_ERROR_CODE_MAPPING + FPNV_TOKEN_INFO } from '../../../src/phone-number-verification/phone-number-verification-api-client-internal'; +import { + FirebasePhoneNumberVerificationError, + FPNV_ERROR_CODE_MAPPING +} from '../../../src/phone-number-verification/error'; import { PrefixedFirebaseError, FirebaseError } from '../../../src/utils/error'; const FPNV_PREFIX = 'phone-number-verification'; diff --git a/test/unit/phone-number-verification/token-verifier.spec.ts b/test/unit/phone-number-verification/token-verifier.spec.ts index 9cc68935e1..a7fa8d2ce7 100644 --- a/test/unit/phone-number-verification/token-verifier.spec.ts +++ b/test/unit/phone-number-verification/token-verifier.spec.ts @@ -27,8 +27,8 @@ import * as util from '../../../src/utils/index'; import { PhoneNumberTokenVerifier } from '../../../src/phone-number-verification/token-verifier'; import { FirebasePhoneNumberTokenInfo, - FPNV_ERROR_CODE_MAPPING } from '../../../src/phone-number-verification/phone-number-verification-api-client-internal'; +import { FPNV_ERROR_CODE_MAPPING } from '../../../src/phone-number-verification/error'; import * as mocks from '../../resources/mocks'; chai.use(chaiAsPromised); From 5218f752d9134a23221810ebd0b727b1078e2b8a Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Tue, 5 May 2026 14:58:45 -0400 Subject: [PATCH 20/22] fix: address gemini review --- etc/firebase-admin.auth.api.md | 2 +- src/auth/error.ts | 6 +++--- src/auth/token-generator.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index c016df5b73..1fb94bf44f 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -56,7 +56,7 @@ export class Auth extends BaseAuth { } // @public -export type AuthErrorCode = 'auth-blocking-token-expired' | 'billing-not-enabled' | 'claims-too-large' | 'configuration-exists' | 'configuration-not-found' | 'id-token-expired' | 'argument-error' | 'invalid-config' | 'email-already-exists' | 'email-not-found' | 'reserved-claim' | 'invalid-id-token' | 'id-token-revoked' | 'internal-error' | 'invalid-claims' | 'invalid-continue-uri' | 'invalid-creation-time' | 'invalid-credential' | 'invalid-disabled-field' | 'invalid-display-name' | 'invalid-dynamic-link-domain' | 'invalid-hosting-link-domain' | 'invalid-email-verified' | 'invalid-email' | 'invalid-new-email' | 'invalid-enrolled-factors' | 'invalid-enrollment-time' | 'invalid-hash-algorithm' | 'invalid-hash-block-size' | 'invalid-hash-derived-key-length' | 'invalid-hash-key' | 'invalid-hash-memory-cost' | 'invalid-hash-parallelization' | 'invalid-hash-rounds' | 'invalid-hash-salt-separator' | 'invalid-last-sign-in-time' | 'invalid-name' | 'invalid-oauth-client-id' | 'invalid-page-token' | 'invalid-password' | 'invalid-password-hash' | 'invalid-password-salt' | 'invalid-phone-number' | 'invalid-photo-url' | 'invalid-project-id' | 'invalid-provider-data' | 'invalid-provider-id' | 'invalid-provider-uid' | 'invalid-oauth-responsetype' | 'invalid-session-cookie-duration' | 'invalid-tenant-id' | 'invalid-tenant-type' | 'invalid-testing-phone-number' | 'invalid-uid' | 'invalid-user-import' | 'invalid-tokens-valid-after-time' | 'mismatching-tenant-id' | 'missing-android-package-name' | 'missing-config' | 'missing-continue-uri' | 'missing-display-name' | 'missing-email' | 'missing-ios-bundle-id' | 'missing-issuer' | 'missing-hash-algorithm' | 'missing-oauth-client-id' | 'missing-oauth-client-secret' | 'missing-provider-id' | 'missing-saml-relying-party-config' | 'test-phone-number-limit-exceeded' | 'maximum-user-count-exceeded' | 'missing-uid' | 'operation-not-allowed' | 'phone-number-already-exists' | 'project-not-found' | 'insufficient-permission' | 'quota-exceeded' | 'second-factor-limit-exceeded' | 'second-factor-uid-already-exists' | 'session-cookie-expired' | 'session-cookie-revoked' | 'tenant-not-found' | 'uid-already-exists' | 'unauthorized-continue-uri' | 'unsupported-first-factor' | 'unsupported-second-factor' | 'unsupported-tenant-operation' | 'unverified-email' | 'user-not-found' | 'not-found' | 'user-disabled' | 'user-not-disabled' | 'invalid-recaptcha-action' | 'invalid-recaptcha-enforcement-state' | 'racaptcha-not-enabled'; +export type AuthErrorCode = 'auth-blocking-token-expired' | 'billing-not-enabled' | 'claims-too-large' | 'configuration-exists' | 'configuration-not-found' | 'id-token-expired' | 'argument-error' | 'invalid-config' | 'email-already-exists' | 'email-not-found' | 'reserved-claim' | 'invalid-id-token' | 'id-token-revoked' | 'internal-error' | 'invalid-claims' | 'invalid-continue-uri' | 'invalid-creation-time' | 'invalid-credential' | 'invalid-disabled-field' | 'invalid-display-name' | 'invalid-dynamic-link-domain' | 'invalid-hosting-link-domain' | 'invalid-email-verified' | 'invalid-email' | 'invalid-new-email' | 'invalid-enrolled-factors' | 'invalid-enrollment-time' | 'invalid-hash-algorithm' | 'invalid-hash-block-size' | 'invalid-hash-derived-key-length' | 'invalid-hash-key' | 'invalid-hash-memory-cost' | 'invalid-hash-parallelization' | 'invalid-hash-rounds' | 'invalid-hash-salt-separator' | 'invalid-last-sign-in-time' | 'invalid-name' | 'invalid-oauth-client-id' | 'invalid-page-token' | 'invalid-password' | 'invalid-password-hash' | 'invalid-password-salt' | 'invalid-phone-number' | 'invalid-photo-url' | 'invalid-project-id' | 'invalid-provider-data' | 'invalid-provider-id' | 'invalid-provider-uid' | 'invalid-oauth-responsetype' | 'invalid-session-cookie-duration' | 'invalid-tenant-id' | 'invalid-tenant-type' | 'invalid-testing-phone-number' | 'invalid-uid' | 'invalid-user-import' | 'invalid-tokens-valid-after-time' | 'mismatching-tenant-id' | 'missing-android-package-name' | 'missing-config' | 'missing-continue-uri' | 'missing-display-name' | 'missing-email' | 'missing-ios-bundle-id' | 'missing-issuer' | 'missing-hash-algorithm' | 'missing-oauth-client-id' | 'missing-oauth-client-secret' | 'missing-provider-id' | 'missing-saml-relying-party-config' | 'test-phone-number-limit-exceeded' | 'maximum-user-count-exceeded' | 'missing-uid' | 'operation-not-allowed' | 'phone-number-already-exists' | 'project-not-found' | 'insufficient-permission' | 'quota-exceeded' | 'second-factor-limit-exceeded' | 'second-factor-uid-already-exists' | 'session-cookie-expired' | 'session-cookie-revoked' | 'tenant-not-found' | 'uid-already-exists' | 'unauthorized-continue-uri' | 'unsupported-first-factor' | 'unsupported-second-factor' | 'unsupported-tenant-operation' | 'unverified-email' | 'user-not-found' | 'not-found' | 'user-disabled' | 'user-not-disabled' | 'invalid-recaptcha-action' | 'invalid-recaptcha-enforcement-state' | 'recaptcha-not-enabled'; // @public export type AuthFactorType = 'phone'; diff --git a/src/auth/error.ts b/src/auth/error.ts index 403bab5611..a301ca785c 100644 --- a/src/auth/error.ts +++ b/src/auth/error.ts @@ -116,7 +116,7 @@ export type AuthErrorCode = | 'user-not-disabled' | 'invalid-recaptcha-action' | 'invalid-recaptcha-enforcement-state' - | 'racaptcha-not-enabled'; + | 'recaptcha-not-enabled'; /** * Auth client error codes and their default messages. @@ -229,7 +229,7 @@ const authClientErrorMessages: Record = { 'user-not-disabled': 'The user must be disabled in order to bulk delete it (or you must pass force=true).', 'invalid-recaptcha-action': 'reCAPTCHA action must be "BLOCK".', 'invalid-recaptcha-enforcement-state': 'reCAPTCHA enforcement state must be either "OFF", "AUDIT" or "ENFORCE".', - 'racaptcha-not-enabled': 'reCAPTCHA enterprise is not enabled.', + 'recaptcha-not-enabled': 'reCAPTCHA enterprise is not enabled.', }; /** @const {Record} Auth server to client enum error codes. */ @@ -462,7 +462,7 @@ export const authClientErrorCode = { USER_NOT_DISABLED: createAuthErrorInfo('user-not-disabled'), INVALID_RECAPTCHA_ACTION: createAuthErrorInfo('invalid-recaptcha-action'), INVALID_RECAPTCHA_ENFORCEMENT_STATE: createAuthErrorInfo('invalid-recaptcha-enforcement-state'), - RECAPTCHA_NOT_ENABLED: createAuthErrorInfo('racaptcha-not-enabled'), + RECAPTCHA_NOT_ENABLED: createAuthErrorInfo('recaptcha-not-enabled'), }; function createAuthErrorInfo(code: AuthErrorCode): ErrorInfo { diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index ac0e63ed3c..4a41a50158 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -229,10 +229,10 @@ export function handleCryptoSignerError(err: Error): Error { 'errorcode from the error.' ); } - return new FirebaseAuthError(mapToauthClientErrorCode(err.code), err.message); + return new FirebaseAuthError(mapToAuthErrorInfo(err.code), err.message); } -function mapToauthClientErrorCode(code: string): ErrorInfo { +function mapToAuthErrorInfo(code: string): ErrorInfo { switch (code) { case CryptoSignerErrorCode.INVALID_CREDENTIAL: return authClientErrorCode.INVALID_CREDENTIAL; From e708f278d0688bef652e379fe5e8ff8343f2536b Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Tue, 5 May 2026 18:20:08 -0400 Subject: [PATCH 21/22] fix: Use Declaration Merging to expose error code constant mapping along side error code type --- .eslintrc.js | 9 + etc/firebase-admin.app-check.api.md | 15 +- etc/firebase-admin.app.api.md | 40 +- etc/firebase-admin.auth.api.md | 101 ++- etc/firebase-admin.data-connect.api.md | 15 +- etc/firebase-admin.eventarc.api.md | 8 +- etc/firebase-admin.extensions.api.md | 11 +- etc/firebase-admin.functions.api.md | 16 +- etc/firebase-admin.installations.api.md | 10 +- etc/firebase-admin.instance-id.api.md | 11 +- etc/firebase-admin.machine-learning.api.md | 23 +- etc/firebase-admin.messaging.api.md | 25 +- ...ase-admin.phone-number-verification.api.md | 11 +- etc/firebase-admin.project-management.api.md | 15 +- etc/firebase-admin.remote-config.api.md | 17 +- etc/firebase-admin.security-rules.api.md | 15 +- src/app-check/error.ts | 28 +- src/app/credential-internal.ts | 26 +- src/app/error.ts | 35 +- src/app/firebase-app.ts | 12 +- src/app/index.ts | 2 +- src/app/lifecycle.ts | 22 +- src/auth/error.ts | 812 +++++++++++------- src/data-connect/error.ts | 28 +- src/database/database.ts | 6 +- src/eventarc/error.ts | 12 +- src/extensions/error.ts | 16 +- src/functions/error.ts | 30 +- src/installations/error.ts | 51 +- src/instance-id/error.ts | 36 +- src/machine-learning/error.ts | 44 +- src/messaging/error.ts | 218 +++-- src/phone-number-verification/error.ts | 22 +- src/project-management/error.ts | 28 +- src/remote-config/error.ts | 32 +- src/security-rules/error.ts | 28 +- src/utils/api-request.ts | 40 +- test/unit/app/firebase-app.spec.ts | 6 +- 38 files changed, 1207 insertions(+), 669 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 1d4c74bd43..03e844dbf9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -71,6 +71,15 @@ module.exports = { "selector": "variable", "format": ["camelCase", "UPPER_CASE"] }, + { + "selector": "variable", + "modifiers": ["const"], + "format": ["PascalCase", "camelCase", "UPPER_CASE"], + "filter": { + "regex": "ErrorCode$", // Matches only if it ends exactly with ErrorCode + "match": true, + }, + }, { "selector": "parameter", "format": ["camelCase"], diff --git a/etc/firebase-admin.app-check.api.md b/etc/firebase-admin.app-check.api.md index 305104f8b9..3786a8aa88 100644 --- a/etc/firebase-admin.app-check.api.md +++ b/etc/firebase-admin.app-check.api.md @@ -17,7 +17,20 @@ export class AppCheck { } // @public -export type AppCheckErrorCode = 'aborted' | 'invalid-argument' | 'invalid-credential' | 'internal-error' | 'permission-denied' | 'unauthenticated' | 'not-found' | 'app-check-token-expired' | 'unknown-error'; +export const AppCheckErrorCode: { + readonly ABORTED: "aborted"; + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INVALID_CREDENTIAL: "invalid-credential"; + readonly INTERNAL: "internal-error"; + readonly PERMISSION_DENIED: "permission-denied"; + readonly UNAUTHENTICATED: "unauthenticated"; + readonly NOT_FOUND: "not-found"; + readonly APP_CHECK_TOKEN_EXPIRED: "app-check-token-expired"; + readonly UNKNOWN: "unknown-error"; +}; + +// @public +export type AppCheckErrorCode = typeof AppCheckErrorCode[keyof typeof AppCheckErrorCode]; // @public export interface AppCheckToken { diff --git a/etc/firebase-admin.app.api.md b/etc/firebase-admin.app.api.md index 7a539e3026..7f5e00ee3e 100644 --- a/etc/firebase-admin.app.api.md +++ b/etc/firebase-admin.app.api.md @@ -13,30 +13,22 @@ export interface App { } // @public -export class AppErrorCodes { - // (undocumented) - static APP_DELETED: string; - // (undocumented) - static DUPLICATE_APP: string; - // (undocumented) - static INTERNAL_ERROR: string; - // (undocumented) - static INVALID_APP_NAME: string; - // (undocumented) - static INVALID_APP_OPTIONS: string; - // (undocumented) - static INVALID_ARGUMENT: string; - // (undocumented) - static INVALID_CREDENTIAL: string; - // (undocumented) - static NETWORK_ERROR: string; - // (undocumented) - static NETWORK_TIMEOUT: string; - // (undocumented) - static NO_APP: string; - // (undocumented) - static UNABLE_TO_PARSE_RESPONSE: string; -} +export const AppErrorCode: { + readonly APP_DELETED: "app-deleted"; + readonly DUPLICATE_APP: "duplicate-app"; + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INTERNAL_ERROR: "internal-error"; + readonly INVALID_APP_NAME: "invalid-app-name"; + readonly INVALID_APP_OPTIONS: "invalid-app-options"; + readonly INVALID_CREDENTIAL: "invalid-credential"; + readonly NETWORK_ERROR: "network-error"; + readonly NETWORK_TIMEOUT: "network-timeout"; + readonly NO_APP: "no-app"; + readonly UNABLE_TO_PARSE_RESPONSE: "unable-to-parse-response"; +}; + +// @public +export type AppErrorCode = typeof AppErrorCode[keyof typeof AppErrorCode]; // @public export function applicationDefault(httpAgent?: Agent): Credential; diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 1fb94bf44f..08b790af47 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -56,7 +56,106 @@ export class Auth extends BaseAuth { } // @public -export type AuthErrorCode = 'auth-blocking-token-expired' | 'billing-not-enabled' | 'claims-too-large' | 'configuration-exists' | 'configuration-not-found' | 'id-token-expired' | 'argument-error' | 'invalid-config' | 'email-already-exists' | 'email-not-found' | 'reserved-claim' | 'invalid-id-token' | 'id-token-revoked' | 'internal-error' | 'invalid-claims' | 'invalid-continue-uri' | 'invalid-creation-time' | 'invalid-credential' | 'invalid-disabled-field' | 'invalid-display-name' | 'invalid-dynamic-link-domain' | 'invalid-hosting-link-domain' | 'invalid-email-verified' | 'invalid-email' | 'invalid-new-email' | 'invalid-enrolled-factors' | 'invalid-enrollment-time' | 'invalid-hash-algorithm' | 'invalid-hash-block-size' | 'invalid-hash-derived-key-length' | 'invalid-hash-key' | 'invalid-hash-memory-cost' | 'invalid-hash-parallelization' | 'invalid-hash-rounds' | 'invalid-hash-salt-separator' | 'invalid-last-sign-in-time' | 'invalid-name' | 'invalid-oauth-client-id' | 'invalid-page-token' | 'invalid-password' | 'invalid-password-hash' | 'invalid-password-salt' | 'invalid-phone-number' | 'invalid-photo-url' | 'invalid-project-id' | 'invalid-provider-data' | 'invalid-provider-id' | 'invalid-provider-uid' | 'invalid-oauth-responsetype' | 'invalid-session-cookie-duration' | 'invalid-tenant-id' | 'invalid-tenant-type' | 'invalid-testing-phone-number' | 'invalid-uid' | 'invalid-user-import' | 'invalid-tokens-valid-after-time' | 'mismatching-tenant-id' | 'missing-android-package-name' | 'missing-config' | 'missing-continue-uri' | 'missing-display-name' | 'missing-email' | 'missing-ios-bundle-id' | 'missing-issuer' | 'missing-hash-algorithm' | 'missing-oauth-client-id' | 'missing-oauth-client-secret' | 'missing-provider-id' | 'missing-saml-relying-party-config' | 'test-phone-number-limit-exceeded' | 'maximum-user-count-exceeded' | 'missing-uid' | 'operation-not-allowed' | 'phone-number-already-exists' | 'project-not-found' | 'insufficient-permission' | 'quota-exceeded' | 'second-factor-limit-exceeded' | 'second-factor-uid-already-exists' | 'session-cookie-expired' | 'session-cookie-revoked' | 'tenant-not-found' | 'uid-already-exists' | 'unauthorized-continue-uri' | 'unsupported-first-factor' | 'unsupported-second-factor' | 'unsupported-tenant-operation' | 'unverified-email' | 'user-not-found' | 'not-found' | 'user-disabled' | 'user-not-disabled' | 'invalid-recaptcha-action' | 'invalid-recaptcha-enforcement-state' | 'recaptcha-not-enabled'; +export const AuthErrorCode: { + readonly AUTH_BLOCKING_TOKEN_EXPIRED: "auth-blocking-token-expired"; + readonly BILLING_NOT_ENABLED: "billing-not-enabled"; + readonly CLAIMS_TOO_LARGE: "claims-too-large"; + readonly CONFIGURATION_EXISTS: "configuration-exists"; + readonly CONFIGURATION_NOT_FOUND: "configuration-not-found"; + readonly ID_TOKEN_EXPIRED: "id-token-expired"; + readonly INVALID_ARGUMENT: "argument-error"; + readonly INVALID_CONFIG: "invalid-config"; + readonly EMAIL_ALREADY_EXISTS: "email-already-exists"; + readonly EMAIL_NOT_FOUND: "email-not-found"; + readonly FORBIDDEN_CLAIM: "reserved-claim"; + readonly INVALID_ID_TOKEN: "invalid-id-token"; + readonly ID_TOKEN_REVOKED: "id-token-revoked"; + readonly INTERNAL_ERROR: "internal-error"; + readonly INVALID_CLAIMS: "invalid-claims"; + readonly INVALID_CONTINUE_URI: "invalid-continue-uri"; + readonly INVALID_CREATION_TIME: "invalid-creation-time"; + readonly INVALID_CREDENTIAL: "invalid-credential"; + readonly INVALID_DISABLED_FIELD: "invalid-disabled-field"; + readonly INVALID_DISPLAY_NAME: "invalid-display-name"; + readonly INVALID_DYNAMIC_LINK_DOMAIN: "invalid-dynamic-link-domain"; + readonly INVALID_HOSTING_LINK_DOMAIN: "invalid-hosting-link-domain"; + readonly INVALID_EMAIL_VERIFIED: "invalid-email-verified"; + readonly INVALID_EMAIL: "invalid-email"; + readonly INVALID_NEW_EMAIL: "invalid-new-email"; + readonly INVALID_ENROLLED_FACTORS: "invalid-enrolled-factors"; + readonly INVALID_ENROLLMENT_TIME: "invalid-enrollment-time"; + readonly INVALID_HASH_ALGORITHM: "invalid-hash-algorithm"; + readonly INVALID_HASH_BLOCK_SIZE: "invalid-hash-block-size"; + readonly INVALID_HASH_DERIVED_KEY_LENGTH: "invalid-hash-derived-key-length"; + readonly INVALID_HASH_KEY: "invalid-hash-key"; + readonly INVALID_HASH_MEMORY_COST: "invalid-hash-memory-cost"; + readonly INVALID_HASH_PARALLELIZATION: "invalid-hash-parallelization"; + readonly INVALID_HASH_ROUNDS: "invalid-hash-rounds"; + readonly INVALID_HASH_SALT_SEPARATOR: "invalid-hash-salt-separator"; + readonly INVALID_LAST_SIGN_IN_TIME: "invalid-last-sign-in-time"; + readonly INVALID_NAME: "invalid-name"; + readonly INVALID_OAUTH_CLIENT_ID: "invalid-oauth-client-id"; + readonly INVALID_PAGE_TOKEN: "invalid-page-token"; + readonly INVALID_PASSWORD: "invalid-password"; + readonly INVALID_PASSWORD_HASH: "invalid-password-hash"; + readonly INVALID_PASSWORD_SALT: "invalid-password-salt"; + readonly INVALID_PHONE_NUMBER: "invalid-phone-number"; + readonly INVALID_PHOTO_URL: "invalid-photo-url"; + readonly INVALID_PROJECT_ID: "invalid-project-id"; + readonly INVALID_PROVIDER_DATA: "invalid-provider-data"; + readonly INVALID_PROVIDER_ID: "invalid-provider-id"; + readonly INVALID_PROVIDER_UID: "invalid-provider-uid"; + readonly INVALID_OAUTH_RESPONSETYPE: "invalid-oauth-responsetype"; + readonly INVALID_SESSION_COOKIE_DURATION: "invalid-session-cookie-duration"; + readonly INVALID_TENANT_ID: "invalid-tenant-id"; + readonly INVALID_TENANT_TYPE: "invalid-tenant-type"; + readonly INVALID_TESTING_PHONE_NUMBER: "invalid-testing-phone-number"; + readonly INVALID_UID: "invalid-uid"; + readonly INVALID_USER_IMPORT: "invalid-user-import"; + readonly INVALID_TOKENS_VALID_AFTER_TIME: "invalid-tokens-valid-after-time"; + readonly MISMATCHING_TENANT_ID: "mismatching-tenant-id"; + readonly MISSING_ANDROID_PACKAGE_NAME: "missing-android-package-name"; + readonly MISSING_CONFIG: "missing-config"; + readonly MISSING_CONTINUE_URI: "missing-continue-uri"; + readonly MISSING_DISPLAY_NAME: "missing-display-name"; + readonly MISSING_EMAIL: "missing-email"; + readonly MISSING_IOS_BUNDLE_ID: "missing-ios-bundle-id"; + readonly MISSING_ISSUER: "missing-issuer"; + readonly MISSING_HASH_ALGORITHM: "missing-hash-algorithm"; + readonly MISSING_OAUTH_CLIENT_ID: "missing-oauth-client-id"; + readonly MISSING_OAUTH_CLIENT_SECRET: "missing-oauth-client-secret"; + readonly MISSING_PROVIDER_ID: "missing-provider-id"; + readonly MISSING_SAML_RELYING_PARTY_CONFIG: "missing-saml-relying-party-config"; + readonly MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED: "test-phone-number-limit-exceeded"; + readonly MAXIMUM_USER_COUNT_EXCEEDED: "maximum-user-count-exceeded"; + readonly MISSING_UID: "missing-uid"; + readonly OPERATION_NOT_ALLOWED: "operation-not-allowed"; + readonly PHONE_NUMBER_ALREADY_EXISTS: "phone-number-already-exists"; + readonly PROJECT_NOT_FOUND: "project-not-found"; + readonly INSUFFICIENT_PERMISSION: "insufficient-permission"; + readonly QUOTA_EXCEEDED: "quota-exceeded"; + readonly SECOND_FACTOR_LIMIT_EXCEEDED: "second-factor-limit-exceeded"; + readonly SECOND_FACTOR_UID_ALREADY_EXISTS: "second-factor-uid-already-exists"; + readonly SESSION_COOKIE_EXPIRED: "session-cookie-expired"; + readonly SESSION_COOKIE_REVOKED: "session-cookie-revoked"; + readonly TENANT_NOT_FOUND: "tenant-not-found"; + readonly UID_ALREADY_EXISTS: "uid-already-exists"; + readonly UNAUTHORIZED_DOMAIN: "unauthorized-continue-uri"; + readonly UNSUPPORTED_FIRST_FACTOR: "unsupported-first-factor"; + readonly UNSUPPORTED_SECOND_FACTOR: "unsupported-second-factor"; + readonly UNSUPPORTED_TENANT_OPERATION: "unsupported-tenant-operation"; + readonly UNVERIFIED_EMAIL: "unverified-email"; + readonly USER_NOT_FOUND: "user-not-found"; + readonly NOT_FOUND: "not-found"; + readonly USER_DISABLED: "user-disabled"; + readonly USER_NOT_DISABLED: "user-not-disabled"; + readonly INVALID_RECAPTCHA_ACTION: "invalid-recaptcha-action"; + readonly INVALID_RECAPTCHA_ENFORCEMENT_STATE: "invalid-recaptcha-enforcement-state"; + readonly RECAPTCHA_NOT_ENABLED: "recaptcha-not-enabled"; +}; + +// @public +export type AuthErrorCode = typeof AuthErrorCode[keyof typeof AuthErrorCode]; // @public export type AuthFactorType = 'phone'; diff --git a/etc/firebase-admin.data-connect.api.md b/etc/firebase-admin.data-connect.api.md index 39d10a5cfd..c86afd866b 100644 --- a/etc/firebase-admin.data-connect.api.md +++ b/etc/firebase-admin.data-connect.api.md @@ -39,7 +39,20 @@ export class DataConnect { } // @public -export type DataConnectErrorCode = 'aborted' | 'invalid-argument' | 'invalid-credential' | 'internal-error' | 'permission-denied' | 'unauthenticated' | 'not-found' | 'unknown-error' | 'query-error'; +export const DataConnectErrorCode: { + readonly ABORTED: "aborted"; + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INVALID_CREDENTIAL: "invalid-credential"; + readonly INTERNAL: "internal-error"; + readonly PERMISSION_DENIED: "permission-denied"; + readonly UNAUTHENTICATED: "unauthenticated"; + readonly NOT_FOUND: "not-found"; + readonly UNKNOWN: "unknown-error"; + readonly QUERY_ERROR: "query-error"; +}; + +// @public +export type DataConnectErrorCode = typeof DataConnectErrorCode[keyof typeof DataConnectErrorCode]; // @public export interface ExecuteGraphqlResponse { diff --git a/etc/firebase-admin.eventarc.api.md b/etc/firebase-admin.eventarc.api.md index e747c46116..8db2134e01 100644 --- a/etc/firebase-admin.eventarc.api.md +++ b/etc/firebase-admin.eventarc.api.md @@ -44,7 +44,13 @@ export class Eventarc { } // @public -export type EventarcErrorCode = 'unknown-error' | 'invalid-argument'; +export const EventarcErrorCode: { + readonly UNKNOWN_ERROR: "unknown-error"; + readonly INVALID_ARGUMENT: "invalid-argument"; +}; + +// @public +export type EventarcErrorCode = typeof EventarcErrorCode[keyof typeof EventarcErrorCode]; // Warning: (ae-forgotten-export) The symbol "PrefixedFirebaseError" needs to be exported by the entry point index.d.ts // diff --git a/etc/firebase-admin.extensions.api.md b/etc/firebase-admin.extensions.api.md index c8c1977fc3..3056b3c345 100644 --- a/etc/firebase-admin.extensions.api.md +++ b/etc/firebase-admin.extensions.api.md @@ -16,7 +16,16 @@ export class Extensions { } // @public -export type ExtensionsErrorCode = 'invalid-argument' | 'not-found' | 'forbidden' | 'internal-error' | 'unknown-error'; +export const ExtensionsErrorCode: { + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly NOT_FOUND: "not-found"; + readonly FORBIDDEN: "forbidden"; + readonly INTERNAL_ERROR: "internal-error"; + readonly UNKNOWN_ERROR: "unknown-error"; +}; + +// @public +export type ExtensionsErrorCode = typeof ExtensionsErrorCode[keyof typeof ExtensionsErrorCode]; // Warning: (ae-forgotten-export) The symbol "PrefixedFirebaseError" needs to be exported by the entry point index.d.ts // diff --git a/etc/firebase-admin.functions.api.md b/etc/firebase-admin.functions.api.md index 6f70b37e2d..6620991ebc 100644 --- a/etc/firebase-admin.functions.api.md +++ b/etc/firebase-admin.functions.api.md @@ -41,7 +41,21 @@ export class Functions { } // @public -export type FunctionsErrorCode = 'aborted' | 'invalid-argument' | 'invalid-credential' | 'internal-error' | 'failed-precondition' | 'permission-denied' | 'unauthenticated' | 'not-found' | 'unknown-error' | 'task-already-exists'; +export const FunctionsErrorCode: { + readonly ABORTED: "aborted"; + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INVALID_CREDENTIAL: "invalid-credential"; + readonly INTERNAL_ERROR: "internal-error"; + readonly FAILED_PRECONDITION: "failed-precondition"; + readonly PERMISSION_DENIED: "permission-denied"; + readonly UNAUTHENTICATED: "unauthenticated"; + readonly NOT_FOUND: "not-found"; + readonly UNKNOWN_ERROR: "unknown-error"; + readonly TASK_ALREADY_EXISTS: "task-already-exists"; +}; + +// @public +export type FunctionsErrorCode = typeof FunctionsErrorCode[keyof typeof FunctionsErrorCode]; // @public export function getFunctions(app?: App): Functions; diff --git a/etc/firebase-admin.installations.api.md b/etc/firebase-admin.installations.api.md index ba3a208046..f0c7a278c6 100644 --- a/etc/firebase-admin.installations.api.md +++ b/etc/firebase-admin.installations.api.md @@ -26,6 +26,14 @@ export class Installations { } // @public -export type InstallationsErrorCode = 'invalid-argument' | 'invalid-project-id' | 'invalid-installation-id' | 'api-error'; +export const InstallationsErrorCode: { + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INVALID_PROJECT_ID: "invalid-project-id"; + readonly INVALID_INSTALLATION_ID: "invalid-installation-id"; + readonly API_ERROR: "api-error"; +}; + +// @public +export type InstallationsErrorCode = typeof InstallationsErrorCode[keyof typeof InstallationsErrorCode]; ``` diff --git a/etc/firebase-admin.instance-id.api.md b/etc/firebase-admin.instance-id.api.md index 333b388117..20d748a49c 100644 --- a/etc/firebase-admin.instance-id.api.md +++ b/etc/firebase-admin.instance-id.api.md @@ -26,6 +26,15 @@ export class InstanceId { } // @public -export type InstanceIdErrorCode = 'invalid-argument' | 'invalid-project-id' | 'invalid-installation-id' | 'api-error' | 'invalid-instance-id'; +export const InstanceIdErrorCode: { + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INVALID_PROJECT_ID: "invalid-project-id"; + readonly INVALID_INSTALLATION_ID: "invalid-installation-id"; + readonly API_ERROR: "api-error"; + readonly INVALID_INSTANCE_ID: "invalid-instance-id"; +}; + +// @public +export type InstanceIdErrorCode = typeof InstanceIdErrorCode[keyof typeof InstanceIdErrorCode]; ``` diff --git a/etc/firebase-admin.machine-learning.api.md b/etc/firebase-admin.machine-learning.api.md index c222e1d6a1..04a390cf9d 100644 --- a/etc/firebase-admin.machine-learning.api.md +++ b/etc/firebase-admin.machine-learning.api.md @@ -53,7 +53,28 @@ export class MachineLearning { } // @public -export type MachineLearningErrorCode = 'already-exists' | 'authentication-error' | 'internal-error' | 'invalid-argument' | 'invalid-server-response' | 'not-found' | 'resource-exhausted' | 'service-unavailable' | 'unknown-error' | 'cancelled' | 'deadline-exceeded' | 'permission-denied' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'data-loss' | 'unauthenticated'; +export const MachineLearningErrorCode: { + readonly ALREADY_EXISTS: "already-exists"; + readonly AUTHENTICATION_ERROR: "authentication-error"; + readonly INTERNAL_ERROR: "internal-error"; + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INVALID_SERVER_RESPONSE: "invalid-server-response"; + readonly NOT_FOUND: "not-found"; + readonly RESOURCE_EXHAUSTED: "resource-exhausted"; + readonly SERVICE_UNAVAILABLE: "service-unavailable"; + readonly UNKNOWN_ERROR: "unknown-error"; + readonly CANCELLED: "cancelled"; + readonly DEADLINE_EXCEEDED: "deadline-exceeded"; + readonly PERMISSION_DENIED: "permission-denied"; + readonly FAILED_PRECONDITION: "failed-precondition"; + readonly ABORTED: "aborted"; + readonly OUT_OF_RANGE: "out-of-range"; + readonly DATA_LOSS: "data-loss"; + readonly UNAUTHENTICATED: "unauthenticated"; +}; + +// @public +export type MachineLearningErrorCode = typeof MachineLearningErrorCode[keyof typeof MachineLearningErrorCode]; // @public export class Model { diff --git a/etc/firebase-admin.messaging.api.md b/etc/firebase-admin.messaging.api.md index 919e6f6282..4c13ff1d27 100644 --- a/etc/firebase-admin.messaging.api.md +++ b/etc/firebase-admin.messaging.api.md @@ -204,7 +204,30 @@ export class Messaging { } // @public -export type MessagingErrorCode = 'invalid-argument' | 'invalid-recipient' | 'invalid-payload' | 'invalid-data-payload-key' | 'payload-size-limit-exceeded' | 'invalid-options' | 'invalid-registration-token' | 'registration-token-not-registered' | 'mismatched-credential' | 'invalid-package-name' | 'device-message-rate-exceeded' | 'topics-message-rate-exceeded' | 'message-rate-exceeded' | 'third-party-auth-error' | 'too-many-topics' | 'authentication-error' | 'server-unavailable' | 'internal-error' | 'unknown-error'; +export const MessagingErrorCode: { + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INVALID_RECIPIENT: "invalid-recipient"; + readonly INVALID_PAYLOAD: "invalid-payload"; + readonly INVALID_DATA_PAYLOAD_KEY: "invalid-data-payload-key"; + readonly PAYLOAD_SIZE_LIMIT_EXCEEDED: "payload-size-limit-exceeded"; + readonly INVALID_OPTIONS: "invalid-options"; + readonly INVALID_REGISTRATION_TOKEN: "invalid-registration-token"; + readonly REGISTRATION_TOKEN_NOT_REGISTERED: "registration-token-not-registered"; + readonly MISMATCHED_CREDENTIAL: "mismatched-credential"; + readonly INVALID_PACKAGE_NAME: "invalid-package-name"; + readonly DEVICE_MESSAGE_RATE_EXCEEDED: "device-message-rate-exceeded"; + readonly TOPICS_MESSAGE_RATE_EXCEEDED: "topics-message-rate-exceeded"; + readonly MESSAGE_RATE_EXCEEDED: "message-rate-exceeded"; + readonly THIRD_PARTY_AUTH_ERROR: "third-party-auth-error"; + readonly TOO_MANY_TOPICS: "too-many-topics"; + readonly AUTHENTICATION_ERROR: "authentication-error"; + readonly SERVER_UNAVAILABLE: "server-unavailable"; + readonly INTERNAL_ERROR: "internal-error"; + readonly UNKNOWN_ERROR: "unknown-error"; +}; + +// @public +export type MessagingErrorCode = typeof MessagingErrorCode[keyof typeof MessagingErrorCode]; // @public export interface MessagingOptions { diff --git a/etc/firebase-admin.phone-number-verification.api.md b/etc/firebase-admin.phone-number-verification.api.md index a3256e649b..9195516d62 100644 --- a/etc/firebase-admin.phone-number-verification.api.md +++ b/etc/firebase-admin.phone-number-verification.api.md @@ -25,8 +25,15 @@ export class PhoneNumberVerification { verifyToken(jwt: string): Promise; } -// @public (undocumented) -export type PhoneNumberVerificationErrorCode = 'invalid-argument' | 'invalid-token' | 'expired-token'; +// @public +export const PhoneNumberVerificationErrorCode: { + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INVALID_TOKEN: "invalid-token"; + readonly EXPIRED_TOKEN: "expired-token"; +}; + +// @public +export type PhoneNumberVerificationErrorCode = typeof PhoneNumberVerificationErrorCode[keyof typeof PhoneNumberVerificationErrorCode]; // @public export interface PhoneNumberVerificationToken { diff --git a/etc/firebase-admin.project-management.api.md b/etc/firebase-admin.project-management.api.md index 8b36425b23..061aaa7c38 100644 --- a/etc/firebase-admin.project-management.api.md +++ b/etc/firebase-admin.project-management.api.md @@ -86,7 +86,20 @@ export class ProjectManagement { } // @public -export type ProjectManagementErrorCode = 'already-exists' | 'authentication-error' | 'internal-error' | 'invalid-argument' | 'invalid-project-id' | 'invalid-server-response' | 'not-found' | 'service-unavailable' | 'unknown-error'; +export const ProjectManagementErrorCode: { + readonly ALREADY_EXISTS: "already-exists"; + readonly AUTHENTICATION_ERROR: "authentication-error"; + readonly INTERNAL_ERROR: "internal-error"; + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INVALID_PROJECT_ID: "invalid-project-id"; + readonly INVALID_SERVER_RESPONSE: "invalid-server-response"; + readonly NOT_FOUND: "not-found"; + readonly SERVICE_UNAVAILABLE: "service-unavailable"; + readonly UNKNOWN_ERROR: "unknown-error"; +}; + +// @public +export type ProjectManagementErrorCode = typeof ProjectManagementErrorCode[keyof typeof ProjectManagementErrorCode]; // @public export class ShaCertificate { diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md index 14d4743ba6..858cbdf8d7 100644 --- a/etc/firebase-admin.remote-config.api.md +++ b/etc/firebase-admin.remote-config.api.md @@ -218,7 +218,22 @@ export interface RemoteConfigCondition { } // @public -export type RemoteConfigErrorCode = 'aborted' | 'already-exists' | 'failed-precondition' | 'internal-error' | 'invalid-argument' | 'not-found' | 'out-of-range' | 'permission-denied' | 'resource-exhausted' | 'unauthenticated' | 'unknown-error'; +export const RemoteConfigErrorCode: { + readonly ABORTED: "aborted"; + readonly ALREADY_EXISTS: "already-exists"; + readonly FAILED_PRECONDITION: "failed-precondition"; + readonly INTERNAL_ERROR: "internal-error"; + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly NOT_FOUND: "not-found"; + readonly OUT_OF_RANGE: "out-of-range"; + readonly PERMISSION_DENIED: "permission-denied"; + readonly RESOURCE_EXHAUSTED: "resource-exhausted"; + readonly UNAUTHENTICATED: "unauthenticated"; + readonly UNKNOWN_ERROR: "unknown-error"; +}; + +// @public +export type RemoteConfigErrorCode = typeof RemoteConfigErrorCode[keyof typeof RemoteConfigErrorCode]; // @public export class RemoteConfigFetchResponse { diff --git a/etc/firebase-admin.security-rules.api.md b/etc/firebase-admin.security-rules.api.md index d78a6ad248..c9569fdde0 100644 --- a/etc/firebase-admin.security-rules.api.md +++ b/etc/firebase-admin.security-rules.api.md @@ -65,6 +65,19 @@ export class SecurityRules { } // @public -export type SecurityRulesErrorCode = 'already-exists' | 'authentication-error' | 'internal-error' | 'invalid-argument' | 'invalid-server-response' | 'not-found' | 'resource-exhausted' | 'service-unavailable' | 'unknown-error'; +export const SecurityRulesErrorCode: { + readonly ALREADY_EXISTS: "already-exists"; + readonly AUTHENTICATION_ERROR: "authentication-error"; + readonly INTERNAL_ERROR: "internal-error"; + readonly INVALID_ARGUMENT: "invalid-argument"; + readonly INVALID_SERVER_RESPONSE: "invalid-server-response"; + readonly NOT_FOUND: "not-found"; + readonly RESOURCE_EXHAUSTED: "resource-exhausted"; + readonly SERVICE_UNAVAILABLE: "service-unavailable"; + readonly UNKNOWN_ERROR: "unknown-error"; +}; + +// @public +export type SecurityRulesErrorCode = typeof SecurityRulesErrorCode[keyof typeof SecurityRulesErrorCode]; ``` diff --git a/src/app-check/error.ts b/src/app-check/error.ts index eedea862a4..5b25b29ba4 100644 --- a/src/app-check/error.ts +++ b/src/app-check/error.ts @@ -29,18 +29,24 @@ export const APP_CHECK_ERROR_CODE_MAPPING: Record = { }; /** - * App Check client error codes and their default messages. + * The constant mapping for valid App Check client error codes. */ -export type AppCheckErrorCode = - | 'aborted' - | 'invalid-argument' - | 'invalid-credential' - | 'internal-error' - | 'permission-denied' - | 'unauthenticated' - | 'not-found' - | 'app-check-token-expired' - | 'unknown-error'; +export const AppCheckErrorCode = { + ABORTED: 'aborted', + INVALID_ARGUMENT: 'invalid-argument', + INVALID_CREDENTIAL: 'invalid-credential', + INTERNAL: 'internal-error', + PERMISSION_DENIED: 'permission-denied', + UNAUTHENTICATED: 'unauthenticated', + NOT_FOUND: 'not-found', + APP_CHECK_TOKEN_EXPIRED: 'app-check-token-expired', + UNKNOWN: 'unknown-error', +} as const; + +/** + * The type definition for valid App Check client error codes. + */ +export type AppCheckErrorCode = typeof AppCheckErrorCode[keyof typeof AppCheckErrorCode]; /** * Firebase App Check error type. This extends PrefixedFirebaseError. diff --git a/src/app/credential-internal.ts b/src/app/credential-internal.ts index c6cb1b6fd6..fe89fc3360 100644 --- a/src/app/credential-internal.ts +++ b/src/app/credential-internal.ts @@ -20,7 +20,7 @@ import fs = require('fs'); import { Credentials as GoogleAuthCredentials, GoogleAuth, Compute, AnyAuthClient } from 'google-auth-library' import { Agent } from 'http'; import { Credential, GoogleOAuthAccessToken } from './credential'; -import { AppErrorCodes, FirebaseAppError } from './error'; +import { AppErrorCode, FirebaseAppError } from './error'; import * as util from '../utils/validator'; const SCOPES = [ @@ -95,7 +95,7 @@ export class ApplicationDefaultCredential implements Credential { } else { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: 'Credentials type should be Compute Engine Credentials.' }); } @@ -183,7 +183,7 @@ class ServiceAccount { } catch (error) { // Throw a nicely formed error message if the file contents cannot be parsed throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: `Failed to parse service account json file: ${(error as Error).message}`, cause: error, }); @@ -193,7 +193,7 @@ class ServiceAccount { constructor(json: object) { if (!util.isNonNullObject(json)) { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: 'Service account must be an object.' }); } @@ -212,7 +212,7 @@ class ServiceAccount { } if (typeof errorMessage !== 'undefined') { - throw new FirebaseAppError({ code: AppErrorCodes.INVALID_CREDENTIAL, message: errorMessage }); + throw new FirebaseAppError({ code: AppErrorCode.INVALID_CREDENTIAL, message: errorMessage }); } // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -221,7 +221,7 @@ class ServiceAccount { forge.pki.privateKeyFromPem(this.privateKey); } catch (error) { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: 'Failed to parse private key.', cause: error, }); @@ -296,7 +296,7 @@ class RefreshToken { } catch (error) { // Throw a nicely formed error message if the file contents cannot be parsed throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: 'Failed to parse refresh token file.', cause: error, }); @@ -324,7 +324,7 @@ class RefreshToken { } if (typeof errorMessage !== 'undefined') { - throw new FirebaseAppError({ code: AppErrorCodes.INVALID_CREDENTIAL, message: errorMessage }); + throw new FirebaseAppError({ code: AppErrorCode.INVALID_CREDENTIAL, message: errorMessage }); } } } @@ -394,7 +394,7 @@ class ImpersonatedServiceAccount { } catch (error) { // Throw a nicely formed error message if the file contents cannot be parsed throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: 'Failed to parse impersonated service account file.', cause: error, }); @@ -418,7 +418,7 @@ class ImpersonatedServiceAccount { } if (typeof errorMessage !== 'undefined') { - throw new FirebaseAppError({ code: AppErrorCodes.INVALID_CREDENTIAL, message: errorMessage }); + throw new FirebaseAppError({ code: AppErrorCode.INVALID_CREDENTIAL, message: errorMessage }); } } } @@ -474,7 +474,7 @@ function populateGoogleAuth(keyFile: string | object, httpAgent?: Agent) if (typeof keyFile === 'object') { if (!util.isNonNullObject(keyFile)) { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: 'Service account must be an object.' }); } @@ -496,12 +496,12 @@ function populateCredential(credentials?: GoogleAuthCredentials): GoogleOAuthAcc if (typeof accessToken !== 'string') throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: 'Failed to parse Google auth credential: access_token must be a non empty string.' }); if (typeof expiryDate !== 'number') throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: 'Failed to parse Google auth credential: Invalid expiry_date.' }); diff --git a/src/app/error.ts b/src/app/error.ts index 6c7a361063..76122c397d 100644 --- a/src/app/error.ts +++ b/src/app/error.ts @@ -31,18 +31,25 @@ export class FirebaseAppError extends PrefixedFirebaseError { } /** - * App client error codes and their default messages. + * The constant mapping for valid App client error codes. */ -export class AppErrorCodes { - public static APP_DELETED = 'app-deleted'; - public static DUPLICATE_APP = 'duplicate-app'; - public static INVALID_ARGUMENT = 'invalid-argument'; - public static INTERNAL_ERROR = 'internal-error'; - public static INVALID_APP_NAME = 'invalid-app-name'; - public static INVALID_APP_OPTIONS = 'invalid-app-options'; - public static INVALID_CREDENTIAL = 'invalid-credential'; - public static NETWORK_ERROR = 'network-error'; - public static NETWORK_TIMEOUT = 'network-timeout'; - public static NO_APP = 'no-app'; - public static UNABLE_TO_PARSE_RESPONSE = 'unable-to-parse-response'; -} +export const AppErrorCode = { + APP_DELETED: 'app-deleted', + DUPLICATE_APP: 'duplicate-app', + INVALID_ARGUMENT: 'invalid-argument', + INTERNAL_ERROR: 'internal-error', + INVALID_APP_NAME: 'invalid-app-name', + INVALID_APP_OPTIONS: 'invalid-app-options', + INVALID_CREDENTIAL: 'invalid-credential', + NETWORK_ERROR: 'network-error', + NETWORK_TIMEOUT: 'network-timeout', + NO_APP: 'no-app', + UNABLE_TO_PARSE_RESPONSE: 'unable-to-parse-response', +} as const; + +/** + * The type definition for valid App client error codes. + */ +export type AppErrorCode = typeof AppErrorCode[keyof typeof AppErrorCode]; + + diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 158aec31ab..bc48772f9f 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -21,7 +21,7 @@ import { Credential } from './credential'; import { getApplicationDefault } from './credential-internal'; import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; -import { AppErrorCodes, FirebaseAppError } from './error'; +import { AppErrorCode, FirebaseAppError } from './error'; const TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1000; @@ -70,7 +70,7 @@ export class FirebaseAppInternals { typeof result.expires_in !== 'number' || typeof result.access_token !== 'string') { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: `Invalid access token generated: "${JSON.stringify(result)}". Valid access ` + 'tokens must be an object with the "expires_in" (number) and "access_token" ' + '(string) properties.', @@ -111,7 +111,7 @@ export class FirebaseAppInternals { } throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: errorMessage, cause: error as Error }); @@ -171,7 +171,7 @@ export class FirebaseApp implements App { if (!validator.isNonNullObject(this.options_)) { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_APP_OPTIONS, + code: AppErrorCode.INVALID_APP_OPTIONS, message: 'Invalid Firebase app options passed as the first argument to initializeApp() for the ' + `app named "${this.name_}". Options must be a non-null object.` }); @@ -186,7 +186,7 @@ export class FirebaseApp implements App { const credential = this.options_.credential; if (typeof credential !== 'object' || credential === null || typeof credential.getAccessToken !== 'function') { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_APP_OPTIONS, + code: AppErrorCode.INVALID_APP_OPTIONS, message: 'Invalid Firebase app options passed as the first argument to initializeApp() for the ' + `app named "${this.name_}". The "credential" property must be an object which implements ` + 'the Credential interface.' @@ -284,7 +284,7 @@ export class FirebaseApp implements App { private checkDestroyed_(): void { if (this.isDeleted_) { throw new FirebaseAppError({ - code: AppErrorCodes.APP_DELETED, + code: AppErrorCode.APP_DELETED, message: `Firebase app named "${this.name_}" has already been deleted.` }); } diff --git a/src/app/index.ts b/src/app/index.ts index f441777fff..b491c0ad93 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -30,6 +30,6 @@ export { Credential, ServiceAccount, GoogleOAuthAccessToken } from './credential export { applicationDefault, cert, refreshToken } from './credential-factory'; export { FirebaseError, ErrorInfo, HttpResponse } from '../utils/error'; -export { FirebaseAppError, AppErrorCodes } from './error'; +export { FirebaseAppError, AppErrorCode } from './error'; export const SDK_VERSION = getSdkVersion(); diff --git a/src/app/lifecycle.ts b/src/app/lifecycle.ts index 606a8f5825..b2414ab3b6 100644 --- a/src/app/lifecycle.ts +++ b/src/app/lifecycle.ts @@ -18,7 +18,7 @@ import fs = require('fs'); import * as validator from '../utils/validator'; -import { AppErrorCodes, FirebaseAppError } from './error'; +import { AppErrorCode, FirebaseAppError } from './error'; import { App, AppOptions } from './core'; import { getApplicationDefault } from './credential-internal'; import { FirebaseApp } from './firebase-app'; @@ -52,7 +52,7 @@ export class AppStore { // Ensure the `autoInit` state matches the existing app's. If not, throw. if (currentApp.autoInit() !== autoInit) { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_APP_OPTIONS, + code: AppErrorCode.INVALID_APP_OPTIONS, message: `A Firebase app named "${appName}" already exists with a different configuration.` }); } @@ -73,7 +73,7 @@ export class AppStore { delete currentAppOptions.credential; if (!fastDeepEqual(options, currentAppOptions)) { throw new FirebaseAppError({ - code: AppErrorCodes.DUPLICATE_APP, + code: AppErrorCode.DUPLICATE_APP, message: `A Firebase app named "${appName}" already exists with a different configuration.` }); @@ -89,7 +89,7 @@ export class AppStore { ? 'The default Firebase app does not exist. ' : `Firebase app named "${appName}" does not exist. `; errorMessage += 'Make sure you call initializeApp() before using any of the Firebase services.'; - throw new FirebaseAppError({ code: AppErrorCodes.NO_APP, message: errorMessage }); + throw new FirebaseAppError({ code: AppErrorCode.NO_APP, message: errorMessage }); } return this.appStore.get(appName)!; @@ -102,7 +102,7 @@ export class AppStore { public deleteApp(app: App): Promise { if (typeof app !== 'object' || app === null || !('options' in app)) { - throw new FirebaseAppError({ code: AppErrorCodes.INVALID_ARGUMENT, message: 'Invalid app argument.' }); + throw new FirebaseAppError({ code: AppErrorCode.INVALID_ARGUMENT, message: 'Invalid app argument.' }); } // Make sure the given app already exists. @@ -152,7 +152,7 @@ function validateAppOptionsSupportDeepEquals( // http.Agent checks. if (typeof requestedOptions.httpAgent !== 'undefined') { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_APP_OPTIONS, + code: AppErrorCode.INVALID_APP_OPTIONS, message: `Firebase app named "${existingApp.name}" already exists and initializeApp was` + ' invoked with an optional http.Agent. The SDK cannot confirm the equality' + ' of http.Agent objects with the existing app. Please use getApp or getApps to reuse' + @@ -160,7 +160,7 @@ function validateAppOptionsSupportDeepEquals( }); } else if (typeof existingApp.options.httpAgent !== 'undefined') { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_APP_OPTIONS, + code: AppErrorCode.INVALID_APP_OPTIONS, message: `An existing app named "${existingApp.name}" already exists with a different` + ' options configuration: httpAgent.' }); @@ -169,7 +169,7 @@ function validateAppOptionsSupportDeepEquals( // Credential checks. if (typeof requestedOptions.credential !== 'undefined') { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_APP_OPTIONS, + code: AppErrorCode.INVALID_APP_OPTIONS, message: `Firebase app named "${existingApp.name}" already exists and initializeApp was` + ' invoked with an optional Credential. The SDK cannot confirm the equality' + ' of Credential objects with the existing app. Please use getApp or getApps' + @@ -179,7 +179,7 @@ function validateAppOptionsSupportDeepEquals( if (existingApp.customCredential()) { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_APP_OPTIONS, + code: AppErrorCode.INVALID_APP_OPTIONS, message: `An existing app named "${existingApp.name}" already exists with a different` + ' options configuration: Credential.' }); @@ -199,7 +199,7 @@ function validateAppOptionsSupportDeepEquals( function validateAppNameFormat(appName: string): void { if (!validator.isNonEmptyString(appName)) { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_APP_NAME, + code: AppErrorCode.INVALID_APP_NAME, message: `Invalid Firebase app name "${appName}" provided. App name must be a non-empty string.` }); } @@ -321,7 +321,7 @@ function loadOptionsFromEnvVar(): AppOptions { } catch (error) { // Throw a nicely formed error message if the file contents cannot be parsed throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_APP_OPTIONS, + code: AppErrorCode.INVALID_APP_OPTIONS, message: `Failed to parse app options file: ${(error as Error).message}`, cause: error as Error }); diff --git a/src/auth/error.ts b/src/auth/error.ts index a301ca785c..3d29020ee2 100644 --- a/src/auth/error.ts +++ b/src/auth/error.ts @@ -19,221 +19,503 @@ import { RequestResponseError } from '../utils/api-request'; import { deepCopy } from '../utils/deep-copy'; /** -* Auth client error codes. -*/ -export type AuthErrorCode = - | 'auth-blocking-token-expired' - | 'billing-not-enabled' - | 'claims-too-large' - | 'configuration-exists' - | 'configuration-not-found' - | 'id-token-expired' - | 'argument-error' - | 'invalid-config' - | 'email-already-exists' - | 'email-not-found' - | 'reserved-claim' - | 'invalid-id-token' - | 'id-token-revoked' - | 'internal-error' - | 'invalid-claims' - | 'invalid-continue-uri' - | 'invalid-creation-time' - | 'invalid-credential' - | 'invalid-disabled-field' - | 'invalid-display-name' - | 'invalid-dynamic-link-domain' - | 'invalid-hosting-link-domain' - | 'invalid-email-verified' - | 'invalid-email' - | 'invalid-new-email' - | 'invalid-enrolled-factors' - | 'invalid-enrollment-time' - | 'invalid-hash-algorithm' - | 'invalid-hash-block-size' - | 'invalid-hash-derived-key-length' - | 'invalid-hash-key' - | 'invalid-hash-memory-cost' - | 'invalid-hash-parallelization' - | 'invalid-hash-rounds' - | 'invalid-hash-salt-separator' - | 'invalid-last-sign-in-time' - | 'invalid-name' - | 'invalid-oauth-client-id' - | 'invalid-page-token' - | 'invalid-password' - | 'invalid-password-hash' - | 'invalid-password-salt' - | 'invalid-phone-number' - | 'invalid-photo-url' - | 'invalid-project-id' - | 'invalid-provider-data' - | 'invalid-provider-id' - | 'invalid-provider-uid' - | 'invalid-oauth-responsetype' - | 'invalid-session-cookie-duration' - | 'invalid-tenant-id' - | 'invalid-tenant-type' - | 'invalid-testing-phone-number' - | 'invalid-uid' - | 'invalid-user-import' - | 'invalid-tokens-valid-after-time' - | 'mismatching-tenant-id' - | 'missing-android-package-name' - | 'missing-config' - | 'missing-continue-uri' - | 'missing-display-name' - | 'missing-email' - | 'missing-ios-bundle-id' - | 'missing-issuer' - | 'missing-hash-algorithm' - | 'missing-oauth-client-id' - | 'missing-oauth-client-secret' - | 'missing-provider-id' - | 'missing-saml-relying-party-config' - | 'test-phone-number-limit-exceeded' - | 'maximum-user-count-exceeded' - | 'missing-uid' - | 'operation-not-allowed' - | 'phone-number-already-exists' - | 'project-not-found' - | 'insufficient-permission' - | 'quota-exceeded' - | 'second-factor-limit-exceeded' - | 'second-factor-uid-already-exists' - | 'session-cookie-expired' - | 'session-cookie-revoked' - | 'tenant-not-found' - | 'uid-already-exists' - | 'unauthorized-continue-uri' - | 'unsupported-first-factor' - | 'unsupported-second-factor' - | 'unsupported-tenant-operation' - | 'unverified-email' - | 'user-not-found' - | 'not-found' - | 'user-disabled' - | 'user-not-disabled' - | 'invalid-recaptcha-action' - | 'invalid-recaptcha-enforcement-state' - | 'recaptcha-not-enabled'; + * The constant mapping for valid Auth client error codes. + */ +export const AuthErrorCode = { + AUTH_BLOCKING_TOKEN_EXPIRED: 'auth-blocking-token-expired', + BILLING_NOT_ENABLED: 'billing-not-enabled', + CLAIMS_TOO_LARGE: 'claims-too-large', + CONFIGURATION_EXISTS: 'configuration-exists', + CONFIGURATION_NOT_FOUND: 'configuration-not-found', + ID_TOKEN_EXPIRED: 'id-token-expired', + INVALID_ARGUMENT: 'argument-error', + INVALID_CONFIG: 'invalid-config', + EMAIL_ALREADY_EXISTS: 'email-already-exists', + EMAIL_NOT_FOUND: 'email-not-found', + FORBIDDEN_CLAIM: 'reserved-claim', + INVALID_ID_TOKEN: 'invalid-id-token', + ID_TOKEN_REVOKED: 'id-token-revoked', + INTERNAL_ERROR: 'internal-error', + INVALID_CLAIMS: 'invalid-claims', + INVALID_CONTINUE_URI: 'invalid-continue-uri', + INVALID_CREATION_TIME: 'invalid-creation-time', + INVALID_CREDENTIAL: 'invalid-credential', + INVALID_DISABLED_FIELD: 'invalid-disabled-field', + INVALID_DISPLAY_NAME: 'invalid-display-name', + INVALID_DYNAMIC_LINK_DOMAIN: 'invalid-dynamic-link-domain', + INVALID_HOSTING_LINK_DOMAIN: 'invalid-hosting-link-domain', + INVALID_EMAIL_VERIFIED: 'invalid-email-verified', + INVALID_EMAIL: 'invalid-email', + INVALID_NEW_EMAIL: 'invalid-new-email', + INVALID_ENROLLED_FACTORS: 'invalid-enrolled-factors', + INVALID_ENROLLMENT_TIME: 'invalid-enrollment-time', + INVALID_HASH_ALGORITHM: 'invalid-hash-algorithm', + INVALID_HASH_BLOCK_SIZE: 'invalid-hash-block-size', + INVALID_HASH_DERIVED_KEY_LENGTH: 'invalid-hash-derived-key-length', + INVALID_HASH_KEY: 'invalid-hash-key', + INVALID_HASH_MEMORY_COST: 'invalid-hash-memory-cost', + INVALID_HASH_PARALLELIZATION: 'invalid-hash-parallelization', + INVALID_HASH_ROUNDS: 'invalid-hash-rounds', + INVALID_HASH_SALT_SEPARATOR: 'invalid-hash-salt-separator', + INVALID_LAST_SIGN_IN_TIME: 'invalid-last-sign-in-time', + INVALID_NAME: 'invalid-name', + INVALID_OAUTH_CLIENT_ID: 'invalid-oauth-client-id', + INVALID_PAGE_TOKEN: 'invalid-page-token', + INVALID_PASSWORD: 'invalid-password', + INVALID_PASSWORD_HASH: 'invalid-password-hash', + INVALID_PASSWORD_SALT: 'invalid-password-salt', + INVALID_PHONE_NUMBER: 'invalid-phone-number', + INVALID_PHOTO_URL: 'invalid-photo-url', + INVALID_PROJECT_ID: 'invalid-project-id', + INVALID_PROVIDER_DATA: 'invalid-provider-data', + INVALID_PROVIDER_ID: 'invalid-provider-id', + INVALID_PROVIDER_UID: 'invalid-provider-uid', + INVALID_OAUTH_RESPONSETYPE: 'invalid-oauth-responsetype', + INVALID_SESSION_COOKIE_DURATION: 'invalid-session-cookie-duration', + INVALID_TENANT_ID: 'invalid-tenant-id', + INVALID_TENANT_TYPE: 'invalid-tenant-type', + INVALID_TESTING_PHONE_NUMBER: 'invalid-testing-phone-number', + INVALID_UID: 'invalid-uid', + INVALID_USER_IMPORT: 'invalid-user-import', + INVALID_TOKENS_VALID_AFTER_TIME: 'invalid-tokens-valid-after-time', + MISMATCHING_TENANT_ID: 'mismatching-tenant-id', + MISSING_ANDROID_PACKAGE_NAME: 'missing-android-package-name', + MISSING_CONFIG: 'missing-config', + MISSING_CONTINUE_URI: 'missing-continue-uri', + MISSING_DISPLAY_NAME: 'missing-display-name', + MISSING_EMAIL: 'missing-email', + MISSING_IOS_BUNDLE_ID: 'missing-ios-bundle-id', + MISSING_ISSUER: 'missing-issuer', + MISSING_HASH_ALGORITHM: 'missing-hash-algorithm', + MISSING_OAUTH_CLIENT_ID: 'missing-oauth-client-id', + MISSING_OAUTH_CLIENT_SECRET: 'missing-oauth-client-secret', + MISSING_PROVIDER_ID: 'missing-provider-id', + MISSING_SAML_RELYING_PARTY_CONFIG: 'missing-saml-relying-party-config', + MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED: 'test-phone-number-limit-exceeded', + MAXIMUM_USER_COUNT_EXCEEDED: 'maximum-user-count-exceeded', + MISSING_UID: 'missing-uid', + OPERATION_NOT_ALLOWED: 'operation-not-allowed', + PHONE_NUMBER_ALREADY_EXISTS: 'phone-number-already-exists', + PROJECT_NOT_FOUND: 'project-not-found', + INSUFFICIENT_PERMISSION: 'insufficient-permission', + QUOTA_EXCEEDED: 'quota-exceeded', + SECOND_FACTOR_LIMIT_EXCEEDED: 'second-factor-limit-exceeded', + SECOND_FACTOR_UID_ALREADY_EXISTS: 'second-factor-uid-already-exists', + SESSION_COOKIE_EXPIRED: 'session-cookie-expired', + SESSION_COOKIE_REVOKED: 'session-cookie-revoked', + TENANT_NOT_FOUND: 'tenant-not-found', + UID_ALREADY_EXISTS: 'uid-already-exists', + UNAUTHORIZED_DOMAIN: 'unauthorized-continue-uri', + UNSUPPORTED_FIRST_FACTOR: 'unsupported-first-factor', + UNSUPPORTED_SECOND_FACTOR: 'unsupported-second-factor', + UNSUPPORTED_TENANT_OPERATION: 'unsupported-tenant-operation', + UNVERIFIED_EMAIL: 'unverified-email', + USER_NOT_FOUND: 'user-not-found', + NOT_FOUND: 'not-found', + USER_DISABLED: 'user-disabled', + USER_NOT_DISABLED: 'user-not-disabled', + INVALID_RECAPTCHA_ACTION: 'invalid-recaptcha-action', + INVALID_RECAPTCHA_ENFORCEMENT_STATE: 'invalid-recaptcha-enforcement-state', + RECAPTCHA_NOT_ENABLED: 'recaptcha-not-enabled', +} as const; + +/** + * The type definition for valid Auth client error codes. + */ +export type AuthErrorCode = typeof AuthErrorCode[keyof typeof AuthErrorCode]; /** -* Auth client error codes and their default messages. -*/ -const authClientErrorMessages: Record = { - 'auth-blocking-token-expired': 'The provided Firebase Auth Blocking token is expired.', - 'billing-not-enabled': 'Feature requires billing to be enabled.', - 'claims-too-large': 'Developer claims maximum payload size exceeded.', - 'configuration-exists': 'A configuration already exists with the provided identifier.', - 'configuration-not-found': 'There is no configuration corresponding to the provided identifier.', - 'id-token-expired': 'The provided Firebase ID token is expired.', - 'argument-error': 'Invalid argument provided.', - 'invalid-config': 'The provided configuration is invalid.', - 'email-already-exists': 'The email address is already in use by another account.', - 'email-not-found': 'There is no user record corresponding to the provided email.', - 'reserved-claim': 'The specified developer claim is reserved and cannot be specified.', - 'invalid-id-token': 'The provided ID token is not a valid Firebase ID token.', - 'id-token-revoked': 'The Firebase ID token has been revoked.', - 'internal-error': 'An internal error has occurred.', - 'invalid-claims': 'The provided custom claim attributes are invalid.', - 'invalid-continue-uri': 'The continue URL must be a valid URL string.', - 'invalid-creation-time': 'The creation time must be a valid UTC date string.', - 'invalid-credential': 'Invalid credential object provided.', - 'invalid-disabled-field': 'The disabled field must be a boolean.', - 'invalid-display-name': 'The displayName field must be a valid string.', - 'invalid-dynamic-link-domain': 'The provided dynamic link domain is not configured or authorized ' + - 'for the current project.', - 'invalid-hosting-link-domain': 'The provided hosting link domain is not configured in Firebase ' + - 'Hosting or is not owned by the current project.', - 'invalid-email-verified': 'The emailVerified field must be a boolean.', - 'invalid-email': 'The email address is improperly formatted.', - 'invalid-new-email': 'The new email address is improperly formatted.', - 'invalid-enrolled-factors': 'The enrolled factors must be a valid array of MultiFactorInfo objects.', - 'invalid-enrollment-time': 'The second factor enrollment time must be a valid UTC date string.', - 'invalid-hash-algorithm': 'The hash algorithm must match one of the strings in the list of ' + - 'supported algorithms.', - 'invalid-hash-block-size': 'The hash block size must be a valid number.', - 'invalid-hash-derived-key-length': 'The hash derived key length must be a valid number.', - 'invalid-hash-key': 'The hash key must a valid byte buffer.', - 'invalid-hash-memory-cost': 'The hash memory cost must be a valid number.', - 'invalid-hash-parallelization': 'The hash parallelization must be a valid number.', - 'invalid-hash-rounds': 'The hash rounds must be a valid number.', - 'invalid-hash-salt-separator': 'The hashing algorithm salt separator field must be a valid byte buffer.', - 'invalid-last-sign-in-time': 'The last sign-in time must be a valid UTC date string.', - 'invalid-name': 'The resource name provided is invalid.', - 'invalid-oauth-client-id': 'The provided OAuth client ID is invalid.', - 'invalid-page-token': 'The page token must be a valid non-empty string.', - 'invalid-password': 'The password must be a string with at least 6 characters.', - 'invalid-password-hash': 'The password hash must be a valid byte buffer.', - 'invalid-password-salt': 'The password salt must be a valid byte buffer.', - 'invalid-phone-number': 'The phone number must be a non-empty E.164 standard compliant identifier string.', - 'invalid-photo-url': 'The photoURL field must be a valid URL.', - 'invalid-project-id': 'Invalid parent project. Either parent project doesn\'t exist or didn\'t enable multi-tenancy.', - 'invalid-provider-data': 'The providerData must be a valid array of UserInfo objects.', - 'invalid-provider-id': 'The providerId must be a valid supported provider identifier string.', - 'invalid-provider-uid': 'The providerUid must be a valid provider uid string.', - 'invalid-oauth-responsetype': 'Only exactly one OAuth responseType should be set to true.', - 'invalid-session-cookie-duration': 'The session cookie duration must be a valid number in milliseconds ' + - 'between 5 minutes and 2 weeks.', - 'invalid-tenant-id': 'The tenant ID must be a valid non-empty string.', - 'invalid-tenant-type': 'Tenant type must be either "full_service" or "lightweight".', - 'invalid-testing-phone-number': 'Invalid testing phone number or invalid test code provided.', - 'invalid-uid': 'The uid must be a non-empty string with at most 128 characters.', - 'invalid-user-import': 'The user record to import is invalid.', - 'invalid-tokens-valid-after-time': 'The tokensValidAfterTime must be a valid UTC number in seconds.', - 'mismatching-tenant-id': 'User tenant ID does not match with the current TenantAwareAuth tenant ID.', - 'missing-android-package-name': 'An Android Package Name must be provided if the Android App is required ' + - 'to be installed.', - 'missing-config': 'The provided configuration is missing required attributes.', - 'missing-continue-uri': 'A valid continue URL must be provided in the request.', - 'missing-display-name': 'The resource being created or edited is missing a valid display name.', - 'missing-email': 'The email is required for the specified action. For example, a multi-factor user requires ' + - 'a verified email.', - 'missing-ios-bundle-id': 'The request is missing an iOS Bundle ID.', - 'missing-issuer': 'The OAuth/OIDC configuration issuer must not be empty.', - 'missing-hash-algorithm': 'Importing users with password hashes requires that the hashing algorithm and its ' + - 'parameters be provided.', - 'missing-oauth-client-id': 'The OAuth/OIDC configuration client ID must not be empty.', - 'missing-oauth-client-secret': 'The OAuth configuration client secret is required to enable OIDC code flow.', - 'missing-provider-id': 'A valid provider ID must be provided in the request.', - 'missing-saml-relying-party-config': 'The SAML configuration provided is missing a relying party configuration.', - 'test-phone-number-limit-exceeded': 'The maximum allowed number of test phone number / code pairs has been exceeded.', - 'maximum-user-count-exceeded': 'The maximum allowed number of users to import has been exceeded.', - 'missing-uid': 'A uid identifier is required for the current operation.', - 'operation-not-allowed': 'The given sign-in provider is disabled for this Firebase project. ' + - 'Enable it in the Firebase console, under the sign-in method tab of the Auth section.', - 'phone-number-already-exists': 'The user with the provided phone number already exists.', - 'project-not-found': 'No Firebase project was found for the provided credential.', - 'insufficient-permission': 'Credential implementation provided to initializeApp() via the ' + - '"credential" property has insufficient permission to access the requested resource. See ' + - 'https://firebase.google.com/docs/admin/setup for details on how to authenticate this SDK ' + - 'with appropriate permissions.', - 'quota-exceeded': 'The project quota for the specified operation has been exceeded.', - 'second-factor-limit-exceeded': 'The maximum number of allowed second factors on a user has been exceeded.', - 'second-factor-uid-already-exists': 'The specified second factor "uid" already exists.', - 'session-cookie-expired': 'The Firebase session cookie is expired.', - 'session-cookie-revoked': 'The Firebase session cookie has been revoked.', - 'tenant-not-found': 'There is no tenant corresponding to the provided identifier.', - 'uid-already-exists': 'The user with the provided uid already exists.', - 'unauthorized-continue-uri': 'The domain of the continue URL is not whitelisted. Whitelist ' + - 'the domain in the Firebase console.', - 'unsupported-first-factor': 'A multi-factor user requires a supported first factor.', - 'unsupported-second-factor': 'The request specified an unsupported type of second factor.', - 'unsupported-tenant-operation': 'This operation is not supported in a multi-tenant context.', - 'unverified-email': 'A verified email is required for the specified action. For example, a multi-factor ' + - 'user requires a verified email.', - 'user-not-found': 'There is no user record corresponding to the provided identifier.', - 'not-found': 'The requested resource was not found.', - 'user-disabled': 'The user record is disabled.', - 'user-not-disabled': 'The user must be disabled in order to bulk delete it (or you must pass force=true).', - 'invalid-recaptcha-action': 'reCAPTCHA action must be "BLOCK".', - 'invalid-recaptcha-enforcement-state': 'reCAPTCHA enforcement state must be either "OFF", "AUDIT" or "ENFORCE".', - 'recaptcha-not-enabled': 'reCAPTCHA enterprise is not enabled.', + * Internal Auth client error code mapping used to construct ErrorInfo. + */ +export const authClientErrorCode: { readonly [K in keyof typeof AuthErrorCode]: ErrorInfo } = { + AUTH_BLOCKING_TOKEN_EXPIRED: { + code: AuthErrorCode.AUTH_BLOCKING_TOKEN_EXPIRED, + message: 'The provided Firebase Auth Blocking token is expired.', + }, + BILLING_NOT_ENABLED: { + code: AuthErrorCode.BILLING_NOT_ENABLED, + message: 'Feature requires billing to be enabled.', + }, + CLAIMS_TOO_LARGE: { + code: AuthErrorCode.CLAIMS_TOO_LARGE, + message: 'Developer claims maximum payload size exceeded.', + }, + CONFIGURATION_EXISTS: { + code: AuthErrorCode.CONFIGURATION_EXISTS, + message: 'A configuration already exists with the provided identifier.', + }, + CONFIGURATION_NOT_FOUND: { + code: AuthErrorCode.CONFIGURATION_NOT_FOUND, + message: 'There is no configuration corresponding to the provided identifier.', + }, + ID_TOKEN_EXPIRED: { + code: AuthErrorCode.ID_TOKEN_EXPIRED, + message: 'The provided Firebase ID token is expired.', + }, + INVALID_ARGUMENT: { + code: AuthErrorCode.INVALID_ARGUMENT, + message: 'Invalid argument provided.', + }, + INVALID_CONFIG: { + code: AuthErrorCode.INVALID_CONFIG, + message: 'The provided configuration is invalid.', + }, + EMAIL_ALREADY_EXISTS: { + code: AuthErrorCode.EMAIL_ALREADY_EXISTS, + message: 'The email address is already in use by another account.', + }, + EMAIL_NOT_FOUND: { + code: AuthErrorCode.EMAIL_NOT_FOUND, + message: 'There is no user record corresponding to the provided email.', + }, + FORBIDDEN_CLAIM: { + code: AuthErrorCode.FORBIDDEN_CLAIM, + message: 'The specified developer claim is reserved and cannot be specified.', + }, + INVALID_ID_TOKEN: { + code: AuthErrorCode.INVALID_ID_TOKEN, + message: 'The provided ID token is not a valid Firebase ID token.', + }, + ID_TOKEN_REVOKED: { + code: AuthErrorCode.ID_TOKEN_REVOKED, + message: 'The Firebase ID token has been revoked.', + }, + INTERNAL_ERROR: { + code: AuthErrorCode.INTERNAL_ERROR, + message: 'An internal error has occurred.', + }, + INVALID_CLAIMS: { + code: AuthErrorCode.INVALID_CLAIMS, + message: 'The provided custom claim attributes are invalid.', + }, + INVALID_CONTINUE_URI: { + code: AuthErrorCode.INVALID_CONTINUE_URI, + message: 'The continue URL must be a valid URL string.', + }, + INVALID_CREATION_TIME: { + code: AuthErrorCode.INVALID_CREATION_TIME, + message: 'The creation time must be a valid UTC date string.', + }, + INVALID_CREDENTIAL: { + code: AuthErrorCode.INVALID_CREDENTIAL, + message: 'Invalid credential object provided.', + }, + INVALID_DISABLED_FIELD: { + code: AuthErrorCode.INVALID_DISABLED_FIELD, + message: 'The disabled field must be a boolean.', + }, + INVALID_DISPLAY_NAME: { + code: AuthErrorCode.INVALID_DISPLAY_NAME, + message: 'The displayName field must be a valid string.', + }, + INVALID_DYNAMIC_LINK_DOMAIN: { + code: AuthErrorCode.INVALID_DYNAMIC_LINK_DOMAIN, + message: 'The provided dynamic link domain is not configured or authorized for the current project.', + }, + INVALID_HOSTING_LINK_DOMAIN: { + code: AuthErrorCode.INVALID_HOSTING_LINK_DOMAIN, + message: 'The provided hosting link domain is not configured in Firebase Hosting or ' + + 'is not owned by the current project.', + }, + INVALID_EMAIL_VERIFIED: { + code: AuthErrorCode.INVALID_EMAIL_VERIFIED, + message: 'The emailVerified field must be a boolean.', + }, + INVALID_EMAIL: { + code: AuthErrorCode.INVALID_EMAIL, + message: 'The email address is improperly formatted.', + }, + INVALID_NEW_EMAIL: { + code: AuthErrorCode.INVALID_NEW_EMAIL, + message: 'The new email address is improperly formatted.', + }, + INVALID_ENROLLED_FACTORS: { + code: AuthErrorCode.INVALID_ENROLLED_FACTORS, + message: 'The enrolled factors must be a valid array of MultiFactorInfo objects.', + }, + INVALID_ENROLLMENT_TIME: { + code: AuthErrorCode.INVALID_ENROLLMENT_TIME, + message: 'The second factor enrollment time must be a valid UTC date string.', + }, + INVALID_HASH_ALGORITHM: { + code: AuthErrorCode.INVALID_HASH_ALGORITHM, + message: 'The hash algorithm must match one of the strings in the list of supported algorithms.', + }, + INVALID_HASH_BLOCK_SIZE: { + code: AuthErrorCode.INVALID_HASH_BLOCK_SIZE, + message: 'The hash block size must be a valid number.', + }, + INVALID_HASH_DERIVED_KEY_LENGTH: { + code: AuthErrorCode.INVALID_HASH_DERIVED_KEY_LENGTH, + message: 'The hash derived key length must be a valid number.', + }, + INVALID_HASH_KEY: { + code: AuthErrorCode.INVALID_HASH_KEY, + message: 'The hash key must a valid byte buffer.', + }, + INVALID_HASH_MEMORY_COST: { + code: AuthErrorCode.INVALID_HASH_MEMORY_COST, + message: 'The hash memory cost must be a valid number.', + }, + INVALID_HASH_PARALLELIZATION: { + code: AuthErrorCode.INVALID_HASH_PARALLELIZATION, + message: 'The hash parallelization must be a valid number.', + }, + INVALID_HASH_ROUNDS: { + code: AuthErrorCode.INVALID_HASH_ROUNDS, + message: 'The hash rounds must be a valid number.', + }, + INVALID_HASH_SALT_SEPARATOR: { + code: AuthErrorCode.INVALID_HASH_SALT_SEPARATOR, + message: 'The hashing algorithm salt separator field must be a valid byte buffer.', + }, + INVALID_LAST_SIGN_IN_TIME: { + code: AuthErrorCode.INVALID_LAST_SIGN_IN_TIME, + message: 'The last sign-in time must be a valid UTC date string.', + }, + INVALID_NAME: { + code: AuthErrorCode.INVALID_NAME, + message: 'The resource name provided is invalid.', + }, + INVALID_OAUTH_CLIENT_ID: { + code: AuthErrorCode.INVALID_OAUTH_CLIENT_ID, + message: 'The provided OAuth client ID is invalid.', + }, + INVALID_PAGE_TOKEN: { + code: AuthErrorCode.INVALID_PAGE_TOKEN, + message: 'The page token must be a valid non-empty string.', + }, + INVALID_PASSWORD: { + code: AuthErrorCode.INVALID_PASSWORD, + message: 'The password must be a string with at least 6 characters.', + }, + INVALID_PASSWORD_HASH: { + code: AuthErrorCode.INVALID_PASSWORD_HASH, + message: 'The password hash must be a valid byte buffer.', + }, + INVALID_PASSWORD_SALT: { + code: AuthErrorCode.INVALID_PASSWORD_SALT, + message: 'The password salt must be a valid byte buffer.', + }, + INVALID_PHONE_NUMBER: { + code: AuthErrorCode.INVALID_PHONE_NUMBER, + message: 'The phone number must be a non-empty E.164 standard compliant identifier string.', + }, + INVALID_PHOTO_URL: { + code: AuthErrorCode.INVALID_PHOTO_URL, + message: 'The photoURL field must be a valid URL.', + }, + INVALID_PROJECT_ID: { + code: AuthErrorCode.INVALID_PROJECT_ID, + message: 'Invalid parent project. Either parent project doesn\'t exist or didn\'t enable multi-tenancy.', + }, + INVALID_PROVIDER_DATA: { + code: AuthErrorCode.INVALID_PROVIDER_DATA, + message: 'The providerData must be a valid array of UserInfo objects.', + }, + INVALID_PROVIDER_ID: { + code: AuthErrorCode.INVALID_PROVIDER_ID, + message: 'The providerId must be a valid supported provider identifier string.', + }, + INVALID_PROVIDER_UID: { + code: AuthErrorCode.INVALID_PROVIDER_UID, + message: 'The providerUid must be a valid provider uid string.', + }, + INVALID_OAUTH_RESPONSETYPE: { + code: AuthErrorCode.INVALID_OAUTH_RESPONSETYPE, + message: 'Only exactly one OAuth responseType should be set to true.', + }, + INVALID_SESSION_COOKIE_DURATION: { + code: AuthErrorCode.INVALID_SESSION_COOKIE_DURATION, + message: 'The session cookie duration must be a valid number in milliseconds between 5 minutes and 2 weeks.', + }, + INVALID_TENANT_ID: { + code: AuthErrorCode.INVALID_TENANT_ID, + message: 'The tenant ID must be a valid non-empty string.', + }, + INVALID_TENANT_TYPE: { + code: AuthErrorCode.INVALID_TENANT_TYPE, + message: 'Tenant type must be either "full_service" or "lightweight".', + }, + INVALID_TESTING_PHONE_NUMBER: { + code: AuthErrorCode.INVALID_TESTING_PHONE_NUMBER, + message: 'Invalid testing phone number or invalid test code provided.', + }, + INVALID_UID: { + code: AuthErrorCode.INVALID_UID, + message: 'The uid must be a non-empty string with at most 128 characters.', + }, + INVALID_USER_IMPORT: { + code: AuthErrorCode.INVALID_USER_IMPORT, + message: 'The user record to import is invalid.', + }, + INVALID_TOKENS_VALID_AFTER_TIME: { + code: AuthErrorCode.INVALID_TOKENS_VALID_AFTER_TIME, + message: 'The tokensValidAfterTime must be a valid UTC number in seconds.', + }, + MISMATCHING_TENANT_ID: { + code: AuthErrorCode.MISMATCHING_TENANT_ID, + message: 'User tenant ID does not match with the current TenantAwareAuth tenant ID.', + }, + MISSING_ANDROID_PACKAGE_NAME: { + code: AuthErrorCode.MISSING_ANDROID_PACKAGE_NAME, + message: 'An Android Package Name must be provided if the Android App is required to be installed.', + }, + MISSING_CONFIG: { + code: AuthErrorCode.MISSING_CONFIG, + message: 'The provided configuration is missing required attributes.', + }, + MISSING_CONTINUE_URI: { + code: AuthErrorCode.MISSING_CONTINUE_URI, + message: 'A valid continue URL must be provided in the request.', + }, + MISSING_DISPLAY_NAME: { + code: AuthErrorCode.MISSING_DISPLAY_NAME, + message: 'The resource being created or edited is missing a valid display name.', + }, + MISSING_EMAIL: { + code: AuthErrorCode.MISSING_EMAIL, + message: 'The email is required for the specified action. For example, a multi-factor ' + + 'user requires a verified email.', + }, + MISSING_IOS_BUNDLE_ID: { + code: AuthErrorCode.MISSING_IOS_BUNDLE_ID, + message: 'The request is missing an iOS Bundle ID.', + }, + MISSING_ISSUER: { + code: AuthErrorCode.MISSING_ISSUER, + message: 'The OAuth/OIDC configuration issuer must not be empty.', + }, + MISSING_HASH_ALGORITHM: { + code: AuthErrorCode.MISSING_HASH_ALGORITHM, + message: 'Importing users with password hashes requires that the hashing algorithm and its parameters be provided.', + }, + MISSING_OAUTH_CLIENT_ID: { + code: AuthErrorCode.MISSING_OAUTH_CLIENT_ID, + message: 'The OAuth/OIDC configuration client ID must not be empty.', + }, + MISSING_OAUTH_CLIENT_SECRET: { + code: AuthErrorCode.MISSING_OAUTH_CLIENT_SECRET, + message: 'The OAuth configuration client secret is required to enable OIDC code flow.', + }, + MISSING_PROVIDER_ID: { + code: AuthErrorCode.MISSING_PROVIDER_ID, + message: 'A valid provider ID must be provided in the request.', + }, + MISSING_SAML_RELYING_PARTY_CONFIG: { + code: AuthErrorCode.MISSING_SAML_RELYING_PARTY_CONFIG, + message: 'The SAML configuration provided is missing a relying party configuration.', + }, + MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED: { + code: AuthErrorCode.MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED, + message: 'The maximum allowed number of test phone number / code pairs has been exceeded.', + }, + MAXIMUM_USER_COUNT_EXCEEDED: { + code: AuthErrorCode.MAXIMUM_USER_COUNT_EXCEEDED, + message: 'The maximum allowed number of users to import has been exceeded.', + }, + MISSING_UID: { + code: AuthErrorCode.MISSING_UID, + message: 'A uid identifier is required for the current operation.', + }, + OPERATION_NOT_ALLOWED: { + code: AuthErrorCode.OPERATION_NOT_ALLOWED, + message: 'The given sign-in provider is disabled for this Firebase project. Enable it in the ' + + 'Firebase console, under the sign-in method tab of the Auth section.', + }, + PHONE_NUMBER_ALREADY_EXISTS: { + code: AuthErrorCode.PHONE_NUMBER_ALREADY_EXISTS, + message: 'The user with the provided phone number already exists.', + }, + PROJECT_NOT_FOUND: { + code: AuthErrorCode.PROJECT_NOT_FOUND, + message: 'No Firebase project was found for the provided credential.', + }, + INSUFFICIENT_PERMISSION: { + code: AuthErrorCode.INSUFFICIENT_PERMISSION, + message: 'Credential implementation provided to initializeApp() via the "credential" property has insufficient permission to access the requested resource. See https://firebase.google.com/docs/admin/setup for details on how to authenticate this SDK with appropriate permissions.', + }, + QUOTA_EXCEEDED: { + code: AuthErrorCode.QUOTA_EXCEEDED, + message: 'The project quota for the specified operation has been exceeded.', + }, + SECOND_FACTOR_LIMIT_EXCEEDED: { + code: AuthErrorCode.SECOND_FACTOR_LIMIT_EXCEEDED, + message: 'The maximum number of allowed second factors on a user has been exceeded.', + }, + SECOND_FACTOR_UID_ALREADY_EXISTS: { + code: AuthErrorCode.SECOND_FACTOR_UID_ALREADY_EXISTS, + message: 'The specified second factor "uid" already exists.', + }, + SESSION_COOKIE_EXPIRED: { + code: AuthErrorCode.SESSION_COOKIE_EXPIRED, + message: 'The Firebase session cookie is expired.', + }, + SESSION_COOKIE_REVOKED: { + code: AuthErrorCode.SESSION_COOKIE_REVOKED, + message: 'The Firebase session cookie has been revoked.', + }, + TENANT_NOT_FOUND: { + code: AuthErrorCode.TENANT_NOT_FOUND, + message: 'There is no tenant corresponding to the provided identifier.', + }, + UID_ALREADY_EXISTS: { + code: AuthErrorCode.UID_ALREADY_EXISTS, + message: 'The user with the provided uid already exists.', + }, + UNAUTHORIZED_DOMAIN: { + code: AuthErrorCode.UNAUTHORIZED_DOMAIN, + message: 'The domain of the continue URL is not whitelisted. Whitelist the domain in the Firebase console.', + }, + UNSUPPORTED_FIRST_FACTOR: { + code: AuthErrorCode.UNSUPPORTED_FIRST_FACTOR, + message: 'A multi-factor user requires a supported first factor.', + }, + UNSUPPORTED_SECOND_FACTOR: { + code: AuthErrorCode.UNSUPPORTED_SECOND_FACTOR, + message: 'The request specified an unsupported type of second factor.', + }, + UNSUPPORTED_TENANT_OPERATION: { + code: AuthErrorCode.UNSUPPORTED_TENANT_OPERATION, + message: 'This operation is not supported in a multi-tenant context.', + }, + UNVERIFIED_EMAIL: { + code: AuthErrorCode.UNVERIFIED_EMAIL, + message: 'A verified email is required for the specified action. For example, a ' + + 'multi-factor user requires a verified email.', + }, + USER_NOT_FOUND: { + code: AuthErrorCode.USER_NOT_FOUND, + message: 'There is no user record corresponding to the provided identifier.', + }, + NOT_FOUND: { + code: AuthErrorCode.NOT_FOUND, + message: 'The requested resource was not found.', + }, + USER_DISABLED: { + code: AuthErrorCode.USER_DISABLED, + message: 'The user record is disabled.', + }, + USER_NOT_DISABLED: { + code: AuthErrorCode.USER_NOT_DISABLED, + message: 'The user must be disabled in order to bulk delete it (or you must pass force=true).', + }, + INVALID_RECAPTCHA_ACTION: { + code: AuthErrorCode.INVALID_RECAPTCHA_ACTION, + message: 'reCAPTCHA action must be "BLOCK".', + }, + INVALID_RECAPTCHA_ENFORCEMENT_STATE: { + code: AuthErrorCode.INVALID_RECAPTCHA_ENFORCEMENT_STATE, + message: 'reCAPTCHA enforcement state must be either "OFF", "AUDIT" or "ENFORCE".', + }, + RECAPTCHA_NOT_ENABLED: { + code: AuthErrorCode.RECAPTCHA_NOT_ENABLED, + message: 'reCAPTCHA enterprise is not enabled.', + }, }; -/** @const {Record} Auth server to client enum error codes. */ -const AUTH_SERVER_TO_CLIENT_CODE: Record = { +/** @const {Record} Auth server to client enum error codes. */ +const AUTH_SERVER_TO_CLIENT_CODE: Record = { // Feature being configured or used requires a billing account. BILLING_NOT_ENABLED: 'BILLING_NOT_ENABLED', // Claims payload is too large. @@ -364,114 +646,6 @@ const AUTH_SERVER_TO_CLIENT_CODE: Record = { RECAPTCHA_NOT_ENABLED: 'RECAPTCHA_NOT_ENABLED' }; -/** - * Internal Auth client error code mapping used to construct ErrorInfo. - */ -export const authClientErrorCode = { - AUTH_BLOCKING_TOKEN_EXPIRED: createAuthErrorInfo('auth-blocking-token-expired'), - BILLING_NOT_ENABLED: createAuthErrorInfo('billing-not-enabled'), - CLAIMS_TOO_LARGE: createAuthErrorInfo('claims-too-large'), - CONFIGURATION_EXISTS: createAuthErrorInfo('configuration-exists'), - CONFIGURATION_NOT_FOUND: createAuthErrorInfo('configuration-not-found'), - ID_TOKEN_EXPIRED: createAuthErrorInfo('id-token-expired'), - INVALID_ARGUMENT: createAuthErrorInfo('argument-error'), - INVALID_CONFIG: createAuthErrorInfo('invalid-config'), - EMAIL_ALREADY_EXISTS: createAuthErrorInfo('email-already-exists'), - EMAIL_NOT_FOUND: createAuthErrorInfo('email-not-found'), - FORBIDDEN_CLAIM: createAuthErrorInfo('reserved-claim'), - INVALID_ID_TOKEN: createAuthErrorInfo('invalid-id-token'), - ID_TOKEN_REVOKED: createAuthErrorInfo('id-token-revoked'), - INTERNAL_ERROR: createAuthErrorInfo('internal-error'), - INVALID_CLAIMS: createAuthErrorInfo('invalid-claims'), - INVALID_CONTINUE_URI: createAuthErrorInfo('invalid-continue-uri'), - INVALID_CREATION_TIME: createAuthErrorInfo('invalid-creation-time'), - INVALID_CREDENTIAL: createAuthErrorInfo('invalid-credential'), - INVALID_DISABLED_FIELD: createAuthErrorInfo('invalid-disabled-field'), - INVALID_DISPLAY_NAME: createAuthErrorInfo('invalid-display-name'), - INVALID_DYNAMIC_LINK_DOMAIN: createAuthErrorInfo('invalid-dynamic-link-domain'), - INVALID_HOSTING_LINK_DOMAIN: createAuthErrorInfo('invalid-hosting-link-domain'), - INVALID_EMAIL_VERIFIED: createAuthErrorInfo('invalid-email-verified'), - INVALID_EMAIL: createAuthErrorInfo('invalid-email'), - INVALID_NEW_EMAIL: createAuthErrorInfo('invalid-new-email'), - INVALID_ENROLLED_FACTORS: createAuthErrorInfo('invalid-enrolled-factors'), - INVALID_ENROLLMENT_TIME: createAuthErrorInfo('invalid-enrollment-time'), - INVALID_HASH_ALGORITHM: createAuthErrorInfo('invalid-hash-algorithm'), - INVALID_HASH_BLOCK_SIZE: createAuthErrorInfo('invalid-hash-block-size'), - INVALID_HASH_DERIVED_KEY_LENGTH: createAuthErrorInfo('invalid-hash-derived-key-length'), - INVALID_HASH_KEY: createAuthErrorInfo('invalid-hash-key'), - INVALID_HASH_MEMORY_COST: createAuthErrorInfo('invalid-hash-memory-cost'), - INVALID_HASH_PARALLELIZATION: createAuthErrorInfo('invalid-hash-parallelization'), - INVALID_HASH_ROUNDS: createAuthErrorInfo('invalid-hash-rounds'), - INVALID_HASH_SALT_SEPARATOR: createAuthErrorInfo('invalid-hash-salt-separator'), - INVALID_LAST_SIGN_IN_TIME: createAuthErrorInfo('invalid-last-sign-in-time'), - INVALID_NAME: createAuthErrorInfo('invalid-name'), - INVALID_OAUTH_CLIENT_ID: createAuthErrorInfo('invalid-oauth-client-id'), - INVALID_PAGE_TOKEN: createAuthErrorInfo('invalid-page-token'), - INVALID_PASSWORD: createAuthErrorInfo('invalid-password'), - INVALID_PASSWORD_HASH: createAuthErrorInfo('invalid-password-hash'), - INVALID_PASSWORD_SALT: createAuthErrorInfo('invalid-password-salt'), - INVALID_PHONE_NUMBER: createAuthErrorInfo('invalid-phone-number'), - INVALID_PHOTO_URL: createAuthErrorInfo('invalid-photo-url'), - INVALID_PROJECT_ID: createAuthErrorInfo('invalid-project-id'), - INVALID_PROVIDER_DATA: createAuthErrorInfo('invalid-provider-data'), - INVALID_PROVIDER_ID: createAuthErrorInfo('invalid-provider-id'), - INVALID_PROVIDER_UID: createAuthErrorInfo('invalid-provider-uid'), - INVALID_OAUTH_RESPONSETYPE: createAuthErrorInfo('invalid-oauth-responsetype'), - INVALID_SESSION_COOKIE_DURATION: createAuthErrorInfo('invalid-session-cookie-duration'), - INVALID_TENANT_ID: createAuthErrorInfo('invalid-tenant-id'), - INVALID_TENANT_TYPE: createAuthErrorInfo('invalid-tenant-type'), - INVALID_TESTING_PHONE_NUMBER: createAuthErrorInfo('invalid-testing-phone-number'), - INVALID_UID: createAuthErrorInfo('invalid-uid'), - INVALID_USER_IMPORT: createAuthErrorInfo('invalid-user-import'), - INVALID_TOKENS_VALID_AFTER_TIME: createAuthErrorInfo('invalid-tokens-valid-after-time'), - MISMATCHING_TENANT_ID: createAuthErrorInfo('mismatching-tenant-id'), - MISSING_ANDROID_PACKAGE_NAME: createAuthErrorInfo('missing-android-package-name'), - MISSING_CONFIG: createAuthErrorInfo('missing-config'), - MISSING_CONTINUE_URI: createAuthErrorInfo('missing-continue-uri'), - MISSING_DISPLAY_NAME: createAuthErrorInfo('missing-display-name'), - MISSING_EMAIL: createAuthErrorInfo('missing-email'), - MISSING_IOS_BUNDLE_ID: createAuthErrorInfo('missing-ios-bundle-id'), - MISSING_ISSUER: createAuthErrorInfo('missing-issuer'), - MISSING_HASH_ALGORITHM: createAuthErrorInfo('missing-hash-algorithm'), - MISSING_OAUTH_CLIENT_ID: createAuthErrorInfo('missing-oauth-client-id'), - MISSING_OAUTH_CLIENT_SECRET: createAuthErrorInfo('missing-oauth-client-secret'), - MISSING_PROVIDER_ID: createAuthErrorInfo('missing-provider-id'), - MISSING_SAML_RELYING_PARTY_CONFIG: createAuthErrorInfo('missing-saml-relying-party-config'), - MAXIMUM_TEST_PHONE_NUMBER_EXCEEDED: createAuthErrorInfo('test-phone-number-limit-exceeded'), - MAXIMUM_USER_COUNT_EXCEEDED: createAuthErrorInfo('maximum-user-count-exceeded'), - MISSING_UID: createAuthErrorInfo('missing-uid'), - OPERATION_NOT_ALLOWED: createAuthErrorInfo('operation-not-allowed'), - PHONE_NUMBER_ALREADY_EXISTS: createAuthErrorInfo('phone-number-already-exists'), - PROJECT_NOT_FOUND: createAuthErrorInfo('project-not-found'), - INSUFFICIENT_PERMISSION: createAuthErrorInfo('insufficient-permission'), - QUOTA_EXCEEDED: createAuthErrorInfo('quota-exceeded'), - SECOND_FACTOR_LIMIT_EXCEEDED: createAuthErrorInfo('second-factor-limit-exceeded'), - SECOND_FACTOR_UID_ALREADY_EXISTS: createAuthErrorInfo('second-factor-uid-already-exists'), - SESSION_COOKIE_EXPIRED: createAuthErrorInfo('session-cookie-expired'), - SESSION_COOKIE_REVOKED: createAuthErrorInfo('session-cookie-revoked'), - TENANT_NOT_FOUND: createAuthErrorInfo('tenant-not-found'), - UID_ALREADY_EXISTS: createAuthErrorInfo('uid-already-exists'), - UNAUTHORIZED_DOMAIN: createAuthErrorInfo('unauthorized-continue-uri'), - UNSUPPORTED_FIRST_FACTOR: createAuthErrorInfo('unsupported-first-factor'), - UNSUPPORTED_SECOND_FACTOR: createAuthErrorInfo('unsupported-second-factor'), - UNSUPPORTED_TENANT_OPERATION: createAuthErrorInfo('unsupported-tenant-operation'), - UNVERIFIED_EMAIL: createAuthErrorInfo('unverified-email'), - USER_NOT_FOUND: createAuthErrorInfo('user-not-found'), - NOT_FOUND: createAuthErrorInfo('not-found'), - USER_DISABLED: createAuthErrorInfo('user-disabled'), - USER_NOT_DISABLED: createAuthErrorInfo('user-not-disabled'), - INVALID_RECAPTCHA_ACTION: createAuthErrorInfo('invalid-recaptcha-action'), - INVALID_RECAPTCHA_ENFORCEMENT_STATE: createAuthErrorInfo('invalid-recaptcha-enforcement-state'), - RECAPTCHA_NOT_ENABLED: createAuthErrorInfo('recaptcha-not-enabled'), -}; - -function createAuthErrorInfo(code: AuthErrorCode): ErrorInfo { - return { - code, - message: authClientErrorMessages[code] || 'An unknown error occurred.', - }; -} - /** * Firebase Auth error code structure. This extends PrefixedFirebaseError. */ diff --git a/src/data-connect/error.ts b/src/data-connect/error.ts index 825b96ecd7..f20ac6cd48 100644 --- a/src/data-connect/error.ts +++ b/src/data-connect/error.ts @@ -30,18 +30,24 @@ export const DATA_CONNECT_ERROR_CODE_MAPPING: Record { if (!resp.text) { throw new FirebaseAppError({ - code: AppErrorCodes.INTERNAL_ERROR, + code: AppErrorCode.INTERNAL_ERROR, message: 'HTTP response missing data.' }); } @@ -299,7 +299,7 @@ class DatabaseRulesClient { private handleError(err: Error): Error { if (err instanceof RequestResponseError) { return new FirebaseDatabaseError({ - code: AppErrorCodes.INTERNAL_ERROR, + code: AppErrorCode.INTERNAL_ERROR, message: this.getErrorMessage(err), httpResponse: toHttpResponse(err.response), cause: err, diff --git a/src/eventarc/error.ts b/src/eventarc/error.ts index 8ab09cc9a0..6d938af093 100644 --- a/src/eventarc/error.ts +++ b/src/eventarc/error.ts @@ -17,9 +17,17 @@ import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; /** - * Eventarc client error codes and their default messages. + * The constant mapping for valid Eventarc client error codes. */ -export type EventarcErrorCode = 'unknown-error' | 'invalid-argument'; +export const EventarcErrorCode = { + UNKNOWN_ERROR: 'unknown-error', + INVALID_ARGUMENT: 'invalid-argument', +} as const; + +/** + * The type definition for valid Eventarc client error codes. + */ +export type EventarcErrorCode = typeof EventarcErrorCode[keyof typeof EventarcErrorCode]; /** * Firebase Eventarc error code structure. This extends PrefixedFirebaseError. diff --git a/src/extensions/error.ts b/src/extensions/error.ts index 5127ecd402..135853ab76 100644 --- a/src/extensions/error.ts +++ b/src/extensions/error.ts @@ -19,7 +19,21 @@ import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; /** * Extensions client error codes and their default messages. */ -export type ExtensionsErrorCode = 'invalid-argument' | 'not-found' | 'forbidden' | 'internal-error' | 'unknown-error'; +/** + * The constant mapping for valid Extensions client error codes. + */ +export const ExtensionsErrorCode = { + INVALID_ARGUMENT: 'invalid-argument', + NOT_FOUND: 'not-found', + FORBIDDEN: 'forbidden', + INTERNAL_ERROR: 'internal-error', + UNKNOWN_ERROR: 'unknown-error', +} as const; + +/** + * The type definition for valid Extensions client error codes. + */ +export type ExtensionsErrorCode = typeof ExtensionsErrorCode[keyof typeof ExtensionsErrorCode]; /** * Firebase Extensions error code structure. This extends PrefixedFirebaseError. diff --git a/src/functions/error.ts b/src/functions/error.ts index 6c81172dfe..a59e00d3d1 100644 --- a/src/functions/error.ts +++ b/src/functions/error.ts @@ -30,19 +30,25 @@ export const FUNCTIONS_ERROR_CODE_MAPPING: Record = }; /** - * Functions client error codes and their default messages. + * The constant mapping for valid Functions client error codes. */ -export type FunctionsErrorCode = - | 'aborted' - | 'invalid-argument' - | 'invalid-credential' - | 'internal-error' - | 'failed-precondition' - | 'permission-denied' - | 'unauthenticated' - | 'not-found' - | 'unknown-error' - | 'task-already-exists'; +export const FunctionsErrorCode = { + ABORTED: 'aborted', + INVALID_ARGUMENT: 'invalid-argument', + INVALID_CREDENTIAL: 'invalid-credential', + INTERNAL_ERROR: 'internal-error', + FAILED_PRECONDITION: 'failed-precondition', + PERMISSION_DENIED: 'permission-denied', + UNAUTHENTICATED: 'unauthenticated', + NOT_FOUND: 'not-found', + UNKNOWN_ERROR: 'unknown-error', + TASK_ALREADY_EXISTS: 'task-already-exists', +} as const; + +/** + * The type definition for valid Functions client error codes. + */ +export type FunctionsErrorCode = typeof FunctionsErrorCode[keyof typeof FunctionsErrorCode]; /** * Firebase Functions error code structure. This extends PrefixedFirebaseError. diff --git a/src/installations/error.ts b/src/installations/error.ts index a51258f3bd..2a8d480563 100644 --- a/src/installations/error.ts +++ b/src/installations/error.ts @@ -17,41 +17,42 @@ import { FirebaseError, ErrorInfo } from '../utils/error'; /** - * Installations client error codes. + * The constant mapping for valid Installations client error codes. */ -export type InstallationsErrorCode = - | 'invalid-argument' - | 'invalid-project-id' - | 'invalid-installation-id' - | 'api-error'; +export const InstallationsErrorCode = { + INVALID_ARGUMENT: 'invalid-argument', + INVALID_PROJECT_ID: 'invalid-project-id', + INVALID_INSTALLATION_ID: 'invalid-installation-id', + API_ERROR: 'api-error', +} as const; /** - * Installations client error codes and their default messages. + * The type definition for valid Installations client error codes. */ -const installationsClientErrorMessages: Record = { - 'invalid-argument': 'Invalid argument provided.', - 'invalid-project-id': 'Invalid project ID provided.', - 'invalid-installation-id': 'Invalid installation ID provided.', - 'api-error': 'Installation ID API call failed.', -}; +export type InstallationsErrorCode = typeof InstallationsErrorCode[keyof typeof InstallationsErrorCode]; /** * Internal Installations client error code mapping used to construct ErrorInfo. */ -export const installationsClientErrorCode = { - INVALID_ARGUMENT: createInstallationsErrorInfo('invalid-argument'), - INVALID_PROJECT_ID: createInstallationsErrorInfo('invalid-project-id'), - INVALID_INSTALLATION_ID: createInstallationsErrorInfo('invalid-installation-id'), - API_ERROR: createInstallationsErrorInfo('api-error'), +export const installationsClientErrorCode: { readonly [K in keyof typeof InstallationsErrorCode]: ErrorInfo } = { + INVALID_ARGUMENT: { + code: InstallationsErrorCode.INVALID_ARGUMENT, + message: 'Invalid argument provided.', + }, + INVALID_PROJECT_ID: { + code: InstallationsErrorCode.INVALID_PROJECT_ID, + message: 'Invalid project ID provided.', + }, + INVALID_INSTALLATION_ID: { + code: InstallationsErrorCode.INVALID_INSTALLATION_ID, + message: 'Invalid installation ID provided.', + }, + API_ERROR: { + code: InstallationsErrorCode.API_ERROR, + message: 'Installation ID API call failed.', + }, }; -function createInstallationsErrorInfo(code: InstallationsErrorCode): ErrorInfo { - return { - code, - message: installationsClientErrorMessages[code] || 'An unknown error occurred.', - }; -} - /** * Firebase Installations service error code structure. This extends `FirebaseError`. */ diff --git a/src/instance-id/error.ts b/src/instance-id/error.ts index 66329f322c..ebe33833b6 100644 --- a/src/instance-id/error.ts +++ b/src/instance-id/error.ts @@ -18,36 +18,30 @@ import { FirebaseError, ErrorInfo } from '../utils/error'; import { installationsClientErrorCode } from '../installations/error'; /** - * Instance ID client error codes. + * The constant mapping for valid Instance ID client error codes. */ -export type InstanceIdErrorCode = - | 'invalid-argument' - | 'invalid-project-id' - | 'invalid-installation-id' - | 'api-error' - | 'invalid-instance-id'; - +export const InstanceIdErrorCode = { + INVALID_ARGUMENT: 'invalid-argument', + INVALID_PROJECT_ID: 'invalid-project-id', + INVALID_INSTALLATION_ID: 'invalid-installation-id', + API_ERROR: 'api-error', + INVALID_INSTANCE_ID: 'invalid-instance-id', +} as const; /** - * Instance ID client error codes and their default messages. + * The type definition for valid Instance ID client error codes. */ -const instanceIdClientErrorMessages = { - 'invalid-instance-id': 'Invalid instance ID provided.', -}; - -function createInstanceIdErrorInfo(code: 'invalid-instance-id'): ErrorInfo { - return { - code, - message: instanceIdClientErrorMessages[code] || 'An unknown error occurred.', - }; -} +export type InstanceIdErrorCode = typeof InstanceIdErrorCode[keyof typeof InstanceIdErrorCode]; /** * Internal Instance ID client error code mapping used to construct ErrorInfo. */ -export const instanceIdClientErrorCode = { +export const instanceIdClientErrorCode: { readonly [K in keyof typeof InstanceIdErrorCode]: ErrorInfo } = { ...installationsClientErrorCode, - INVALID_INSTANCE_ID: createInstanceIdErrorInfo('invalid-instance-id'), + INVALID_INSTANCE_ID: { + code: InstanceIdErrorCode.INVALID_INSTANCE_ID, + message: 'Invalid instance ID provided.', + }, }; /** diff --git a/src/machine-learning/error.ts b/src/machine-learning/error.ts index 0028b5ee74..b8a2a0e102 100644 --- a/src/machine-learning/error.ts +++ b/src/machine-learning/error.ts @@ -17,26 +17,32 @@ import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; /** - * Machine Learning client error codes. + * The constant mapping for valid Machine Learning client error codes. */ -export type MachineLearningErrorCode = - | 'already-exists' - | 'authentication-error' - | 'internal-error' - | 'invalid-argument' - | 'invalid-server-response' - | 'not-found' - | 'resource-exhausted' - | 'service-unavailable' - | 'unknown-error' - | 'cancelled' - | 'deadline-exceeded' - | 'permission-denied' - | 'failed-precondition' - | 'aborted' - | 'out-of-range' - | 'data-loss' - | 'unauthenticated'; +export const MachineLearningErrorCode = { + ALREADY_EXISTS: 'already-exists', + AUTHENTICATION_ERROR: 'authentication-error', + INTERNAL_ERROR: 'internal-error', + INVALID_ARGUMENT: 'invalid-argument', + INVALID_SERVER_RESPONSE: 'invalid-server-response', + NOT_FOUND: 'not-found', + RESOURCE_EXHAUSTED: 'resource-exhausted', + SERVICE_UNAVAILABLE: 'service-unavailable', + UNKNOWN_ERROR: 'unknown-error', + CANCELLED: 'cancelled', + DEADLINE_EXCEEDED: 'deadline-exceeded', + PERMISSION_DENIED: 'permission-denied', + FAILED_PRECONDITION: 'failed-precondition', + ABORTED: 'aborted', + OUT_OF_RANGE: 'out-of-range', + DATA_LOSS: 'data-loss', + UNAUTHENTICATED: 'unauthenticated', +} as const; + +/** + * The type definition for valid Machine Learning client error codes. + */ +export type MachineLearningErrorCode = typeof MachineLearningErrorCode[keyof typeof MachineLearningErrorCode]; export class FirebaseMachineLearningError extends PrefixedFirebaseError { /** @internal */ diff --git a/src/messaging/error.ts b/src/messaging/error.ts index 863e3c7a1b..702502fbdb 100644 --- a/src/messaging/error.ts +++ b/src/messaging/error.ts @@ -20,106 +20,137 @@ import { deepCopy } from '../utils/deep-copy'; import { BatchResponse } from './messaging-api'; /** - * Messaging client error codes. + * The constant mapping for valid Messaging client error codes. */ -export type MessagingErrorCode = - | 'invalid-argument' - | 'invalid-recipient' - | 'invalid-payload' - | 'invalid-data-payload-key' - | 'payload-size-limit-exceeded' - | 'invalid-options' - | 'invalid-registration-token' - | 'registration-token-not-registered' - | 'mismatched-credential' - | 'invalid-package-name' - | 'device-message-rate-exceeded' - | 'topics-message-rate-exceeded' - | 'message-rate-exceeded' - | 'third-party-auth-error' - | 'too-many-topics' - | 'authentication-error' - | 'server-unavailable' - | 'internal-error' - | 'unknown-error'; +export const MessagingErrorCode = { + INVALID_ARGUMENT: 'invalid-argument', + INVALID_RECIPIENT: 'invalid-recipient', + INVALID_PAYLOAD: 'invalid-payload', + INVALID_DATA_PAYLOAD_KEY: 'invalid-data-payload-key', + PAYLOAD_SIZE_LIMIT_EXCEEDED: 'payload-size-limit-exceeded', + INVALID_OPTIONS: 'invalid-options', + INVALID_REGISTRATION_TOKEN: 'invalid-registration-token', + REGISTRATION_TOKEN_NOT_REGISTERED: 'registration-token-not-registered', + MISMATCHED_CREDENTIAL: 'mismatched-credential', + INVALID_PACKAGE_NAME: 'invalid-package-name', + DEVICE_MESSAGE_RATE_EXCEEDED: 'device-message-rate-exceeded', + TOPICS_MESSAGE_RATE_EXCEEDED: 'topics-message-rate-exceeded', + MESSAGE_RATE_EXCEEDED: 'message-rate-exceeded', + THIRD_PARTY_AUTH_ERROR: 'third-party-auth-error', + TOO_MANY_TOPICS: 'too-many-topics', + AUTHENTICATION_ERROR: 'authentication-error', + SERVER_UNAVAILABLE: 'server-unavailable', + INTERNAL_ERROR: 'internal-error', + UNKNOWN_ERROR: 'unknown-error', +} as const; /** - * Messaging client error codes and their default messages. + * The type definition for valid Messaging client error codes. */ -const messagingClientErrorMessages: Record = { - 'invalid-argument': 'Invalid argument provided.', - 'invalid-recipient': 'Invalid message recipient provided.', - 'invalid-payload': 'Invalid message payload provided.', - 'invalid-data-payload-key': 'The data message payload contains an invalid key. See the reference ' + - 'documentation for the DataMessagePayload type for restricted keys.', - 'payload-size-limit-exceeded': 'The provided message payload exceeds the FCM size limits. See the ' + - 'error documentation for more details.', - 'invalid-options': 'Invalid message options provided.', - 'invalid-registration-token': 'Invalid registration token provided. Make sure it matches the ' + - 'registration token the client app receives from registering with FCM.', - 'registration-token-not-registered': 'The provided registration token is not registered. A ' + - 'previously valid registration token can be unregistered for a variety of reasons. See the ' + - 'error documentation for more details. Remove this registration token and stop using it to ' + - 'send messages.', - 'mismatched-credential': 'The credential used to authenticate this SDK does not have permission ' + - 'to send messages to the device corresponding to the provided registration token. Make sure the ' + - 'credential and registration token both belong to the same Firebase project.', - 'invalid-package-name': 'The message was addressed to a registration token whose package name does ' + - 'not match the provided "restrictedPackageName" option.', - 'device-message-rate-exceeded': 'The rate of messages to a particular device is too high. Reduce ' + - 'the number of messages sent to this device and do not immediately retry sending to this device.', - 'topics-message-rate-exceeded': 'The rate of messages to subscribers to a particular topic is too ' + - 'high. Reduce the number of messages sent for this topic, and do not immediately retry sending ' + - 'to this topic.', - 'message-rate-exceeded': 'Sending limit exceeded for the message target.', - 'third-party-auth-error': 'A message targeted to an iOS device could not be sent because the ' + - 'required APNs SSL certificate was not uploaded or has expired. Check the validity of your ' + - 'development and production certificates.', - 'too-many-topics': 'The maximum number of topics the provided registration token can be ' + - 'subscribed to has been exceeded.', - 'authentication-error': 'An error occurred when trying to authenticate to the FCM servers. Make ' + - 'sure the credential used to authenticate this SDK has the proper permissions. See ' + - 'https://firebase.google.com/docs/admin/setup for setup instructions.', - 'server-unavailable': 'The FCM server could not process the request in time. See the error ' + - 'documentation for more details.', - 'internal-error': 'An internal error has occurred. Please retry the request.', - 'unknown-error': 'An unknown server error was returned.', -}; - -function createMessagingErrorInfo(code: MessagingErrorCode): ErrorInfo { - return { - code, - message: messagingClientErrorMessages[code] || 'An unknown error occurred.', - }; -} +export type MessagingErrorCode = typeof MessagingErrorCode[keyof typeof MessagingErrorCode]; /** * Internal Messaging client error code mapping used to construct ErrorInfo. */ -export const messagingClientErrorCode = { - INVALID_ARGUMENT: createMessagingErrorInfo('invalid-argument'), - INVALID_RECIPIENT: createMessagingErrorInfo('invalid-recipient'), - INVALID_PAYLOAD: createMessagingErrorInfo('invalid-payload'), - INVALID_DATA_PAYLOAD_KEY: createMessagingErrorInfo('invalid-data-payload-key'), - PAYLOAD_SIZE_LIMIT_EXCEEDED: createMessagingErrorInfo('payload-size-limit-exceeded'), - INVALID_OPTIONS: createMessagingErrorInfo('invalid-options'), - INVALID_REGISTRATION_TOKEN: createMessagingErrorInfo('invalid-registration-token'), - REGISTRATION_TOKEN_NOT_REGISTERED: createMessagingErrorInfo('registration-token-not-registered'), - MISMATCHED_CREDENTIAL: createMessagingErrorInfo('mismatched-credential'), - INVALID_PACKAGE_NAME: createMessagingErrorInfo('invalid-package-name'), - DEVICE_MESSAGE_RATE_EXCEEDED: createMessagingErrorInfo('device-message-rate-exceeded'), - TOPICS_MESSAGE_RATE_EXCEEDED: createMessagingErrorInfo('topics-message-rate-exceeded'), - MESSAGE_RATE_EXCEEDED: createMessagingErrorInfo('message-rate-exceeded'), - THIRD_PARTY_AUTH_ERROR: createMessagingErrorInfo('third-party-auth-error'), - TOO_MANY_TOPICS: createMessagingErrorInfo('too-many-topics'), - AUTHENTICATION_ERROR: createMessagingErrorInfo('authentication-error'), - SERVER_UNAVAILABLE: createMessagingErrorInfo('server-unavailable'), - INTERNAL_ERROR: createMessagingErrorInfo('internal-error'), - UNKNOWN_ERROR: createMessagingErrorInfo('unknown-error'), +export const messagingClientErrorCode: { readonly [K in keyof typeof MessagingErrorCode]: ErrorInfo } = { + INVALID_ARGUMENT: { + code: MessagingErrorCode.INVALID_ARGUMENT, + message: 'Invalid argument provided.', + }, + INVALID_RECIPIENT: { + code: MessagingErrorCode.INVALID_RECIPIENT, + message: 'Invalid message recipient provided.', + }, + INVALID_PAYLOAD: { + code: MessagingErrorCode.INVALID_PAYLOAD, + message: 'Invalid message payload provided.', + }, + INVALID_DATA_PAYLOAD_KEY: { + code: MessagingErrorCode.INVALID_DATA_PAYLOAD_KEY, + message: 'The data message payload contains an invalid key. See the reference ' + + 'documentation for the DataMessagePayload type for restricted keys.', + }, + PAYLOAD_SIZE_LIMIT_EXCEEDED: { + code: MessagingErrorCode.PAYLOAD_SIZE_LIMIT_EXCEEDED, + message: 'The provided message payload exceeds the FCM size limits. See the ' + + 'error documentation for more details.', + }, + INVALID_OPTIONS: { + code: MessagingErrorCode.INVALID_OPTIONS, + message: 'Invalid message options provided.', + }, + INVALID_REGISTRATION_TOKEN: { + code: MessagingErrorCode.INVALID_REGISTRATION_TOKEN, + message: 'Invalid registration token provided. Make sure it matches the ' + + 'registration token the client app receives from registering with FCM.', + }, + REGISTRATION_TOKEN_NOT_REGISTERED: { + code: MessagingErrorCode.REGISTRATION_TOKEN_NOT_REGISTERED, + message: 'The provided registration token is not registered. A ' + + 'previously valid registration token can be unregistered for a variety of reasons. See the ' + + 'error documentation for more details. Remove this registration token and stop using it to ' + + 'send messages.', + }, + MISMATCHED_CREDENTIAL: { + code: MessagingErrorCode.MISMATCHED_CREDENTIAL, + message: 'The credential used to authenticate this SDK does not have permission ' + + 'to send messages to the device corresponding to the provided registration token. Make sure the ' + + 'credential and registration token both belong to the same Firebase project.', + }, + INVALID_PACKAGE_NAME: { + code: MessagingErrorCode.INVALID_PACKAGE_NAME, + message: 'The message was addressed to a registration token whose package name does ' + + 'not match the provided "restrictedPackageName" option.', + }, + DEVICE_MESSAGE_RATE_EXCEEDED: { + code: MessagingErrorCode.DEVICE_MESSAGE_RATE_EXCEEDED, + message: 'The rate of messages to a particular device is too high. Reduce ' + + 'the number of messages sent to this device and do not immediately retry sending to this device.', + }, + TOPICS_MESSAGE_RATE_EXCEEDED: { + code: MessagingErrorCode.TOPICS_MESSAGE_RATE_EXCEEDED, + message: 'The rate of messages to subscribers to a particular topic is too ' + + 'high. Reduce the number of messages sent for this topic, and do not immediately retry sending ' + + 'to this topic.', + }, + MESSAGE_RATE_EXCEEDED: { + code: MessagingErrorCode.MESSAGE_RATE_EXCEEDED, + message: 'Sending limit exceeded for the message target.', + }, + THIRD_PARTY_AUTH_ERROR: { + code: MessagingErrorCode.THIRD_PARTY_AUTH_ERROR, + message: 'A message targeted to an iOS device could not be sent because the ' + + 'required APNs SSL certificate was not uploaded or has expired. Check the validity of your ' + + 'development and production certificates.', + }, + TOO_MANY_TOPICS: { + code: MessagingErrorCode.TOO_MANY_TOPICS, + message: 'The maximum number of topics the provided registration token can be ' + + 'subscribed to has been exceeded.', + }, + AUTHENTICATION_ERROR: { + code: MessagingErrorCode.AUTHENTICATION_ERROR, + message: 'An error occurred when trying to authenticate to the FCM servers. Make ' + + 'sure the credential used to authenticate this SDK has the proper permissions. See ' + + 'https://firebase.google.com/docs/admin/setup for setup instructions.', + }, + SERVER_UNAVAILABLE: { + code: MessagingErrorCode.SERVER_UNAVAILABLE, + message: 'The FCM server could not process the request in time. See the error ' + + 'documentation for more details.', + }, + INTERNAL_ERROR: { + code: MessagingErrorCode.INTERNAL_ERROR, + message: 'An internal error has occurred. Please retry the request.', + }, + UNKNOWN_ERROR: { + code: MessagingErrorCode.UNKNOWN_ERROR, + message: 'An unknown server error was returned.', + }, }; -/** @const {Record} Messaging server to client enum error codes. */ -const MESSAGING_SERVER_TO_CLIENT_CODE: Record = { +/** @const {Record} Messaging server to client enum error codes. */ +const MESSAGING_SERVER_TO_CLIENT_CODE: Record = { /* GENERIC ERRORS */ // Generic invalid message parameter provided. InvalidParameters: 'INVALID_ARGUMENT', @@ -168,8 +199,11 @@ const MESSAGING_SERVER_TO_CLIENT_CODE: Record = { UNSPECIFIED_ERROR: 'UNKNOWN_ERROR', }; -/** @const {Record} Topic management (IID) server to client enum error codes. */ -const TOPIC_MGT_SERVER_TO_CLIENT_CODE: Record = { +/** + * @const {Record} Topic management (IID) + * server to client enum error codes. + */ +const TOPIC_MGT_SERVER_TO_CLIENT_CODE: Record = { /* TOPIC SUBSCRIPTION MANAGEMENT ERRORS */ NOT_FOUND: 'REGISTRATION_TOKEN_NOT_REGISTERED', INVALID_ARGUMENT: 'INVALID_REGISTRATION_TOKEN', diff --git a/src/phone-number-verification/error.ts b/src/phone-number-verification/error.ts index efd9647adc..898f890005 100644 --- a/src/phone-number-verification/error.ts +++ b/src/phone-number-verification/error.ts @@ -16,16 +16,22 @@ import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; -export const FPNV_ERROR_CODE_MAPPING = { +/** + * The constant mapping for valid Phone Number Verification client error codes. + */ +export const PhoneNumberVerificationErrorCode = { INVALID_ARGUMENT: 'invalid-argument', INVALID_TOKEN: 'invalid-token', EXPIRED_TOKEN: 'expired-token', -} satisfies Record; +} as const; +/** + * The type definition for valid Phone Number Verification client error codes. + */ export type PhoneNumberVerificationErrorCode = - | 'invalid-argument' - | 'invalid-token' - | 'expired-token'; + typeof PhoneNumberVerificationErrorCode[keyof typeof PhoneNumberVerificationErrorCode]; + +export const FPNV_ERROR_CODE_MAPPING = PhoneNumberVerificationErrorCode; /** * Firebase Phone Number Verification error code structure. This extends `PrefixedFirebaseError`. @@ -36,11 +42,5 @@ export type PhoneNumberVerificationErrorCode = export class FirebasePhoneNumberVerificationError extends PrefixedFirebaseError { constructor(info: ErrorInfo, message?: string) { super('phone-number-verification', info.code, message || info.message, info.httpResponse, info.cause); - - /* tslint:disable:max-line-length */ - // Set the prototype explicitly. See the following link for more details: - // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - /* tslint:enable:max-line-length */ - (this as any).__proto__ = FirebasePhoneNumberVerificationError.prototype; } } diff --git a/src/project-management/error.ts b/src/project-management/error.ts index bc4052499c..7c734372de 100644 --- a/src/project-management/error.ts +++ b/src/project-management/error.ts @@ -17,18 +17,24 @@ import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; /** - * Project Management client error codes. + * The constant mapping for valid Project Management client error codes. */ -export type ProjectManagementErrorCode = - | 'already-exists' - | 'authentication-error' - | 'internal-error' - | 'invalid-argument' - | 'invalid-project-id' - | 'invalid-server-response' - | 'not-found' - | 'service-unavailable' - | 'unknown-error'; +export const ProjectManagementErrorCode = { + ALREADY_EXISTS: 'already-exists', + AUTHENTICATION_ERROR: 'authentication-error', + INTERNAL_ERROR: 'internal-error', + INVALID_ARGUMENT: 'invalid-argument', + INVALID_PROJECT_ID: 'invalid-project-id', + INVALID_SERVER_RESPONSE: 'invalid-server-response', + NOT_FOUND: 'not-found', + SERVICE_UNAVAILABLE: 'service-unavailable', + UNKNOWN_ERROR: 'unknown-error', +} as const; + +/** + * The type definition for valid Project Management client error codes. + */ +export type ProjectManagementErrorCode = typeof ProjectManagementErrorCode[keyof typeof ProjectManagementErrorCode]; /** * Firebase project management error code structure. This extends PrefixedFirebaseError. diff --git a/src/remote-config/error.ts b/src/remote-config/error.ts index 17c303dea0..ae25bce881 100644 --- a/src/remote-config/error.ts +++ b/src/remote-config/error.ts @@ -32,20 +32,26 @@ export const ERROR_CODE_MAPPING: Record = { }; /** - * Remote Config client error codes and their default messages. + * The constant mapping for valid Remote Config client error codes. */ -export type RemoteConfigErrorCode = - | 'aborted' - | 'already-exists' - | 'failed-precondition' - | 'internal-error' - | 'invalid-argument' - | 'not-found' - | 'out-of-range' - | 'permission-denied' - | 'resource-exhausted' - | 'unauthenticated' - | 'unknown-error'; +export const RemoteConfigErrorCode = { + ABORTED: 'aborted', + ALREADY_EXISTS: 'already-exists', + FAILED_PRECONDITION: 'failed-precondition', + INTERNAL_ERROR: 'internal-error', + INVALID_ARGUMENT: 'invalid-argument', + NOT_FOUND: 'not-found', + OUT_OF_RANGE: 'out-of-range', + PERMISSION_DENIED: 'permission-denied', + RESOURCE_EXHAUSTED: 'resource-exhausted', + UNAUTHENTICATED: 'unauthenticated', + UNKNOWN_ERROR: 'unknown-error', +} as const; + +/** + * The type definition for valid Remote Config client error codes. + */ +export type RemoteConfigErrorCode = typeof RemoteConfigErrorCode[keyof typeof RemoteConfigErrorCode]; /** * Firebase Remote Config error code structure. This extends PrefixedFirebaseError. diff --git a/src/security-rules/error.ts b/src/security-rules/error.ts index b9c4547556..37b327c9bb 100644 --- a/src/security-rules/error.ts +++ b/src/security-rules/error.ts @@ -17,18 +17,24 @@ import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; /** - * Security Rules client error codes and their default messages. + * The constant mapping for valid Security Rules client error codes. */ -export type SecurityRulesErrorCode = - 'already-exists' - | 'authentication-error' - | 'internal-error' - | 'invalid-argument' - | 'invalid-server-response' - | 'not-found' - | 'resource-exhausted' - | 'service-unavailable' - | 'unknown-error'; +export const SecurityRulesErrorCode = { + ALREADY_EXISTS: 'already-exists', + AUTHENTICATION_ERROR: 'authentication-error', + INTERNAL_ERROR: 'internal-error', + INVALID_ARGUMENT: 'invalid-argument', + INVALID_SERVER_RESPONSE: 'invalid-server-response', + NOT_FOUND: 'not-found', + RESOURCE_EXHAUSTED: 'resource-exhausted', + SERVICE_UNAVAILABLE: 'service-unavailable', + UNKNOWN_ERROR: 'unknown-error', +} as const; + +/** + * The type definition for valid Security Rules client error codes. + */ +export type SecurityRulesErrorCode = typeof SecurityRulesErrorCode[keyof typeof SecurityRulesErrorCode]; export class FirebaseSecurityRulesError extends PrefixedFirebaseError { /** diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index e37891d5d2..642a628b8c 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -17,7 +17,7 @@ import { FirebaseApp } from '../app/firebase-app'; import { toHttpResponse } from './error'; -import { AppErrorCodes, FirebaseAppError } from '../app/error'; +import { AppErrorCode, FirebaseAppError } from '../app/error'; import * as validator from './validator'; import http = require('http'); @@ -143,7 +143,7 @@ class DefaultRequestResponse implements RequestResponse { this.text = resp.data; try { if (!resp.data) { - throw new FirebaseAppError({ code: AppErrorCodes.INTERNAL_ERROR, message: 'HTTP response missing data.' }); + throw new FirebaseAppError({ code: AppErrorCode.INTERNAL_ERROR, message: 'HTTP response missing data.' }); } this.parsedData = JSON.parse(resp.data); } catch (err) { @@ -157,7 +157,7 @@ class DefaultRequestResponse implements RequestResponse { return this.parsedData; } throw new FirebaseAppError({ - code: AppErrorCodes.UNABLE_TO_PARSE_RESPONSE, + code: AppErrorCode.UNABLE_TO_PARSE_RESPONSE, message: 'Error while parsing response data', cause: this.parseError as Error, httpResponse: toHttpResponse(this) @@ -187,14 +187,14 @@ class MultipartRequestResponse implements RequestResponse { get text(): string { throw new FirebaseAppError({ - code: AppErrorCodes.UNABLE_TO_PARSE_RESPONSE, + code: AppErrorCode.UNABLE_TO_PARSE_RESPONSE, message: 'Unable to parse multipart payload as text' }); } get data(): any { throw new FirebaseAppError({ - code: AppErrorCodes.UNABLE_TO_PARSE_RESPONSE, + code: AppErrorCode.UNABLE_TO_PARSE_RESPONSE, message: 'Unable to parse multipart payload as JSON' }); } @@ -260,7 +260,7 @@ export function defaultRetryConfig(): RetryConfig { function validateRetryConfig(retry: RetryConfig): void { if (!validator.isNumber(retry.maxRetries) || retry.maxRetries < 0) { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_ARGUMENT, + code: AppErrorCode.INVALID_ARGUMENT, message: 'maxRetries must be a non-negative integer' }); } @@ -268,7 +268,7 @@ function validateRetryConfig(retry: RetryConfig): void { if (typeof retry.backOffFactor !== 'undefined') { if (!validator.isNumber(retry.backOffFactor) || retry.backOffFactor < 0) { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_ARGUMENT, + code: AppErrorCode.INVALID_ARGUMENT, message: 'backOffFactor must be a non-negative number' }); } @@ -276,17 +276,17 @@ function validateRetryConfig(retry: RetryConfig): void { if (!validator.isNumber(retry.maxDelayInMillis) || retry.maxDelayInMillis < 0) { throw new FirebaseAppError({ - code: AppErrorCodes.INVALID_ARGUMENT, + code: AppErrorCode.INVALID_ARGUMENT, message: 'maxDelayInMillis must be a non-negative integer' }); } if (typeof retry.statusCodes !== 'undefined' && !validator.isArray(retry.statusCodes)) { - throw new FirebaseAppError({ code: AppErrorCodes.INVALID_ARGUMENT, message: 'statusCodes must be an array' }); + throw new FirebaseAppError({ code: AppErrorCode.INVALID_ARGUMENT, message: 'statusCodes must be an array' }); } if (typeof retry.ioErrorCodes !== 'undefined' && !validator.isArray(retry.ioErrorCodes)) { - throw new FirebaseAppError({ code: AppErrorCodes.INVALID_ARGUMENT, message: 'ioErrorCodes must be an array' }); + throw new FirebaseAppError({ code: AppErrorCode.INVALID_ARGUMENT, message: 'ioErrorCodes must be an array' }); } } @@ -386,7 +386,7 @@ export class RequestClient { } if (!this.retry) { - throw new FirebaseAppError({ code: AppErrorCodes.INTERNAL_ERROR, message: 'Expected this.retry to exist.' }); + throw new FirebaseAppError({ code: AppErrorCode.INTERNAL_ERROR, message: 'Expected this.retry to exist.' }); } const backOffFactor = this.retry.backOffFactor || 0; @@ -447,13 +447,13 @@ export class HttpClient extends RequestClient { if (err.code === 'ETIMEDOUT') { throw new FirebaseAppError({ - code: AppErrorCodes.NETWORK_TIMEOUT, + code: AppErrorCode.NETWORK_TIMEOUT, message: `Error while making request: ${err.message}.`, cause: err }); } throw new FirebaseAppError({ - code: AppErrorCodes.NETWORK_ERROR, + code: AppErrorCode.NETWORK_ERROR, message: `Error while making request: ${err.message}. Error code: ${err.code}`, cause: err }); @@ -513,13 +513,13 @@ export class Http2Client extends RequestClient { if (err.code === 'ETIMEDOUT') { throw new FirebaseAppError({ - code: AppErrorCodes.NETWORK_TIMEOUT, + code: AppErrorCode.NETWORK_TIMEOUT, message: `Error while making request: ${err.message}.`, cause: err }); } throw new FirebaseAppError({ - code: AppErrorCodes.NETWORK_ERROR, + code: AppErrorCode.NETWORK_ERROR, message: `Error while making request: ${err.message}. Error code: ${err.code}`, cause: err }); @@ -569,7 +569,7 @@ export function parseHttpResponse( request: null, }; if (!validator.isNumber(lowLevelResponse.status)) { - throw new FirebaseAppError({ code: AppErrorCodes.INTERNAL_ERROR, message: 'Malformed HTTP status line.' }); + throw new FirebaseAppError({ code: AppErrorCode.INTERNAL_ERROR, message: 'Malformed HTTP status line.' }); } return new DefaultRequestResponse(lowLevelResponse); } @@ -794,7 +794,7 @@ class AsyncHttpCall extends AsyncRequestCall { if (!res.statusCode) { throw new FirebaseAppError({ - code: AppErrorCodes.INTERNAL_ERROR, + code: AppErrorCode.INTERNAL_ERROR, message: 'Expected a statusCode on the response from a ClientRequest' }); } @@ -902,7 +902,7 @@ class AsyncHttp2Call extends AsyncRequestCall { if (!headers[':status']) { throw new FirebaseAppError({ - code: AppErrorCodes.INTERNAL_ERROR, + code: AppErrorCode.INTERNAL_ERROR, message: 'Expected a statusCode on the response from a ClientRequest' }); } @@ -1366,7 +1366,7 @@ export class Http2SessionHandler { http2Session.on('goaway', (errorCode, _, opaqueData) => { this.reject(new FirebaseAppError({ - code: AppErrorCodes.NETWORK_ERROR, + code: AppErrorCode.NETWORK_ERROR, message: `Error while making requests: GOAWAY - ${opaqueData?.toString()}, Error code: ${errorCode}` })); }) @@ -1380,7 +1380,7 @@ export class Http2SessionHandler { errorMessage = `Session error while making requests: ${error.code} - ${error.message} ` } this.reject(new FirebaseAppError({ - code: AppErrorCodes.NETWORK_ERROR, + code: AppErrorCode.NETWORK_ERROR, message: errorMessage, cause: error, })); diff --git a/test/unit/app/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts index 2192e84962..ce2b07cdbc 100644 --- a/test/unit/app/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -35,7 +35,7 @@ import { auth, messaging, machineLearning, storage, firestore, database, instanceId, installations, projectManagement, securityRules, remoteConfig, appCheck, } from '../../../src/firebase-namespace-api'; -import { FirebaseAppError, AppErrorCodes } from '../../../src/app/error'; +import { FirebaseAppError, AppErrorCode } from '../../../src/app/error'; import Auth = auth.Auth; import Database = database.Database; @@ -850,7 +850,7 @@ describe('FirebaseApp', () => { it('Includes the original error in exception', async () => { getTokenStub.restore(); const mockError = new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: 'Something went wrong' }); getTokenStub = sinon.stub(ServiceAccountCredential.prototype, 'getAccessToken').rejects(mockError); @@ -869,7 +869,7 @@ describe('FirebaseApp', () => { it('Returns a detailed message when an error is due to an invalid_grant', async () => { getTokenStub.restore(); const mockError = new FirebaseAppError({ - code: AppErrorCodes.INVALID_CREDENTIAL, + code: AppErrorCode.INVALID_CREDENTIAL, message: 'Failed to get credentials: invalid_grant (reason)' }); getTokenStub = sinon.stub(ServiceAccountCredential.prototype, 'getAccessToken').rejects(mockError); From afd6a90a9868f7bfc10ab722a9421474bc13257f Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Wed, 6 May 2026 12:30:38 -0400 Subject: [PATCH 22/22] fix: Address gemini review --- etc/firebase-admin.database.api.md | 4 ++-- etc/firebase-admin.firestore.api.md | 4 ++-- etc/firebase-admin.installations.api.md | 4 ++-- etc/firebase-admin.instance-id.api.md | 4 ++-- src/database/error.ts | 13 ++++--------- src/extensions/error.ts | 3 --- src/firestore/error.ts | 13 ++++--------- src/installations/error.ts | 13 ++++--------- src/instance-id/error.ts | 13 ++++--------- src/messaging/error.ts | 3 +-- 10 files changed, 25 insertions(+), 49 deletions(-) diff --git a/etc/firebase-admin.database.api.md b/etc/firebase-admin.database.api.md index 1d28f4a2aa..1b3b1f86dc 100644 --- a/etc/firebase-admin.database.api.md +++ b/etc/firebase-admin.database.api.md @@ -28,10 +28,10 @@ export const enableLogging: typeof rtdb.enableLogging; export { EventType } -// Warning: (ae-forgotten-export) The symbol "FirebaseError" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "PrefixedFirebaseError" needs to be exported by the entry point index.d.ts // // @public -export class FirebaseDatabaseError extends FirebaseError { +export class FirebaseDatabaseError extends PrefixedFirebaseError { // Warning: (ae-forgotten-export) The symbol "ErrorInfo" needs to be exported by the entry point index.d.ts constructor(info: ErrorInfo, message?: string); } diff --git a/etc/firebase-admin.firestore.api.md b/etc/firebase-admin.firestore.api.md index e7fa353032..983462539a 100644 --- a/etc/firebase-admin.firestore.api.md +++ b/etc/firebase-admin.firestore.api.md @@ -100,10 +100,10 @@ export { FieldValue } export { Filter } -// Warning: (ae-forgotten-export) The symbol "FirebaseError" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "PrefixedFirebaseError" needs to be exported by the entry point index.d.ts // // @public -export class FirebaseFirestoreError extends FirebaseError { +export class FirebaseFirestoreError extends PrefixedFirebaseError { // Warning: (ae-forgotten-export) The symbol "ErrorInfo" needs to be exported by the entry point index.d.ts constructor(info: ErrorInfo, message?: string); } diff --git a/etc/firebase-admin.installations.api.md b/etc/firebase-admin.installations.api.md index f0c7a278c6..a04a8893eb 100644 --- a/etc/firebase-admin.installations.api.md +++ b/etc/firebase-admin.installations.api.md @@ -6,10 +6,10 @@ import { Agent } from 'http'; -// Warning: (ae-forgotten-export) The symbol "FirebaseError" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "PrefixedFirebaseError" needs to be exported by the entry point index.d.ts // // @public -export class FirebaseInstallationsError extends FirebaseError { +export class FirebaseInstallationsError extends PrefixedFirebaseError { // Warning: (ae-forgotten-export) The symbol "ErrorInfo" needs to be exported by the entry point index.d.ts constructor(info: ErrorInfo, message?: string); } diff --git a/etc/firebase-admin.instance-id.api.md b/etc/firebase-admin.instance-id.api.md index 20d748a49c..52ce830004 100644 --- a/etc/firebase-admin.instance-id.api.md +++ b/etc/firebase-admin.instance-id.api.md @@ -6,10 +6,10 @@ import { Agent } from 'http'; -// Warning: (ae-forgotten-export) The symbol "FirebaseError" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "PrefixedFirebaseError" needs to be exported by the entry point index.d.ts // // @public -export class FirebaseInstanceIdError extends FirebaseError { +export class FirebaseInstanceIdError extends PrefixedFirebaseError { // Warning: (ae-forgotten-export) The symbol "ErrorInfo" needs to be exported by the entry point index.d.ts constructor(info: ErrorInfo, message?: string); } diff --git a/src/database/error.ts b/src/database/error.ts index c5babe3871..15bbd45bea 100644 --- a/src/database/error.ts +++ b/src/database/error.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { FirebaseError, ErrorInfo } from '../utils/error'; +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; /** - * Firebase Database error code structure. This extends FirebaseError. + * Firebase Database error code structure. This extends PrefixedFirebaseError. */ -export class FirebaseDatabaseError extends FirebaseError { +export class FirebaseDatabaseError extends PrefixedFirebaseError { /** * @param info - The error code info. * @param message - The error message. This will override the default @@ -27,11 +27,6 @@ export class FirebaseDatabaseError extends FirebaseError { */ constructor(info: ErrorInfo, message?: string) { // Override default message if custom message provided. - super({ - code: 'database/' + info.code, - message: message || info.message, - httpResponse: info.httpResponse, - cause: info.cause - }); + super('database', info.code, message || info.message, info.httpResponse, info.cause); } } diff --git a/src/extensions/error.ts b/src/extensions/error.ts index 135853ab76..14b7f024ac 100644 --- a/src/extensions/error.ts +++ b/src/extensions/error.ts @@ -16,9 +16,6 @@ import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; -/** - * Extensions client error codes and their default messages. - */ /** * The constant mapping for valid Extensions client error codes. */ diff --git a/src/firestore/error.ts b/src/firestore/error.ts index a9340877ea..6207cbda31 100644 --- a/src/firestore/error.ts +++ b/src/firestore/error.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { FirebaseError, ErrorInfo } from '../utils/error'; +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; /** - * Firebase Firestore error code structure. This extends FirebaseError. + * Firebase Firestore error code structure. This extends PrefixedFirebaseError. */ -export class FirebaseFirestoreError extends FirebaseError { +export class FirebaseFirestoreError extends PrefixedFirebaseError { /** * @param info - The error code info. * @param message - The error message. This will override the default @@ -27,11 +27,6 @@ export class FirebaseFirestoreError extends FirebaseError { */ constructor(info: ErrorInfo, message?: string) { // Override default message if custom message provided. - super({ - code: 'firestore/' + info.code, - message: message || info.message, - httpResponse: info.httpResponse, - cause: info.cause - }); + super('firestore', info.code, message || info.message, info.httpResponse, info.cause); } } diff --git a/src/installations/error.ts b/src/installations/error.ts index 2a8d480563..8f53d5c874 100644 --- a/src/installations/error.ts +++ b/src/installations/error.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseError, ErrorInfo } from '../utils/error'; +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; /** * The constant mapping for valid Installations client error codes. @@ -54,9 +54,9 @@ export const installationsClientErrorCode: { readonly [K in keyof typeof Install }; /** - * Firebase Installations service error code structure. This extends `FirebaseError`. + * Firebase Installations service error code structure. This extends `PrefixedFirebaseError`. */ -export class FirebaseInstallationsError extends FirebaseError { +export class FirebaseInstallationsError extends PrefixedFirebaseError { /** * * @param info - The error code info. @@ -65,11 +65,6 @@ export class FirebaseInstallationsError extends FirebaseError { */ constructor(info: ErrorInfo, message?: string) { // Override default message if custom message provided. - super({ - code: 'installations/' + info.code, - message: message || info.message, - httpResponse: info.httpResponse, - cause: info.cause - }); + super('installations', info.code, message || info.message, info.httpResponse, info.cause); } } diff --git a/src/instance-id/error.ts b/src/instance-id/error.ts index ebe33833b6..e0cda50167 100644 --- a/src/instance-id/error.ts +++ b/src/instance-id/error.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseError, ErrorInfo } from '../utils/error'; +import { PrefixedFirebaseError, ErrorInfo } from '../utils/error'; import { installationsClientErrorCode } from '../installations/error'; /** @@ -45,9 +45,9 @@ export const instanceIdClientErrorCode: { readonly [K in keyof typeof InstanceId }; /** - * Firebase Instance ID service error code structure. This extends `FirebaseError`. + * Firebase Instance ID service error code structure. This extends `PrefixedFirebaseError`. */ -export class FirebaseInstanceIdError extends FirebaseError { +export class FirebaseInstanceIdError extends PrefixedFirebaseError { /** * * @param info - The error code info. @@ -56,11 +56,6 @@ export class FirebaseInstanceIdError extends FirebaseError { */ constructor(info: ErrorInfo, message?: string) { // Override default message if custom message provided. - super({ - code: 'instance-id/' + info.code, - message: message || info.message, - httpResponse: info.httpResponse, - cause: info.cause - }); + super('instance-id', info.code, message || info.message, info.httpResponse, info.cause); } } diff --git a/src/messaging/error.ts b/src/messaging/error.ts index 702502fbdb..749479e085 100644 --- a/src/messaging/error.ts +++ b/src/messaging/error.ts @@ -311,8 +311,7 @@ export class FirebaseMessagingSessionError extends FirebaseMessagingError { /** @returns The object representation of the error. */ public toJSON(): object { return { - code: this.code, - message: this.message, + ...super.toJSON(), pendingBatchResponse: this.pendingBatchResponse, }; }