token-generator.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /*! firebase-admin v12.1.1 */
  2. "use strict";
  3. /*!
  4. * @license
  5. * Copyright 2017 Google Inc.
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. */
  19. Object.defineProperty(exports, "__esModule", { value: true });
  20. exports.handleCryptoSignerError = exports.FirebaseTokenGenerator = exports.EmulatedSigner = exports.BLACKLISTED_CLAIMS = void 0;
  21. const error_1 = require("../utils/error");
  22. const crypto_signer_1 = require("../utils/crypto-signer");
  23. const validator = require("../utils/validator");
  24. const utils_1 = require("../utils");
  25. const ALGORITHM_NONE = 'none';
  26. const ONE_HOUR_IN_SECONDS = 60 * 60;
  27. // List of blacklisted claims which cannot be provided when creating a custom token
  28. exports.BLACKLISTED_CLAIMS = [
  29. 'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat', 'iss', 'jti',
  30. 'nbf', 'nonce',
  31. ];
  32. // Audience to use for Firebase Auth Custom tokens
  33. const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit';
  34. /**
  35. * A CryptoSigner implementation that is used when communicating with the Auth emulator.
  36. * It produces unsigned tokens.
  37. */
  38. class EmulatedSigner {
  39. constructor() {
  40. this.algorithm = ALGORITHM_NONE;
  41. }
  42. /**
  43. * @inheritDoc
  44. */
  45. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  46. sign(buffer) {
  47. return Promise.resolve(Buffer.from(''));
  48. }
  49. /**
  50. * @inheritDoc
  51. */
  52. getAccountId() {
  53. return Promise.resolve('firebase-auth-emulator@example.com');
  54. }
  55. }
  56. exports.EmulatedSigner = EmulatedSigner;
  57. /**
  58. * Class for generating different types of Firebase Auth tokens (JWTs).
  59. *
  60. * @internal
  61. */
  62. class FirebaseTokenGenerator {
  63. /**
  64. * @param tenantId - The tenant ID to use for the generated Firebase Auth
  65. * Custom token. If absent, then no tenant ID claim will be set in the
  66. * resulting JWT.
  67. */
  68. constructor(signer, tenantId) {
  69. this.tenantId = tenantId;
  70. if (!validator.isNonNullObject(signer)) {
  71. throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, 'INTERNAL ASSERT: Must provide a CryptoSigner to use FirebaseTokenGenerator.');
  72. }
  73. if (typeof this.tenantId !== 'undefined' && !validator.isNonEmptyString(this.tenantId)) {
  74. throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, '`tenantId` argument must be a non-empty string.');
  75. }
  76. this.signer = signer;
  77. }
  78. /**
  79. * Creates a new Firebase Auth Custom token.
  80. *
  81. * @param uid - The user ID to use for the generated Firebase Auth Custom token.
  82. * @param developerClaims - Optional developer claims to include in the generated Firebase
  83. * Auth Custom token.
  84. * @returns A Promise fulfilled with a Firebase Auth Custom token signed with a
  85. * service account key and containing the provided payload.
  86. */
  87. createCustomToken(uid, developerClaims) {
  88. let errorMessage;
  89. if (!validator.isNonEmptyString(uid)) {
  90. errorMessage = '`uid` argument must be a non-empty string uid.';
  91. }
  92. else if (uid.length > 128) {
  93. errorMessage = '`uid` argument must a uid with less than or equal to 128 characters.';
  94. }
  95. else if (!this.isDeveloperClaimsValid_(developerClaims)) {
  96. errorMessage = '`developerClaims` argument must be a valid, non-null object containing the developer claims.';
  97. }
  98. if (errorMessage) {
  99. throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage);
  100. }
  101. const claims = {};
  102. if (typeof developerClaims !== 'undefined') {
  103. for (const key in developerClaims) {
  104. /* istanbul ignore else */
  105. if (Object.prototype.hasOwnProperty.call(developerClaims, key)) {
  106. if (exports.BLACKLISTED_CLAIMS.indexOf(key) !== -1) {
  107. throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, `Developer claim "${key}" is reserved and cannot be specified.`);
  108. }
  109. claims[key] = developerClaims[key];
  110. }
  111. }
  112. }
  113. return this.signer.getAccountId().then((account) => {
  114. const header = {
  115. alg: this.signer.algorithm,
  116. typ: 'JWT',
  117. };
  118. const iat = Math.floor(Date.now() / 1000);
  119. const body = {
  120. aud: FIREBASE_AUDIENCE,
  121. iat,
  122. exp: iat + ONE_HOUR_IN_SECONDS,
  123. iss: account,
  124. sub: account,
  125. uid,
  126. };
  127. if (this.tenantId) {
  128. body.tenant_id = this.tenantId;
  129. }
  130. if (Object.keys(claims).length > 0) {
  131. body.claims = claims;
  132. }
  133. const token = `${this.encodeSegment(header)}.${this.encodeSegment(body)}`;
  134. const signPromise = this.signer.sign(Buffer.from(token));
  135. return Promise.all([token, signPromise]);
  136. }).then(([token, signature]) => {
  137. return `${token}.${this.encodeSegment(signature)}`;
  138. }).catch((err) => {
  139. throw handleCryptoSignerError(err);
  140. });
  141. }
  142. encodeSegment(segment) {
  143. const buffer = (segment instanceof Buffer) ? segment : Buffer.from(JSON.stringify(segment));
  144. return (0, utils_1.toWebSafeBase64)(buffer).replace(/=+$/, '');
  145. }
  146. /**
  147. * Returns whether or not the provided developer claims are valid.
  148. *
  149. * @param developerClaims - Optional developer claims to validate.
  150. * @returns True if the provided claims are valid; otherwise, false.
  151. */
  152. // eslint-disable-next-line @typescript-eslint/naming-convention
  153. isDeveloperClaimsValid_(developerClaims) {
  154. if (typeof developerClaims === 'undefined') {
  155. return true;
  156. }
  157. return validator.isNonNullObject(developerClaims);
  158. }
  159. }
  160. exports.FirebaseTokenGenerator = FirebaseTokenGenerator;
  161. /**
  162. * Creates a new FirebaseAuthError by extracting the error code, message and other relevant
  163. * details from a CryptoSignerError.
  164. *
  165. * @param err - The Error to convert into a FirebaseAuthError error
  166. * @returns A Firebase Auth error that can be returned to the user.
  167. */
  168. function handleCryptoSignerError(err) {
  169. if (!(err instanceof crypto_signer_1.CryptoSignerError)) {
  170. return err;
  171. }
  172. if (err.code === crypto_signer_1.CryptoSignerErrorCode.SERVER_ERROR && validator.isNonNullObject(err.cause)) {
  173. const httpError = err.cause;
  174. const errorResponse = httpError.response.data;
  175. if (validator.isNonNullObject(errorResponse) && errorResponse.error) {
  176. const errorCode = errorResponse.error.status;
  177. const description = 'Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens ' +
  178. 'for more details on how to use and troubleshoot this feature.';
  179. const errorMsg = `${errorResponse.error.message}; ${description}`;
  180. return error_1.FirebaseAuthError.fromServerError(errorCode, errorMsg, errorResponse);
  181. }
  182. return new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_ERROR, 'Error returned from server: ' + errorResponse + '. Additionally, an ' +
  183. 'internal error occurred while attempting to extract the ' +
  184. 'errorcode from the error.');
  185. }
  186. return new error_1.FirebaseAuthError(mapToAuthClientErrorCode(err.code), err.message);
  187. }
  188. exports.handleCryptoSignerError = handleCryptoSignerError;
  189. function mapToAuthClientErrorCode(code) {
  190. switch (code) {
  191. case crypto_signer_1.CryptoSignerErrorCode.INVALID_CREDENTIAL:
  192. return error_1.AuthClientErrorCode.INVALID_CREDENTIAL;
  193. case crypto_signer_1.CryptoSignerErrorCode.INVALID_ARGUMENT:
  194. return error_1.AuthClientErrorCode.INVALID_ARGUMENT;
  195. default:
  196. return error_1.AuthClientErrorCode.INTERNAL_ERROR;
  197. }
  198. }