text: /*! @license Firebase v3.7.3
      Build: 3.7.3-rc.1
      Terms: https://developers.google.com/terms */
  'use strict';
  
  var fs = require('fs');
  var https = require('https');
  var jwt = require('jsonwebtoken');
  var firebase = require('../app-node');
  
  
  var ALGORITHM = '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'
  ];
  
  // URL containing the public keys for the Google certs (whose private keys are used to sign Firebase
  // Auth ID tokens)
  var CLIENT_CERT_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com';
  
  // Audience to use for Firebase Auth Custom tokens
  var FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit';
  
  
  /**
   * Class for generating and verifying different types of Firebase Auth tokens (JWTs).
   *
   * @constructor
   * @param {string|Object} serviceAccount Either the path to a service account key or the service account key itself.
   */
  var FirebaseTokenGenerator = function(serviceAccount) {
    serviceAccount = serviceAccount || process.env.GOOGLE_APPLICATION_CREDENTIALS;
    if (typeof serviceAccount === 'string') {
      try {
        this.serviceAccount = JSON.parse(fs.readFileSync(serviceAccount, 'utf8'));
      } catch (error) {
        throw new Error('Failed to parse service account key file: ' + error);
      }
    } else if (typeof serviceAccount === 'object' && serviceAccount !== null) {
      this.serviceAccount = serviceAccount;
    } else {
      throw new Error('Must provide a service account to use FirebaseTokenGenerator');
    }
  
    if (typeof this.serviceAccount.private_key !== 'string' || this.serviceAccount.private_key === '') {
      throw new Error('Service account key must contain a string "private_key" field');
    } else if (typeof this.serviceAccount.client_email !== 'string' || this.serviceAccount.client_email === '') {
      throw new Error('Service account key must contain a string "client_email" field');
    }
  };
  
  
  /**
   * 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 {string} A Firebase Auth Custom token signed with a service account key and containing
   *                  the provided payload.
   */
  FirebaseTokenGenerator.prototype.createCustomToken = function(uid, developerClaims) {
    if (typeof uid !== 'string' || uid === '') {
      throw new Error('First argument to createCustomToken() must be a non-empty string uid');
    } else if (uid.length > 128) {
      throw new Error('First argument to createCustomToken() must a uid with less than or equal to 128 characters');
    } else if (typeof developerClaims !== 'undefined' && (typeof developerClaims !== 'object' || developerClaims === null || developerClaims instanceof Array)) {
      throw new Error('Optional second argument to createCustomToken() must be an object containing the developer claims');
    }
  
    var jwtPayload = {};
  
    if (typeof developerClaims !== 'undefined') {
      jwtPayload.claims = {};
  
      for (var key in developerClaims) {
        /* istanbul ignore else */
        if (developerClaims.hasOwnProperty(key)) {
          if (BLACKLISTED_CLAIMS.indexOf(key) !== -1) {
            throw new Error('Developer claim "' + key + '" is reserved and cannot be specified');
          }
  
          jwtPayload.claims[key] = developerClaims[key];
        }
      }
    }
    jwtPayload.uid = uid;
  
    return jwt.sign(jwtPayload, this.serviceAccount.private_key, {
      audience: FIREBASE_AUDIENCE,
      expiresIn: ONE_HOUR_IN_SECONDS,
      issuer: this.serviceAccount.client_email,
      subject: this.serviceAccount.client_email,
      algorithm: ALGORITHM
    });
  };
  
  
  /**
   * Verifies the format and signature of a Firebase Auth ID token.
   *
   * @param {string} idToken The Firebase Auth ID token to verify.
   * @return {Promise<Object>} A promise fulfilled with the decoded claims of the Firebase Auth ID
   *                           token.
   */
  FirebaseTokenGenerator.prototype.verifyIdToken = function(idToken) {
    if (typeof idToken !== 'string') {
      throw new Error('First argument to verifyIdToken() must be a Firebase ID token');
    }
  
    if (typeof this.serviceAccount.project_id !== 'string' || this.serviceAccount.project_id === '') {
      throw new Error('verifyIdToken() requires a service account with "project_id" set');
    }
  
    var fullDecodedToken = jwt.decode(idToken, {
      complete: true
    });
  
    var header = fullDecodedToken && fullDecodedToken.header;
    var payload = fullDecodedToken && fullDecodedToken.payload;
  
    var projectIdMatchMessage = ' Make sure the ID token comes from the same Firebase project as the ' +
      'service account used to authenticate this SDK.';
    var verifyIdTokenDocsMessage = ' See https://firebase.google.com/docs/auth/server/verify-id-tokens ' +
      'for details on how to retrieve an ID token.';
  
    var errorMessage;
    if (!fullDecodedToken) {
      errorMessage = 'Decoding Firebase ID token failed. Make sure you passed the entire string JWT ' +
        'which represents an ID token.' + verifyIdTokenDocsMessage;
    } else if (typeof header.kid === 'undefined') {
      var isCustomToken = (payload.aud === FIREBASE_AUDIENCE);
      var isLegacyCustomToken = (header.alg === 'HS256' && payload.v === 0 && 'd' in payload && 'uid' in payload.d);
  
      if (isCustomToken) {
        errorMessage = 'verifyIdToken() expects an ID token, but was given a custom token.';
      } else if (isLegacyCustomToken) {
        errorMessage = 'verifyIdToken() expects an ID token, but was given a legacy custom token.';
      } else {
        errorMessage = 'Firebase ID token has no "kid" claim.';
      }
  
      errorMessage += verifyIdTokenDocsMessage;
    } else if (header.alg !== ALGORITHM) {
      errorMessage = 'Firebase ID token has incorrect algorithm. Expected "' + ALGORITHM + '" but got ' +
        '"' + header.alg + '".' + verifyIdTokenDocsMessage;
    } else if (payload.aud !== this.serviceAccount.project_id) {
      errorMessage = 'Firebase ID token has incorrect "aud" (audience) claim. Expected "' + this.serviceAccount.project_id +
        '" but got "' + payload.aud + '".' + projectIdMatchMessage + verifyIdTokenDocsMessage;
    } else if (payload.iss !== 'https://securetoken.google.com/' + this.serviceAccount.project_id) {
      errorMessage = 'Firebase ID token has incorrect "iss" (issuer) claim. Expected "https://securetoken.google.com/' +
        this.serviceAccount.project_id + '" but got "' + payload.iss + '".' + projectIdMatchMessage + verifyIdTokenDocsMessage;
    } else if (typeof payload.sub !== 'string') {
      errorMessage = 'Firebase ID token has no "sub" (subject) claim.' + verifyIdTokenDocsMessage;
    } else if (payload.sub === '') {
      errorMessage = 'Firebase ID token has an empty string "sub" (subject) claim.' + verifyIdTokenDocsMessage;
    } else if (payload.sub.length > 128) {
      errorMessage = 'Firebase ID token has "sub" (subject) claim longer than 128 characters.' + verifyIdTokenDocsMessage;
    }
  
    if (typeof errorMessage !== 'undefined') {
      return firebase.Promise.reject(new Error(errorMessage));
    }
  
    return this._fetchPublicKeys().then(function(publicKeys) {
      if (!publicKeys.hasOwnProperty(header.kid)) {
        return firebase.Promise.reject('Firebase ID token has "kid" claim which does not correspond to ' +
          'a known public key. Most likely the ID token is expired, so get a fresh token from your client ' +
          'app and try again.' + verifyIdTokenDocsMessage);
      }
  
      return new firebase.Promise(function(resolve, reject) {
        jwt.verify(idToken, publicKeys[header.kid], {
          algorithms: [ALGORITHM]
        }, function(error, decodedToken) {
          if (error) {
            if (error.name === 'TokenExpiredError') {
              error = 'Firebase ID token has expired. Get a fresh token from your client app and try ' +
                'again.' + verifyIdTokenDocsMessage;
            } else if (error.name === 'JsonWebTokenError') {
              error = 'Firebase ID token has invalid signature.' + verifyIdTokenDocsMessage;
            }
            reject(error);
          } else {
            decodedToken.uid = decodedToken.sub;
            resolve(decodedToken);
          }
        });
      });
    });
  };
  
  
  /**
   * Fetches the public keys for the Google certs.
   *
   * @return {Promise<Object>} A promise fulfilled with public keys for the Google certs.
   */
  FirebaseTokenGenerator.prototype._fetchPublicKeys = function() {
    if (typeof this._publicKeys !== 'undefined' && typeof this._publicKeysExpireAt !== 'undefined' && Date.now() < this._publicKeysExpireAt) {
      return firebase.Promise.resolve(this._publicKeys);
    }
  
    var self = this;
  
    return new firebase.Promise(function(resolve, reject) {
      https.get(CLIENT_CERT_URL, function(res) {
        var buffers = [];
  
        res.on('data', function(buffer) {
          buffers.push(buffer);
        });
  
        res.on('end', function() {
          try {
            var response = JSON.parse(Buffer.concat(buffers));
  
            if (response.error) {
              var errorMessage = 'Error fetching public keys for Google certs: ' + response.error;
              /* istanbul ignore else */
              if (response.error_description) {
                errorMessage += ' (' + response.error_description + ')';
              }
              reject(new Error(errorMessage));
            } else {
              /* istanbul ignore else */
              if (res.headers.hasOwnProperty('cache-control')) {
                var cacheControlHeader = res.headers['cache-control'];
                var parts = cacheControlHeader.split(',');
                parts.forEach(function(part) {
                  var subParts = part.trim().split('=');
                  if (subParts[0] === 'max-age') {
                    var maxAge = subParts[1];
                    self._publicKeysExpireAt = Date.now() + (maxAge * 1000);
                  }
                });
              }
  
              self._publicKeys = response;
              resolve(response);
            }
          } catch (e) {
            /* istanbul ignore next */
            reject(e);
          }
        });
      }).on('error', reject);
    });
  };
  
  
  module.exports = FirebaseTokenGenerator;
  
  