1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 | /*! firebase-admin v4.2.1 https://firebase.google.com/terms/ */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var error_1 = require("../utils/error"); var validator = require("../utils/validator"); var jwt = require("jsonwebtoken"); // Use untyped import syntax for Node built-ins var https = require("https"); 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). */ var FirebaseTokenGenerator = (function () { function FirebaseTokenGenerator(certificate) { if (!certificate) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, 'INTERNAL ASSERT: Must provide a certificate to use FirebaseTokenGenerator.'); } this.certificate_ = certificate; } /** * 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<string>} 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 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); } if (!validator.isNonEmptyString(this.certificate_.privateKey)) { errorMessage = 'createCustomToken() requires a certificate with "private_key" set.'; } else if (!validator.isNonEmptyString(this.certificate_.clientEmail)) { errorMessage = 'createCustomToken() requires a certificate with "client_email" set.'; } if (typeof errorMessage !== 'undefined') { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, errorMessage); } var jwtPayload = {}; if (typeof developerClaims !== 'undefined') { var claims = {}; 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]; } } jwtPayload.claims = claims; } jwtPayload.uid = uid; var customToken = jwt.sign(jwtPayload, this.certificate_.privateKey, { audience: FIREBASE_AUDIENCE, expiresIn: ONE_HOUR_IN_SECONDS, issuer: this.certificate_.clientEmail, subject: this.certificate_.clientEmail, algorithm: ALGORITHM, }); return Promise.resolve(customToken); }; /** * 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_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, 'First argument to verifyIdToken() must be a Firebase ID token string.'); } if (!validator.isNonEmptyString(this.certificate_.projectId)) { throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, 'verifyIdToken() requires a certificate 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/admin/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.certificate_.projectId) { errorMessage = 'Firebase ID token has incorrect "aud" (audience) claim. Expected "' + this.certificate_.projectId + '" but got "' + payload.aud + '".' + projectIdMatchMessage + verifyIdTokenDocsMessage; } else if (payload.iss !== 'https://securetoken.google.com/' + this.certificate_.projectId) { errorMessage = 'Firebase ID token has incorrect "iss" (issuer) claim. Expected ' + '"https://securetoken.google.com/' + this.certificate_.projectId + '" 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 Promise.reject(new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage)); } return this.fetchPublicKeys_().then(function (publicKeys) { if (!publicKeys.hasOwnProperty(header.kid)) { return Promise.reject(new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, '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 Promise(function (resolve, reject) { jwt.verify(idToken, publicKeys[header.kid], { algorithms: [ALGORITHM], }, function (error, decodedToken) { if (error) { if (error.name === 'TokenExpiredError') { errorMessage = 'Firebase ID token has expired. Get a fresh token from your client app and try ' + 'again.' + verifyIdTokenDocsMessage; } else if (error.name === 'JsonWebTokenError') { errorMessage = 'Firebase ID token has invalid signature.' + verifyIdTokenDocsMessage; } return reject(new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_ARGUMENT, errorMessage)); } else { decodedToken.uid = decodedToken.sub; resolve(decodedToken); } }); }); }); }; /** * 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; } if (typeof developerClaims === 'object' && developerClaims !== null && !(developerClaims instanceof Array)) { return true; } return false; }; /** * 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 () { var _this = this; var publicKeysExist = (typeof this.publicKeys_ !== 'undefined'); var publicKeysExpiredExists = (typeof this.publicKeysExpireAt_ !== 'undefined'); var publicKeysStillValid = (publicKeysExpiredExists && Date.now() < this.publicKeysExpireAt_); if (publicKeysExist && publicKeysStillValid) { return Promise.resolve(this.publicKeys_); } return new Promise(function (resolve, reject) { https.get(CLIENT_CERT_URL, function (res) { var buffers = []; res.on('data', function (buffer) { return buffers.push(buffer); }); res.on('end', function () { try { var response = JSON.parse(Buffer.concat(buffers).toString()); 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_1.FirebaseAuthError(error_1.AuthClientErrorCode.INTERNAL_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]; _this.publicKeysExpireAt_ = Date.now() + (maxAge * 1000); } }); } _this.publicKeys_ = response; resolve(response); } } catch (e) { /* istanbul ignore next */ reject(e); } }); }).on('error', reject); }); }; return FirebaseTokenGenerator; }()); exports.FirebaseTokenGenerator = FirebaseTokenGenerator; |