123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- /*! firebase-admin v12.1.1 */
- "use strict";
- /*!
- * Copyright 2021 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.JwtErrorCode = exports.JwtError = exports.decodeJwt = exports.verifyJwtSignature = exports.EmulatorSignatureVerifier = exports.PublicKeySignatureVerifier = exports.UrlKeyFetcher = exports.JwksFetcher = exports.ALGORITHM_RS256 = void 0;
- const validator = require("./validator");
- const jwt = require("jsonwebtoken");
- const jwks = require("jwks-rsa");
- const api_request_1 = require("../utils/api-request");
- exports.ALGORITHM_RS256 = 'RS256';
- // `jsonwebtoken` converts errors from the `getKey` callback to its own `JsonWebTokenError` type
- // and prefixes the error message with the following. Use the prefix to identify errors thrown
- // from the key provider callback.
- // https://github.com/auth0/node-jsonwebtoken/blob/d71e383862fc735991fd2e759181480f066bf138/verify.js#L96
- const JWT_CALLBACK_ERROR_PREFIX = 'error in secret or public key callback: ';
- const NO_MATCHING_KID_ERROR_MESSAGE = 'no-matching-kid-error';
- const NO_KID_IN_HEADER_ERROR_MESSAGE = 'no-kid-in-header-error';
- const HOUR_IN_SECONDS = 3600;
- class JwksFetcher {
- constructor(jwksUrl) {
- this.publicKeysExpireAt = 0;
- if (!validator.isURL(jwksUrl)) {
- throw new Error('The provided JWKS URL is not a valid URL.');
- }
- this.client = jwks({
- jwksUri: jwksUrl,
- cache: false, // disable jwks-rsa LRU cache as the keys are always cached for 6 hours.
- });
- }
- fetchPublicKeys() {
- if (this.shouldRefresh()) {
- return this.refresh();
- }
- return Promise.resolve(this.publicKeys);
- }
- shouldRefresh() {
- return !this.publicKeys || this.publicKeysExpireAt <= Date.now();
- }
- refresh() {
- return this.client.getSigningKeys()
- .then((signingKeys) => {
- // reset expire at from previous set of keys.
- this.publicKeysExpireAt = 0;
- const newKeys = signingKeys.reduce((map, signingKey) => {
- map[signingKey.kid] = signingKey.getPublicKey();
- return map;
- }, {});
- this.publicKeysExpireAt = Date.now() + (HOUR_IN_SECONDS * 6 * 1000);
- this.publicKeys = newKeys;
- return newKeys;
- }).catch((err) => {
- throw new Error(`Error fetching Json Web Keys: ${err.message}`);
- });
- }
- }
- exports.JwksFetcher = JwksFetcher;
- /**
- * Class to fetch public keys from a client certificates URL.
- */
- class UrlKeyFetcher {
- constructor(clientCertUrl, httpAgent) {
- this.clientCertUrl = clientCertUrl;
- this.httpAgent = httpAgent;
- this.publicKeysExpireAt = 0;
- if (!validator.isURL(clientCertUrl)) {
- throw new Error('The provided public client certificate URL is not a valid URL.');
- }
- }
- /**
- * Fetches the public keys for the Google certs.
- *
- * @returns A promise fulfilled with public keys for the Google certs.
- */
- fetchPublicKeys() {
- if (this.shouldRefresh()) {
- return this.refresh();
- }
- return Promise.resolve(this.publicKeys);
- }
- /**
- * Checks if the cached public keys need to be refreshed.
- *
- * @returns Whether the keys should be fetched from the client certs url or not.
- */
- shouldRefresh() {
- return !this.publicKeys || this.publicKeysExpireAt <= Date.now();
- }
- refresh() {
- const client = new api_request_1.HttpClient();
- const request = {
- method: 'GET',
- url: this.clientCertUrl,
- httpAgent: this.httpAgent,
- };
- return client.send(request).then((resp) => {
- if (!resp.isJson() || resp.data.error) {
- // Treat all non-json messages and messages with an 'error' field as
- // error responses.
- throw new api_request_1.HttpError(resp);
- }
- // reset expire at from previous set of keys.
- this.publicKeysExpireAt = 0;
- if (Object.prototype.hasOwnProperty.call(resp.headers, 'cache-control')) {
- const cacheControlHeader = resp.headers['cache-control'];
- const parts = cacheControlHeader.split(',');
- parts.forEach((part) => {
- const subParts = part.trim().split('=');
- if (subParts[0] === 'max-age') {
- const maxAge = +subParts[1];
- this.publicKeysExpireAt = Date.now() + (maxAge * 1000);
- }
- });
- }
- this.publicKeys = resp.data;
- return resp.data;
- }).catch((err) => {
- if (err instanceof api_request_1.HttpError) {
- let errorMessage = 'Error fetching public keys for Google certs: ';
- const resp = err.response;
- if (resp.isJson() && resp.data.error) {
- errorMessage += `${resp.data.error}`;
- if (resp.data.error_description) {
- errorMessage += ' (' + resp.data.error_description + ')';
- }
- }
- else {
- errorMessage += `${resp.text}`;
- }
- throw new Error(errorMessage);
- }
- throw err;
- });
- }
- }
- exports.UrlKeyFetcher = UrlKeyFetcher;
- /**
- * Class for verifying JWT signature with a public key.
- */
- class PublicKeySignatureVerifier {
- constructor(keyFetcher) {
- this.keyFetcher = keyFetcher;
- if (!validator.isNonNullObject(keyFetcher)) {
- throw new Error('The provided key fetcher is not an object or null.');
- }
- }
- static withCertificateUrl(clientCertUrl, httpAgent) {
- return new PublicKeySignatureVerifier(new UrlKeyFetcher(clientCertUrl, httpAgent));
- }
- static withJwksUrl(jwksUrl) {
- return new PublicKeySignatureVerifier(new JwksFetcher(jwksUrl));
- }
- verify(token) {
- if (!validator.isString(token)) {
- return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, 'The provided token must be a string.'));
- }
- return verifyJwtSignature(token, getKeyCallback(this.keyFetcher), { algorithms: [exports.ALGORITHM_RS256] })
- .catch((error) => {
- if (error.code === JwtErrorCode.NO_KID_IN_HEADER) {
- // No kid in JWT header. Try with all the public keys.
- return this.verifyWithoutKid(token);
- }
- throw error;
- });
- }
- verifyWithoutKid(token) {
- return this.keyFetcher.fetchPublicKeys()
- .then(publicKeys => this.verifyWithAllKeys(token, publicKeys));
- }
- verifyWithAllKeys(token, keys) {
- const promises = [];
- Object.values(keys).forEach((key) => {
- const result = verifyJwtSignature(token, key)
- .then(() => true)
- .catch((error) => {
- if (error.code === JwtErrorCode.TOKEN_EXPIRED) {
- throw error;
- }
- return false;
- });
- promises.push(result);
- });
- return Promise.all(promises)
- .then((result) => {
- if (result.every((r) => r === false)) {
- throw new JwtError(JwtErrorCode.INVALID_SIGNATURE, 'Invalid token signature.');
- }
- });
- }
- }
- exports.PublicKeySignatureVerifier = PublicKeySignatureVerifier;
- /**
- * Class for verifying unsigned (emulator) JWTs.
- */
- class EmulatorSignatureVerifier {
- verify(token) {
- // Signature checks skipped for emulator; no need to fetch public keys.
- return verifyJwtSignature(token, undefined, { algorithms: ['none'] });
- }
- }
- exports.EmulatorSignatureVerifier = EmulatorSignatureVerifier;
- /**
- * Provides a callback to fetch public keys.
- *
- * @param fetcher - KeyFetcher to fetch the keys from.
- * @returns A callback function that can be used to get keys in `jsonwebtoken`.
- */
- function getKeyCallback(fetcher) {
- return (header, callback) => {
- if (!header.kid) {
- callback(new Error(NO_KID_IN_HEADER_ERROR_MESSAGE));
- }
- const kid = header.kid || '';
- fetcher.fetchPublicKeys().then((publicKeys) => {
- if (!Object.prototype.hasOwnProperty.call(publicKeys, kid)) {
- callback(new Error(NO_MATCHING_KID_ERROR_MESSAGE));
- }
- else {
- callback(null, publicKeys[kid]);
- }
- })
- .catch(error => {
- callback(error);
- });
- };
- }
- /**
- * Verifies the signature of a JWT using the provided secret or a function to fetch
- * the secret or public key.
- *
- * @param token - The JWT to be verified.
- * @param secretOrPublicKey - The secret or a function to fetch the secret or public key.
- * @param options - JWT verification options.
- * @returns A Promise resolving for a token with a valid signature.
- */
- function verifyJwtSignature(token, secretOrPublicKey, options) {
- if (!validator.isString(token)) {
- return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, 'The provided token must be a string.'));
- }
- return new Promise((resolve, reject) => {
- jwt.verify(token, secretOrPublicKey, options, (error) => {
- if (!error) {
- return resolve();
- }
- if (error.name === 'TokenExpiredError') {
- return reject(new JwtError(JwtErrorCode.TOKEN_EXPIRED, 'The provided token has expired. Get a fresh token from your ' +
- 'client app and try again.'));
- }
- else if (error.name === 'JsonWebTokenError') {
- if (error.message && error.message.includes(JWT_CALLBACK_ERROR_PREFIX)) {
- const message = error.message.split(JWT_CALLBACK_ERROR_PREFIX).pop() || 'Error fetching public keys.';
- let code = JwtErrorCode.KEY_FETCH_ERROR;
- if (message === NO_MATCHING_KID_ERROR_MESSAGE) {
- code = JwtErrorCode.NO_MATCHING_KID;
- }
- else if (message === NO_KID_IN_HEADER_ERROR_MESSAGE) {
- code = JwtErrorCode.NO_KID_IN_HEADER;
- }
- return reject(new JwtError(code, message));
- }
- }
- return reject(new JwtError(JwtErrorCode.INVALID_SIGNATURE, error.message));
- });
- });
- }
- exports.verifyJwtSignature = verifyJwtSignature;
- /**
- * Decodes general purpose Firebase JWTs.
- *
- * @param jwtToken - JWT token to be decoded.
- * @returns Decoded token containing the header and payload.
- */
- function decodeJwt(jwtToken) {
- if (!validator.isString(jwtToken)) {
- return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, 'The provided token must be a string.'));
- }
- const fullDecodedToken = jwt.decode(jwtToken, {
- complete: true,
- });
- if (!fullDecodedToken) {
- return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, 'Decoding token failed.'));
- }
- const header = fullDecodedToken?.header;
- const payload = fullDecodedToken?.payload;
- return Promise.resolve({ header, payload });
- }
- exports.decodeJwt = decodeJwt;
- /**
- * Jwt error code structure.
- *
- * @param code - The error code.
- * @param message - The error message.
- * @constructor
- */
- class JwtError extends Error {
- constructor(code, message) {
- super(message);
- this.code = code;
- this.message = message;
- this.__proto__ = JwtError.prototype;
- }
- }
- exports.JwtError = JwtError;
- /**
- * JWT error codes.
- */
- var JwtErrorCode;
- (function (JwtErrorCode) {
- JwtErrorCode["INVALID_ARGUMENT"] = "invalid-argument";
- JwtErrorCode["INVALID_CREDENTIAL"] = "invalid-credential";
- JwtErrorCode["TOKEN_EXPIRED"] = "token-expired";
- JwtErrorCode["INVALID_SIGNATURE"] = "invalid-token";
- JwtErrorCode["NO_MATCHING_KID"] = "no-matching-kid-error";
- JwtErrorCode["NO_KID_IN_HEADER"] = "no-kid-error";
- JwtErrorCode["KEY_FETCH_ERROR"] = "key-fetch-error";
- })(JwtErrorCode || (exports.JwtErrorCode = JwtErrorCode = {}));
|