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 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 jwt = require("jsonwebtoken"); // Use untyped import syntax for Node built-ins var fs = require("fs"); var os = require("os"); var http = require("http"); var path = require("path"); var https = require("https"); var error_1 = require("../utils/error"); var GOOGLE_TOKEN_AUDIENCE = 'https://accounts.google.com/o/oauth2/token'; var GOOGLE_AUTH_TOKEN_HOST = 'accounts.google.com'; var GOOGLE_AUTH_TOKEN_PATH = '/o/oauth2/token'; var GOOGLE_AUTH_TOKEN_PORT = 443; // NOTE: the Google Metadata Service uses HTTP over a vlan var GOOGLE_METADATA_SERVICE_HOST = 'metadata.google.internal'; var GOOGLE_METADATA_SERVICE_PATH = '/computeMetadata/v1beta1/instance/service-accounts/default/token'; var configDir = (function () { // Windows has a dedicated low-rights location for apps at ~/Application Data var sys = os.platform(); Iif (sys && sys.length >= 3 && sys.substring(0, 3).toLowerCase() === 'win') { return process.env.APPDATA; } // On *nix the gcloud cli creates a . dir. return process.env.HOME && path.resolve(process.env.HOME, '.config'); })(); var GCLOUD_CREDENTIAL_SUFFIX = 'gcloud/application_default_credentials.json'; var GCLOUD_CREDENTIAL_PATH = configDir && path.resolve(configDir, GCLOUD_CREDENTIAL_SUFFIX); var REFRESH_TOKEN_HOST = 'www.googleapis.com'; var REFRESH_TOKEN_PORT = 443; var REFRESH_TOKEN_PATH = '/oauth2/v4/token'; var ONE_HOUR_IN_SECONDS = 60 * 60; var JWT_ALGORITHM = 'RS256'; function copyAttr(to, from, key, alt) { var tmp = from[key] || from[alt]; if (typeof tmp !== 'undefined') { to[key] = tmp; } } var RefreshToken = (function () { function RefreshToken(json) { copyAttr(this, json, 'clientId', 'client_id'); copyAttr(this, json, 'clientSecret', 'client_secret'); copyAttr(this, json, 'refreshToken', 'refresh_token'); copyAttr(this, json, 'type', 'type'); var errorMessage; if (typeof this.clientId !== 'string' || !this.clientId) { errorMessage = 'Refresh token must contain a "client_id" property.'; } else if (typeof this.clientSecret !== 'string' || !this.clientSecret) { errorMessage = 'Refresh token must contain a "client_secret" property.'; } else if (typeof this.refreshToken !== 'string' || !this.refreshToken) { errorMessage = 'Refresh token must contain a "refresh_token" property.'; } else if (typeof this.type !== 'string' || !this.type) { errorMessage = 'Refresh token must contain a "type" property.'; } if (typeof errorMessage !== 'undefined') { throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, errorMessage); } } /* * Tries to load a RefreshToken from a path. If the path is not present, returns null. * Throws if data at the path is invalid. */ RefreshToken.fromPath = function (path) { var jsonString; try { jsonString = fs.readFileSync(path, 'utf8'); } catch (ignored) { // Ignore errors if the file is not present, as this is sometimes an expected condition return null; } try { return new RefreshToken(JSON.parse(jsonString)); } catch (error) { // Throw a nicely formed error message if the file contents cannot be parsed throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Failed to parse refresh token file: ' + error); } }; return RefreshToken; }()); exports.RefreshToken = RefreshToken; /** * A struct containing the properties necessary to use service-account JSON credentials. */ var Certificate = (function () { function Certificate(json) { if (typeof json !== 'object' || json === null) { throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Certificate object must be an object.'); } copyAttr(this, json, 'projectId', 'project_id'); copyAttr(this, json, 'privateKey', 'private_key'); copyAttr(this, json, 'clientEmail', 'client_email'); var errorMessage; if (typeof this.privateKey !== 'string' || !this.privateKey) { errorMessage = 'Certificate object must contain a string "private_key" property.'; } else if (typeof this.clientEmail !== 'string' || !this.clientEmail) { errorMessage = 'Certificate object must contain a string "client_email" property.'; } if (typeof errorMessage !== 'undefined') { throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, errorMessage); } } Certificate.fromPath = function (path) { // Node bug encountered in v6.x. fs.readFileSync hangs when path is a 0 or 1. if (typeof path !== 'string') { throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Failed to parse certificate key file: TypeError: path must be a string'); } try { return new Certificate(JSON.parse(fs.readFileSync(path, 'utf8'))); } catch (error) { // Throw a nicely formed error message if the file contents cannot be parsed throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, 'Failed to parse certificate key file: ' + error); } }; return Certificate; }()); exports.Certificate = Certificate; /** * A wrapper around the http and https request libraries to simplify & promisify JSON requests. * TODO(inlined): Create a type for "transit". */ function requestAccessToken(transit, options, data) { return new Promise(function (resolve, reject) { var req = transit.request(options, function (res) { var buffers = []; res.on('data', function (buffer) { return buffers.push(buffer); }); res.on('end', function () { try { var json = JSON.parse(Buffer.concat(buffers).toString()); if (json.error) { var errorMessage = 'Error fetching access token: ' + json.error; if (json.error_description) { errorMessage += ' (' + json.error_description + ')'; } reject(new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, errorMessage)); } else if (!json.access_token || !json.expires_in) { reject(new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, "Unexpected response while fetching access token: " + JSON.stringify(json))); } else { resolve(json); } } catch (err) { reject(new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_CREDENTIAL, "Failed to parse access token response: " + err.toString())); } }); }); req.on('error', reject); if (data) { req.write(data); } req.end(); }); } /** * Implementation of Credential that uses a service account certificate. */ var CertCredential = (function () { function CertCredential(serviceAccountPathOrObject) { if (typeof serviceAccountPathOrObject === 'string') { this.certificate_ = Certificate.fromPath(serviceAccountPathOrObject); } else { this.certificate_ = new Certificate(serviceAccountPathOrObject); } } CertCredential.prototype.getAccessToken = function () { var token = this.createAuthJwt_(); var postData = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3A' + 'grant-type%3Ajwt-bearer&assertion=' + token; var options = { method: 'POST', host: GOOGLE_AUTH_TOKEN_HOST, port: GOOGLE_AUTH_TOKEN_PORT, path: GOOGLE_AUTH_TOKEN_PATH, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': postData.length, }, }; return requestAccessToken(https, options, postData); }; CertCredential.prototype.getCertificate = function () { return this.certificate_; }; CertCredential.prototype.createAuthJwt_ = function () { var claims = { scope: [ 'https://www.googleapis.com/auth/firebase.database', 'https://www.googleapis.com/auth/firebase.messaging', 'https://www.googleapis.com/auth/identitytoolkit', 'https://www.googleapis.com/auth/userinfo.email', ].join(' '), }; // This method is actually synchronous so we can capture and return the buffer. return jwt.sign(claims, this.certificate_.privateKey, { audience: GOOGLE_TOKEN_AUDIENCE, expiresIn: ONE_HOUR_IN_SECONDS, issuer: this.certificate_.clientEmail, algorithm: JWT_ALGORITHM, }); }; return CertCredential; }()); exports.CertCredential = CertCredential; /** * Implementation of Credential that gets access tokens from refresh tokens. */ var RefreshTokenCredential = (function () { function RefreshTokenCredential(refreshTokenPathOrObject) { if (typeof refreshTokenPathOrObject === 'string') { this.refreshToken_ = RefreshToken.fromPath(refreshTokenPathOrObject); } else { this.refreshToken_ = new RefreshToken(refreshTokenPathOrObject); } } RefreshTokenCredential.prototype.getAccessToken = function () { var postData = 'client_id=' + this.refreshToken_.clientId + '&' + 'client_secret=' + this.refreshToken_.clientSecret + '&' + 'refresh_token=' + this.refreshToken_.refreshToken + '&' + 'grant_type=refresh_token'; var options = { method: 'POST', host: REFRESH_TOKEN_HOST, port: REFRESH_TOKEN_PORT, path: REFRESH_TOKEN_PATH, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': postData.length, }, }; return requestAccessToken(https, options, postData); }; ; RefreshTokenCredential.prototype.getCertificate = function () { return null; }; return RefreshTokenCredential; }()); exports.RefreshTokenCredential = RefreshTokenCredential; /** * Implementation of Credential that gets access tokens from the metadata service available * in the Google Cloud Platform. This authenticates the process as the default service account * of an App Engine instance or Google Compute Engine machine. */ var MetadataServiceCredential = (function () { function MetadataServiceCredential() { } MetadataServiceCredential.prototype.getAccessToken = function () { var options = { method: 'GET', host: GOOGLE_METADATA_SERVICE_HOST, path: GOOGLE_METADATA_SERVICE_PATH, headers: { 'Content-Length': 0, }, }; return requestAccessToken(http, options); }; MetadataServiceCredential.prototype.getCertificate = function () { return null; }; return MetadataServiceCredential; }()); exports.MetadataServiceCredential = MetadataServiceCredential; /** * ApplicationDefaultCredential implements the process for loading credentials as * described in https://developers.google.com/identity/protocols/application-default-credentials */ var ApplicationDefaultCredential = (function () { function ApplicationDefaultCredential() { if (process.env.GOOGLE_APPLICATION_CREDENTIALS) { var serviceAccount = Certificate.fromPath(process.env.GOOGLE_APPLICATION_CREDENTIALS); this.credential_ = new CertCredential(serviceAccount); return; } // It is OK to not have this file. If it is present, it must be valid. var refreshToken = RefreshToken.fromPath(GCLOUD_CREDENTIAL_PATH); if (refreshToken) { this.credential_ = new RefreshTokenCredential(refreshToken); return; } this.credential_ = new MetadataServiceCredential(); } ApplicationDefaultCredential.prototype.getAccessToken = function () { return this.credential_.getAccessToken(); }; ApplicationDefaultCredential.prototype.getCertificate = function () { return this.credential_.getCertificate(); }; // Used in testing to verify we are delegating to the correct implementation. ApplicationDefaultCredential.prototype.getCredential = function () { return this.credential_; }; return ApplicationDefaultCredential; }()); exports.ApplicationDefaultCredential = ApplicationDefaultCredential; |