/*! firebase-admin v6.0.0 */ "use strict"; /*! * Copyright 2017 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 }); var error_1 = require("../utils/error"); var api_request_1 = require("../utils/api-request"); var validator = require("../utils/validator"); var utils_1 = require("../utils"); var ALGORITHM_RS256 = 'RS256'; var ONE_HOUR_IN_SECONDS = 60 * 60; // List of blacklisted claims which cannot be provided when creating a custom token var BLACKLISTED_CLAIMS = [ 'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat', 'iss', 'jti', 'nbf', 'nonce', ]; // Audience to use for Firebase Auth Custom tokens var FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit'; /** * A CryptoSigner implementation that uses an explicitly specified service account private key to * sign data. Performs all operations locally, and does not make any RPC calls. */ var ServiceAccountSigner = /** @class */ (function () { /** * Creates a new CryptoSigner instance from the given service account certificate. * * @param {Certificate} certificate A service account certificate. */ function ServiceAccountSigner(certificate) { if (!certificate) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, 'INTERNAL ASSERT: Must provide a certificate to initialize ServiceAccountSigner.'); } if (!validator.isNonEmptyString(certificate.clientEmail) || !validator.isNonEmptyString(certificate.privateKey)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, 'INTERNAL ASSERT: Must provide a certificate with validate clientEmail and privateKey to ' + 'initialize ServiceAccountSigner.'); } this.certificate = certificate; } /** * @inheritDoc */ ServiceAccountSigner.prototype.sign = function (buffer) { var crypto = require('crypto'); var sign = crypto.createSign('RSA-SHA256'); sign.update(buffer); return Promise.resolve(sign.sign(this.certificate.privateKey)); }; /** * @inheritDoc */ ServiceAccountSigner.prototype.getAccountId = function () { return Promise.resolve(this.certificate.clientEmail); }; return ServiceAccountSigner; }()); exports.ServiceAccountSigner = ServiceAccountSigner; /** * A CryptoSigner implementation that uses the remote IAM service to sign data. If initialized without * a service account ID, attempts to discover a service account ID by consulting the local Metadata * service. This will succeed in managed environments like Google Cloud Functions and App Engine. * * @see https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signBlob * @see https://cloud.google.com/compute/docs/storing-retrieving-metadata */ var IAMSigner = /** @class */ (function () { function IAMSigner(httpClient, serviceAccountId) { if (!httpClient) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'INTERNAL ASSERT: Must provide a HTTP client to initialize IAMSigner.'); } if (typeof serviceAccountId !== 'undefined' && !validator.isNonEmptyString(serviceAccountId)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'INTERNAL ASSERT: Service account ID must be undefined or a non-empty string.'); } this.httpClient = httpClient; this.serviceAccountId = serviceAccountId; } /** * @inheritDoc */ IAMSigner.prototype.sign = function (buffer) { var _this = this; return this.getAccountId().then(function (serviceAccount) { var request = { method: 'POST', url: "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" + serviceAccount + ":signBlob", data: { bytesToSign: buffer.toString('base64') }, }; return _this.httpClient.send(request); }).then(function (response) { // Response from IAM is base64 encoded. Decode it into a buffer and return. return Buffer.from(response.data.signature, 'base64'); }).catch(function (err) { if (err instanceof api_request_1.HttpError) { var error = err.response.data; var errorCode = void 0; var errorMsg = void 0; if (validator.isNonNullObject(error) && error.error) { errorCode = error.error.status || null; var description = 'Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens ' + 'for more details on how to use and troubleshoot this feature.'; errorMsg = error.error.message + "; " + description || null; } throw error_1.FirebaseAuthError.fromServerError(errorCode, errorMsg, error); } throw err; }); }; /** * @inheritDoc */ IAMSigner.prototype.getAccountId = function () { var _this = this; if (validator.isNonEmptyString(this.serviceAccountId)) { return Promise.resolve(this.serviceAccountId); } var request = { method: 'GET', url: 'http://metadata/computeMetadata/v1/instance/service-accounts/default/email', headers: { 'Metadata-Flavor': 'Google', }, }; var client = new api_request_1.HttpClient(); return client.send(request).then(function (response) { _this.serviceAccountId = response.text; return _this.serviceAccountId; }).catch(function (err) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, "Failed to determine service account. Make sure to initialize " + "the SDK with a service account credential. Alternatively specify a service " + ("account with iam.serviceAccounts.signBlob permission. Original error: " + err)); }); }; return IAMSigner; }()); exports.IAMSigner = IAMSigner; /** * Create a new CryptoSigner instance for the given app. If the app has been initialized with a service * account credential, creates a ServiceAccountSigner. Otherwise creates an IAMSigner. * * @param {FirebaseApp} app A FirebaseApp instance. * @return {CryptoSigner} A CryptoSigner instance. */ function cryptoSignerFromApp(app) { var cert = app.options.credential.getCertificate(); if (cert != null && validator.isNonEmptyString(cert.privateKey) && validator.isNonEmptyString(cert.clientEmail)) { return new ServiceAccountSigner(cert); } return new IAMSigner(new api_request_1.AuthorizedHttpClient(app), app.options.serviceAccountId); } exports.cryptoSignerFromApp = cryptoSignerFromApp; /** * Class for generating different types of Firebase Auth tokens (JWTs). */ var FirebaseTokenGenerator = /** @class */ (function () { function FirebaseTokenGenerator(signer) { if (!validator.isNonNullObject(signer)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, 'INTERNAL ASSERT: Must provide a CryptoSigner to use FirebaseTokenGenerator.'); } this.signer = signer; } /** * Creates a new Firebase Auth Custom token. * * @param {string} uid The user ID to use for the generated Firebase Auth Custom token. * @param {object} [developerClaims] Optional developer claims to include in the generated Firebase * Auth Custom token. * @return {Promise} A Promise fulfilled with a Firebase Auth Custom token signed with a * service account key and containing the provided payload. */ FirebaseTokenGenerator.prototype.createCustomToken = function (uid, developerClaims) { var _this = this; var errorMessage; if (typeof uid !== 'string' || uid === '') { errorMessage = 'First argument to createCustomToken() must be a non-empty string uid.'; } else if (uid.length > 128) { errorMessage = 'First argument to createCustomToken() must a uid with less than or equal to 128 characters.'; } else if (!this.isDeveloperClaimsValid_(developerClaims)) { errorMessage = 'Second argument to createCustomToken() must be an object containing the developer claims.'; } if (typeof errorMessage !== 'undefined') { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); } var claims = {}; if (typeof developerClaims !== 'undefined') { for (var key in developerClaims) { /* istanbul ignore else */ if (developerClaims.hasOwnProperty(key)) { if (BLACKLISTED_CLAIMS.indexOf(key) !== -1) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, "Developer claim \"" + key + "\" is reserved and cannot be specified."); } claims[key] = developerClaims[key]; } } } return this.signer.getAccountId().then(function (account) { var header = { alg: ALGORITHM_RS256, typ: 'JWT', }; var iat = Math.floor(Date.now() / 1000); var body = { aud: FIREBASE_AUDIENCE, iat: iat, exp: iat + ONE_HOUR_IN_SECONDS, iss: account, sub: account, uid: uid, }; if (Object.keys(claims).length > 0) { body.claims = claims; } var token = _this.encodeSegment(header) + "." + _this.encodeSegment(body); var signPromise = _this.signer.sign(Buffer.from(token)); return Promise.all([token, signPromise]); }).then(function (_a) { var token = _a[0], signature = _a[1]; return token + "." + _this.encodeSegment(signature); }); }; FirebaseTokenGenerator.prototype.encodeSegment = function (segment) { var buffer = (segment instanceof Buffer) ? segment : Buffer.from(JSON.stringify(segment)); return utils_1.toWebSafeBase64(buffer).replace(/\=+$/, ''); }; /** * Returns whether or not the provided developer claims are valid. * * @param {object} [developerClaims] Optional developer claims to validate. * @return {boolean} True if the provided claims are valid; otherwise, false. */ FirebaseTokenGenerator.prototype.isDeveloperClaimsValid_ = function (developerClaims) { if (typeof developerClaims === 'undefined') { return true; } return validator.isNonNullObject(developerClaims); }; return FirebaseTokenGenerator; }()); exports.FirebaseTokenGenerator = FirebaseTokenGenerator;