UNPKG

firebase-auth-cloudflare-workers

Version:

Zero-dependencies firebase auth library for Cloudflare Workers.

263 lines (262 loc) 12.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthApiClient = exports.FIREBASE_AUTH_SET_CUSTOM_USER_CLAIMS = exports.FIREBASE_AUTH_REVOKE_REFRESH_TOKENS = exports.FIREBASE_AUTH_GET_ACCOUNT_INFO = exports.FIREBASE_AUTH_CREATE_SESSION_COOKIE = void 0; const api_requests_1 = require("./api-requests"); const client_1 = require("./client"); const errors_1 = require("./errors"); const user_record_1 = require("./user-record"); const validator_1 = require("./validator"); /** Minimum allowed session cookie duration in seconds (5 minutes). */ const MIN_SESSION_COOKIE_DURATION_SECS = 5 * 60; /** Maximum allowed session cookie duration in seconds (2 weeks). */ const MAX_SESSION_COOKIE_DURATION_SECS = 14 * 24 * 60 * 60; /** List of reserved claims which cannot be provided when creating a custom token. */ const RESERVED_CLAIMS = [ 'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat', 'iss', 'jti', 'nbf', 'nonce', 'sub', 'firebase', ]; /** Maximum allowed number of characters in the custom claims payload. */ const MAX_CLAIMS_PAYLOAD_SIZE = 1000; /** * Instantiates the createSessionCookie endpoint settings. * * @internal */ exports.FIREBASE_AUTH_CREATE_SESSION_COOKIE = new api_requests_1.ApiSettings('v1', ':createSessionCookie', 'POST') // Set request validator. .setRequestValidator((request) => { // Validate the ID token is a non-empty string. if (!(0, validator_1.isNonEmptyString)(request.idToken)) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INVALID_ID_TOKEN); } // Validate the custom session cookie duration. if (!(0, validator_1.isNumber)(request.validDuration) || request.validDuration < MIN_SESSION_COOKIE_DURATION_SECS || request.validDuration > MAX_SESSION_COOKIE_DURATION_SECS) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION); } }) // Set response validator. .setResponseValidator((response) => { // Response should always contain the session cookie. if (!(0, validator_1.isNonEmptyString)(response.sessionCookie)) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INTERNAL_ERROR); } }); /** * Instantiates the getAccountInfo endpoint settings. * * @internal */ exports.FIREBASE_AUTH_GET_ACCOUNT_INFO = new api_requests_1.ApiSettings('v1', '/accounts:lookup', 'POST') // Set request validator. .setRequestValidator((request) => { if (!request.localId && !request.email && !request.phoneNumber && !request.federatedUserId) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier'); } }) // Set response validator. .setResponseValidator((response) => { if (!response.users || !response.users.length) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.USER_NOT_FOUND); } }); /** * Instantiates the revokeRefreshTokens endpoint settings for updating existing accounts. * * @internal * @link https://github.com/firebase/firebase-admin-node/blob/9955bca47249301aa970679ae99fe01d54adf6a8/src/auth/auth-api-request.ts#L746 */ exports.FIREBASE_AUTH_REVOKE_REFRESH_TOKENS = new api_requests_1.ApiSettings('v1', '/accounts:update', 'POST') // Set request validator. .setRequestValidator((request) => { // localId is a required parameter. if (typeof request.localId === 'undefined') { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier'); } // validSince should be a number. if (typeof request.validSince !== 'undefined' && !(0, validator_1.isNumber)(request.validSince)) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INVALID_TOKENS_VALID_AFTER_TIME); } }) // Set response validator. .setResponseValidator((response) => { // If the localId is not returned, then the request failed. if (!response.localId) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.USER_NOT_FOUND); } }); /** * Instantiates the setCustomUserClaims endpoint settings for updating existing accounts. * * @internal * @link https://github.com/firebase/firebase-admin-node/blob/9955bca47249301aa970679ae99fe01d54adf6a8/src/auth/auth-api-request.ts#L746 */ exports.FIREBASE_AUTH_SET_CUSTOM_USER_CLAIMS = new api_requests_1.ApiSettings('v1', '/accounts:update', 'POST') // Set request validator. .setRequestValidator((request) => { // localId is a required parameter. if (typeof request.localId === 'undefined') { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Server request is missing user identifier'); } // customAttributes should be stringified JSON with no blacklisted claims. // The payload should not exceed 1KB. if (typeof request.customAttributes !== 'undefined') { let developerClaims; try { developerClaims = JSON.parse(request.customAttributes); } catch (error) { if (error instanceof Error) { // JSON parsing error. This should never happen as we stringify the claims internally. // However, we still need to check since setAccountInfo via edit requests could pass // this field. throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INVALID_CLAIMS, error.message); } throw error; } const invalidClaims = []; // Check for any invalid claims. RESERVED_CLAIMS.forEach(blacklistedClaim => { if (Object.prototype.hasOwnProperty.call(developerClaims, blacklistedClaim)) { invalidClaims.push(blacklistedClaim); } }); // Throw an error if an invalid claim is detected. if (invalidClaims.length > 0) { throw new errors_1.FirebaseAuthError(errors_1.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.`); } // Check claims payload does not exceed maxmimum size. if (request.customAttributes.length > MAX_CLAIMS_PAYLOAD_SIZE) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.CLAIMS_TOO_LARGE, `Developer claims payload should not exceed ${MAX_CLAIMS_PAYLOAD_SIZE} characters.`); } } }) // Set response validator. .setResponseValidator((response) => { // If the localId is not returned, then the request failed. if (!response.localId) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.USER_NOT_FOUND); } }); class AuthApiClient extends client_1.BaseClient { /** * Creates a new Firebase session cookie with the specified duration that can be used for * session management (set as a server side session cookie with custom cookie policy). * The session cookie JWT will have the same payload claims as the provided ID token. * * @param idToken - The Firebase ID token to exchange for a session cookie. * @param expiresIn - The session cookie duration in milliseconds. * @param env - An optional parameter specifying the environment in which the function is running. * If the function is running in an emulator environment, this should be set to `EmulatorEnv`. * If not specified, the function will assume it is running in a production environment. * * @returns A promise that resolves on success with the created session cookie. */ async createSessionCookie(idToken, expiresIn, env) { const request = { idToken, // Convert to seconds. validDuration: expiresIn / 1000, }; const res = await this.fetch(exports.FIREBASE_AUTH_CREATE_SESSION_COOKIE, request, env); return res.sessionCookie; } /** * Looks up a user by uid. * * @param uid - The uid of the user to lookup. * @param env - An optional parameter specifying the environment in which the function is running. * If the function is running in an emulator environment, this should be set to `EmulatorEnv`. * If not specified, the function will assume it is running in a production environment. * @returns A promise that resolves with the user information. */ async getAccountInfoByUid(uid, env) { if (!(0, validator_1.isUid)(uid)) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INVALID_UID); } const request = { localId: [uid], }; const res = await this.fetch(exports.FIREBASE_AUTH_GET_ACCOUNT_INFO, request, env); // Returns the user record populated with server response. return new user_record_1.UserRecord(res.users[0]); } /** * Revokes all refresh tokens for the specified user identified by the uid provided. * In addition to revoking all refresh tokens for a user, all ID tokens issued * before revocation will also be revoked on the Auth backend. Any request with an * ID token generated before revocation will be rejected with a token expired error. * Note that due to the fact that the timestamp is stored in seconds, any tokens minted in * the same second as the revocation will still be valid. If there is a chance that a token * was minted in the last second, delay for 1 second before revoking. * * @param uid - The user whose tokens are to be revoked. * @param env - An optional parameter specifying the environment in which the function is running. * If the function is running in an emulator environment, this should be set to `EmulatorEnv`. * If not specified, the function will assume it is running in a production environment. * @returns A promise that resolves when the operation completes * successfully with the user id of the corresponding user. */ async revokeRefreshTokens(uid, env) { // Validate user UID. if (!(0, validator_1.isUid)(uid)) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INVALID_UID); } const request = { localId: uid, // validSince is in UTC seconds. validSince: Math.floor(new Date().getTime() / 1000), }; const res = await this.fetch(exports.FIREBASE_AUTH_REVOKE_REFRESH_TOKENS, request, env); return res.localId; } /** * Sets additional developer claims on an existing user identified by provided UID. * * @param uid - The user to edit. * @param customUserClaims - The developer claims to set. * @param env - An optional parameter specifying the environment in which the function is running. * If the function is running in an emulator environment, this should be set to `EmulatorEnv`. * If not specified, the function will assume it is running in a production environment. * @returns A promise that resolves when the operation completes * with the user id that was edited. */ async setCustomUserClaims(uid, customUserClaims, env) { // Validate user UID. if (!(0, validator_1.isUid)(uid)) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INVALID_UID); } else if (!(0, validator_1.isObject)(customUserClaims)) { throw new errors_1.FirebaseAuthError(errors_1.AuthClientErrorCode.INVALID_ARGUMENT, 'CustomUserClaims argument must be an object or null.'); } // Delete operation. Replace null with an empty object. if (customUserClaims === null) { customUserClaims = {}; } // Construct custom user attribute editting request. const request = { localId: uid, customAttributes: JSON.stringify(customUserClaims), }; const res = await this.fetch(exports.FIREBASE_AUTH_SET_CUSTOM_USER_CLAIMS, request, env); return res.localId; } } exports.AuthApiClient = AuthApiClient;