UNPKG

@pedwise/next-firebase-auth-edge

Version:

Next.js 13 Firebase Authentication for Edge and server runtimes. Dedicated for Next 13 server components. Compatible with Next.js middleware.

624 lines (598 loc) 21.8 kB
import { JwtError, JwtErrorCode } from "./jwt/error"; import { FirebaseTokenInfo } from "./firebase"; export class FirebaseError extends Error { constructor(private errorInfo: ErrorInfo) { super(errorInfo.message); Object.setPrototypeOf(this, FirebaseError.prototype); } public get code(): string { return this.errorInfo.code; } public get message(): string { return this.errorInfo.message; } public toJSON(): object { return { code: this.code, message: this.message, }; } } export interface ErrorInfo { code: string; message: string; } interface ServerToClientCode { [code: string]: string; } export class PrefixedFirebaseError extends FirebaseError { constructor(private prefix: string, code: string, message: string) { super({ code: `${prefix}/${code}`, message, }); Object.setPrototypeOf(this, PrefixedFirebaseError.prototype); } public hasCode(code: string): boolean { return `${this.prefix}/${code}` === this.code; } } export class FirebaseAuthError extends PrefixedFirebaseError { public static fromServerError( serverErrorCode: string, message?: string, rawServerResponse?: object ): FirebaseAuthError { const colonSeparator = (serverErrorCode || "").indexOf(":"); let customMessage = null; if (colonSeparator !== -1) { customMessage = serverErrorCode.substring(colonSeparator + 1).trim(); serverErrorCode = serverErrorCode.substring(0, colonSeparator).trim(); } const clientCodeKey = AUTH_SERVER_TO_CLIENT_CODE[serverErrorCode] || "INTERNAL_ERROR"; const error: ErrorInfo = JSON.parse( JSON.stringify( AuthClientErrorCode[clientCodeKey as keyof AuthClientErrorCode] ) ); error.message = customMessage || message || error.message; if ( clientCodeKey === "INTERNAL_ERROR" && typeof rawServerResponse !== "undefined" ) { try { error.message += ` Raw server response: "${JSON.stringify( rawServerResponse )}"`; } catch (e) { // Ignore JSON parsing error. } } return new FirebaseAuthError(error); } constructor(info: ErrorInfo, message?: string) { super("auth", info.code, message || info.message); Object.setPrototypeOf(this, FirebaseAuthError.prototype); } public static toAuthErrorWithStack( code: ErrorInfo, message: string, jwtError: JwtError ) { const error = new FirebaseAuthError(code, message); error.stack = jwtError.stack; return error; } public static fromJwtError( error: JwtError, tokenInfo: FirebaseTokenInfo, shortNameArticle: string ): FirebaseAuthError { const verifyJwtTokenDocsMessage = ` See ${tokenInfo.url} ` + `for details on how to retrieve ${shortNameArticle} ${tokenInfo.shortName}.`; if (error.code === JwtErrorCode.TOKEN_EXPIRED) { const errorMessage = `${tokenInfo.jwtName} has expired. Get a fresh ${tokenInfo.shortName}` + ` from your client app and try again (auth/${tokenInfo.expiredErrorCode.code}).` + verifyJwtTokenDocsMessage; return FirebaseAuthError.toAuthErrorWithStack( tokenInfo.expiredErrorCode, errorMessage, error ); } else if (error.code === JwtErrorCode.INVALID_SIGNATURE) { const errorMessage = `${tokenInfo.jwtName} has invalid signature.` + verifyJwtTokenDocsMessage; return FirebaseAuthError.toAuthErrorWithStack( AuthClientErrorCode.INVALID_ARGUMENT, errorMessage, error ); } else if (error.code === JwtErrorCode.NO_MATCHING_KID) { const errorMessage = `${tokenInfo.jwtName} has "kid" claim which does not ` + `correspond to a known public key. Most likely the ${tokenInfo.shortName} ` + "is expired, so get a fresh token from your client app and try again."; return FirebaseAuthError.toAuthErrorWithStack( AuthClientErrorCode.INVALID_ARGUMENT, errorMessage, error ); } return FirebaseAuthError.toAuthErrorWithStack( AuthClientErrorCode.INVALID_ARGUMENT, error.message, error ); } } export class AuthClientErrorCode { public static AUTH_BLOCKING_TOKEN_EXPIRED = { code: "auth-blocking-token-expired", message: "The provided Firebase Auth Blocking token is expired.", }; public static BILLING_NOT_ENABLED = { code: "billing-not-enabled", message: "Feature requires billing to be enabled.", }; public static CLAIMS_TOO_LARGE = { code: "claims-too-large", message: "Developer claims maximum payload size exceeded.", }; public static CONFIGURATION_EXISTS = { code: "configuration-exists", message: "A configuration already exists with the provided identifier.", }; public static CONFIGURATION_NOT_FOUND = { code: "configuration-not-found", message: "There is no configuration corresponding to the provided identifier.", }; public static ID_TOKEN_EXPIRED = { code: "id-token-expired", message: "The provided Firebase ID token is expired.", }; public static INVALID_ARGUMENT = { code: "argument-error", message: "Invalid argument provided.", }; public static INVALID_CONFIG = { code: "invalid-config", message: "The provided configuration is invalid.", }; public static EMAIL_ALREADY_EXISTS = { code: "email-already-exists", message: "The email address is already in use by another account.", }; public static EMAIL_NOT_FOUND = { code: "email-not-found", message: "There is no user record corresponding to the provided email.", }; public static FORBIDDEN_CLAIM = { code: "reserved-claim", message: "The specified developer claim is reserved and cannot be specified.", }; public static INVALID_ID_TOKEN = { code: "invalid-id-token", message: "The provided ID token is not a valid Firebase ID token.", }; public static ID_TOKEN_REVOKED = { code: "id-token-revoked", message: "The Firebase ID token has been revoked.", }; public static INTERNAL_ERROR = { code: "internal-error", message: "An internal error has occurred.", }; public static INVALID_CLAIMS = { code: "invalid-claims", message: "The provided custom claim attributes are invalid.", }; public static INVALID_CONTINUE_URI = { code: "invalid-continue-uri", message: "The continue URL must be a valid URL string.", }; public static INVALID_CREATION_TIME = { code: "invalid-creation-time", message: "The creation time must be a valid UTC date string.", }; public static INVALID_CREDENTIAL = { code: "invalid-credential", message: "Invalid credential object provided.", }; public static INVALID_DISABLED_FIELD = { code: "invalid-disabled-field", message: "The disabled field must be a boolean.", }; public static INVALID_DISPLAY_NAME = { code: "invalid-display-name", message: "The displayName field must be a valid string.", }; public static INVALID_DYNAMIC_LINK_DOMAIN = { code: "invalid-dynamic-link-domain", message: "The provided dynamic link domain is not configured or authorized " + "for the current project.", }; public static INVALID_EMAIL_VERIFIED = { code: "invalid-email-verified", message: "The emailVerified field must be a boolean.", }; public static INVALID_EMAIL = { code: "invalid-email", message: "The email address is improperly formatted.", }; public static INVALID_NEW_EMAIL = { code: "invalid-new-email", message: "The new email address is improperly formatted.", }; public static INVALID_ENROLLED_FACTORS = { code: "invalid-enrolled-factors", message: "The enrolled factors must be a valid array of MultiFactorInfo objects.", }; public static INVALID_ENROLLMENT_TIME = { code: "invalid-enrollment-time", message: "The second factor enrollment time must be a valid UTC date string.", }; public static INVALID_HASH_ALGORITHM = { code: "invalid-hash-algorithm", message: "The hash algorithm must match one of the strings in the list of " + "supported algorithms.", }; public static INVALID_HASH_BLOCK_SIZE = { code: "invalid-hash-block-size", message: "The hash block size must be a valid number.", }; public static INVALID_HASH_DERIVED_KEY_LENGTH = { code: "invalid-hash-derived-key-length", message: "The hash derived key length must be a valid number.", }; public static INVALID_HASH_KEY = { code: "invalid-hash-key", message: "The hash key must a valid byte buffer.", }; public static INVALID_HASH_MEMORY_COST = { code: "invalid-hash-memory-cost", message: "The hash memory cost must be a valid number.", }; public static INVALID_HASH_PARALLELIZATION = { code: "invalid-hash-parallelization", message: "The hash parallelization must be a valid number.", }; public static INVALID_HASH_ROUNDS = { code: "invalid-hash-rounds", message: "The hash rounds must be a valid number.", }; public static INVALID_HASH_SALT_SEPARATOR = { code: "invalid-hash-salt-separator", message: "The hashing algorithm salt separator field must be a valid byte buffer.", }; public static INVALID_LAST_SIGN_IN_TIME = { code: "invalid-last-sign-in-time", message: "The last sign-in time must be a valid UTC date string.", }; public static INVALID_NAME = { code: "invalid-name", message: "The resource name provided is invalid.", }; public static INVALID_OAUTH_CLIENT_ID = { code: "invalid-oauth-client-id", message: "The provided OAuth client ID is invalid.", }; public static INVALID_PAGE_TOKEN = { code: "invalid-page-token", message: "The page token must be a valid non-empty string.", }; public static INVALID_PASSWORD = { code: "invalid-password", message: "The password must be a string with at least 6 characters.", }; public static INVALID_PASSWORD_HASH = { code: "invalid-password-hash", message: "The password hash must be a valid byte buffer.", }; public static INVALID_PASSWORD_SALT = { code: "invalid-password-salt", message: "The password salt must be a valid byte buffer.", }; public static INVALID_PHONE_NUMBER = { code: "invalid-phone-number", message: "The phone number must be a non-empty E.164 standard compliant identifier " + "string.", }; public static INVALID_PHOTO_URL = { code: "invalid-photo-url", message: "The photoURL field must be a valid URL.", }; public static INVALID_PROJECT_ID = { code: "invalid-project-id", message: "Invalid parent project. Either parent project doesn't exist or didn't enable multi-tenancy.", }; public static INVALID_PROVIDER_DATA = { code: "invalid-provider-data", message: "The providerData must be a valid array of UserInfo objects.", }; public static INVALID_PROVIDER_ID = { code: "invalid-provider-id", message: "The providerId must be a valid supported provider identifier string.", }; public static INVALID_PROVIDER_UID = { code: "invalid-provider-uid", message: "The providerUid must be a valid provider uid string.", }; public static INVALID_OAUTH_RESPONSETYPE = { code: "invalid-oauth-responsetype", message: "Only exactly one OAuth responseType should be set to true.", }; public static 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.", }; public static INVALID_TENANT_ID = { code: "invalid-tenant-id", message: "The tenant ID must be a valid non-empty string.", }; public static INVALID_TENANT_TYPE = { code: "invalid-tenant-type", message: 'Tenant type must be either "full_service" or "lightweight".', }; public static INVALID_TESTING_PHONE_NUMBER = { code: "invalid-testing-phone-number", message: "Invalid testing phone number or invalid test code provided.", }; public static INVALID_UID = { code: "invalid-uid", message: "The uid must be a non-empty string with at most 128 characters.", }; public static INVALID_USER_IMPORT = { code: "invalid-user-import", message: "The user record to import is invalid.", }; public static INVALID_TOKENS_VALID_AFTER_TIME = { code: "invalid-tokens-valid-after-time", message: "The tokensValidAfterTime must be a valid UTC number in seconds.", }; public static MISMATCHING_TENANT_ID = { code: "mismatching-tenant-id", message: "User tenant ID does not match with the current TenantAwareAuth tenant ID.", }; public static 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.", }; public static MISSING_CONFIG = { code: "missing-config", message: "The provided configuration is missing required attributes.", }; public static MISSING_CONTINUE_URI = { code: "missing-continue-uri", message: "A valid continue URL must be provided in the request.", }; public static MISSING_DISPLAY_NAME = { code: "missing-display-name", message: "The resource being created or edited is missing a valid display name.", }; public static MISSING_EMAIL = { code: "missing-email", message: "The email is required for the specified action. For example, a multi-factor user " + "requires a verified email.", }; public static MISSING_IOS_BUNDLE_ID = { code: "missing-ios-bundle-id", message: "The request is missing an iOS Bundle ID.", }; public static MISSING_ISSUER = { code: "missing-issuer", message: "The OAuth/OIDC configuration issuer must not be empty.", }; public static MISSING_HASH_ALGORITHM = { code: "missing-hash-algorithm", message: "Importing users with password hashes requires that the hashing " + "algorithm and its parameters be provided.", }; public static MISSING_OAUTH_CLIENT_ID = { code: "missing-oauth-client-id", message: "The OAuth/OIDC configuration client ID must not be empty.", }; public static MISSING_OAUTH_CLIENT_SECRET = { code: "missing-oauth-client-secret", message: "The OAuth configuration client secret is required to enable OIDC code flow.", }; public static MISSING_PROVIDER_ID = { code: "missing-provider-id", message: "A valid provider ID must be provided in the request.", }; public static MISSING_SAML_RELYING_PARTY_CONFIG = { code: "missing-saml-relying-party-config", message: "The SAML configuration provided is missing a relying party configuration.", }; public static 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.", }; public static MAXIMUM_USER_COUNT_EXCEEDED = { code: "maximum-user-count-exceeded", message: "The maximum allowed number of users to import has been exceeded.", }; public static MISSING_UID = { code: "missing-uid", message: "A uid identifier is required for the current operation.", }; public static 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.", }; public static PHONE_NUMBER_ALREADY_EXISTS = { code: "phone-number-already-exists", message: "The user with the provided phone number already exists.", }; public static PROJECT_NOT_FOUND = { code: "project-not-found", message: "No Firebase project was found for the provided credential.", }; public static 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.", }; public static QUOTA_EXCEEDED = { code: "quota-exceeded", message: "The project quota for the specified operation has been exceeded.", }; public static SECOND_FACTOR_LIMIT_EXCEEDED = { code: "second-factor-limit-exceeded", message: "The maximum number of allowed second factors on a user has been exceeded.", }; public static SECOND_FACTOR_UID_ALREADY_EXISTS = { code: "second-factor-uid-already-exists", message: 'The specified second factor "uid" already exists.', }; public static SESSION_COOKIE_EXPIRED = { code: "session-cookie-expired", message: "The Firebase session cookie is expired.", }; public static SESSION_COOKIE_REVOKED = { code: "session-cookie-revoked", message: "The Firebase session cookie has been revoked.", }; public static TENANT_NOT_FOUND = { code: "tenant-not-found", message: "There is no tenant corresponding to the provided identifier.", }; public static UID_ALREADY_EXISTS = { code: "uid-already-exists", message: "The user with the provided uid already exists.", }; public static UNAUTHORIZED_DOMAIN = { code: "unauthorized-continue-uri", message: "The domain of the continue URL is not whitelisted. Whitelist the domain in the " + "Firebase console.", }; public static UNSUPPORTED_FIRST_FACTOR = { code: "unsupported-first-factor", message: "A multi-factor user requires a supported first factor.", }; public static UNSUPPORTED_SECOND_FACTOR = { code: "unsupported-second-factor", message: "The request specified an unsupported type of second factor.", }; public static UNSUPPORTED_TENANT_OPERATION = { code: "unsupported-tenant-operation", message: "This operation is not supported in a multi-tenant context.", }; public static 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.", }; public static USER_NOT_FOUND = { code: "user-not-found", message: "There is no user record corresponding to the provided identifier.", }; public static NOT_FOUND = { code: "not-found", message: "The requested resource was not found.", }; public static USER_DISABLED = { code: "user-disabled", message: "The user record is disabled.", }; public static 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).", }; } const AUTH_SERVER_TO_CLIENT_CODE: ServerToClientCode = { 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_CONFIG: "INVALID_CONFIG", INVALID_CONFIG_ID: "INVALID_PROVIDER_ID", INVALID_CONTINUE_URI: "INVALID_CONTINUE_URI", INVALID_DYNAMIC_LINK_DOMAIN: "INVALID_DYNAMIC_LINK_DOMAIN", DUPLICATE_EMAIL: "EMAIL_ALREADY_EXISTS", DUPLICATE_LOCAL_ID: "UID_ALREADY_EXISTS", DUPLICATE_MFA_ENROLLMENT_ID: "SECOND_FACTOR_UID_ALREADY_EXISTS", EMAIL_EXISTS: "EMAIL_ALREADY_EXISTS", EMAIL_NOT_FOUND: "EMAIL_NOT_FOUND", FORBIDDEN_CLAIM: "FORBIDDEN_CLAIM", INVALID_CLAIMS: "INVALID_CLAIMS", INVALID_DURATION: "INVALID_SESSION_COOKIE_DURATION", INVALID_EMAIL: "INVALID_EMAIL", INVALID_NEW_EMAIL: "INVALID_NEW_EMAIL", INVALID_DISPLAY_NAME: "INVALID_DISPLAY_NAME", INVALID_ID_TOKEN: "INVALID_ID_TOKEN", INVALID_NAME: "INVALID_NAME", INVALID_OAUTH_CLIENT_ID: "INVALID_OAUTH_CLIENT_ID", INVALID_PAGE_SELECTION: "INVALID_PAGE_TOKEN", INVALID_PHONE_NUMBER: "INVALID_PHONE_NUMBER", INVALID_PROJECT_ID: "INVALID_PROJECT_ID", INVALID_PROVIDER_ID: "INVALID_PROVIDER_ID", INVALID_SERVICE_ACCOUNT: "INVALID_SERVICE_ACCOUNT", INVALID_TESTING_PHONE_NUMBER: "INVALID_TESTING_PHONE_NUMBER", INVALID_TENANT_TYPE: "INVALID_TENANT_TYPE", MISSING_ANDROID_PACKAGE_NAME: "MISSING_ANDROID_PACKAGE_NAME", MISSING_CONFIG: "MISSING_CONFIG", MISSING_CONFIG_ID: "MISSING_PROVIDER_ID", MISSING_DISPLAY_NAME: "MISSING_DISPLAY_NAME", MISSING_EMAIL: "MISSING_EMAIL", MISSING_IOS_BUNDLE_ID: "MISSING_IOS_BUNDLE_ID", MISSING_ISSUER: "MISSING_ISSUER", MISSING_LOCAL_ID: "MISSING_UID", MISSING_OAUTH_CLIENT_ID: "MISSING_OAUTH_CLIENT_ID", MISSING_PROVIDER_ID: "MISSING_PROVIDER_ID", MISSING_SAML_RELYING_PARTY_CONFIG: "MISSING_SAML_RELYING_PARTY_CONFIG", MISSING_USER_ACCOUNT: "MISSING_UID", OPERATION_NOT_ALLOWED: "OPERATION_NOT_ALLOWED", PERMISSION_DENIED: "INSUFFICIENT_PERMISSION", PHONE_NUMBER_EXISTS: "PHONE_NUMBER_ALREADY_EXISTS", PROJECT_NOT_FOUND: "PROJECT_NOT_FOUND", QUOTA_EXCEEDED: "QUOTA_EXCEEDED", SECOND_FACTOR_LIMIT_EXCEEDED: "SECOND_FACTOR_LIMIT_EXCEEDED", TENANT_NOT_FOUND: "TENANT_NOT_FOUND", TENANT_ID_MISMATCH: "MISMATCHING_TENANT_ID", TOKEN_EXPIRED: "ID_TOKEN_EXPIRED", 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_FOUND: "USER_NOT_FOUND", USER_DISABLED: "USER_DISABLED", WEAK_PASSWORD: "INVALID_PASSWORD", };