UNPKG

google-auth-library

Version:

Google APIs Authentication Client Library for Node.js

677 lines 29.3 kB
"use strict"; /** * Copyright 2014 Google Inc. All Rights Reserved. * * 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. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [0, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var child_process_1 = require("child_process"); var fs = require("fs"); var os = require("os"); var path = require("path"); var util = require("util"); var transporters_1 = require("../transporters"); var computeclient_1 = require("./computeclient"); var jwtclient_1 = require("./jwtclient"); var refreshclient_1 = require("./refreshclient"); var GoogleAuth = /** @class */ (function () { function GoogleAuth() { /** * Caches a value indicating whether the auth layer is running on Google * Compute Engine. * @private */ this.checkIsGCE = undefined; // To save the contents of the JSON credential file this.jsonContent = null; this.cachedCredential = null; } Object.defineProperty(GoogleAuth.prototype, "isGCE", { // Note: this properly is only public to satisify unit tests. // https://github.com/Microsoft/TypeScript/issues/5228 get: function () { return this.checkIsGCE; }, enumerable: true, configurable: true }); GoogleAuth.prototype.getDefaultProjectId = function (callback) { if (callback) { this.getDefaultProjectIdAsync() .then(function (r) { return callback(null, r); }) .catch(callback); } else { return this.getDefaultProjectIdAsync(); } }; GoogleAuth.prototype.getDefaultProjectIdAsync = function () { // In implicit case, supports three environments. In order of precedence, // the implicit environments are: // // * GCLOUD_PROJECT or GOOGLE_CLOUD_PROJECT environment variable // * GOOGLE_APPLICATION_CREDENTIALS JSON file // * Get default service project from // ``$ gcloud beta auth application-default login`` // * Google App Engine application ID (Not implemented yet) // * Google Compute Engine project ID (from metadata server) (Not // implemented yet) var _this = this; if (this._cachedProjectId) { return Promise.resolve(this._cachedProjectId); } if (!this._getDefaultProjectIdPromise) { this._getDefaultProjectIdPromise = new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { var projectId, _a, _b, _c, e_1; return __generator(this, function (_d) { switch (_d.label) { case 0: _d.trys.push([0, 7, , 8]); _c = this.getProductionProjectId(); if (_c) return [3 /*break*/, 2]; return [4 /*yield*/, this.getFileProjectId()]; case 1: _c = (_d.sent()); _d.label = 2; case 2: _b = _c; if (_b) return [3 /*break*/, 4]; return [4 /*yield*/, this.getDefaultServiceProjectId()]; case 3: _b = (_d.sent()); _d.label = 4; case 4: _a = _b; if (_a) return [3 /*break*/, 6]; return [4 /*yield*/, this.getGCEProjectId()]; case 5: _a = (_d.sent()); _d.label = 6; case 6: projectId = _a; this._cachedProjectId = projectId; resolve(projectId); return [3 /*break*/, 8]; case 7: e_1 = _d.sent(); reject(e_1); return [3 /*break*/, 8]; case 8: return [2 /*return*/]; } }); }); }); } return this._getDefaultProjectIdPromise; }; /** * Run the Google Cloud SDK command that prints the default project ID */ GoogleAuth.prototype._getSDKDefaultProjectId = function () { // TODO: make this a proper async function return new Promise(function (resolve, reject) { child_process_1.exec('gcloud config config-helper --format json', function (err, stdout, stderr) { if (err) { reject(err); } else { resolve({ stdout: stdout, stderr: stderr }); } }); }); }; GoogleAuth.prototype.getApplicationDefault = function (callback) { if (callback) { this.getApplicationDefaultAsync() .then(function (r) { return callback(null, r.credential, r.projectId); }) .catch(callback); } else { return this.getApplicationDefaultAsync(); } }; GoogleAuth.prototype.getApplicationDefaultAsync = function () { return __awaiter(this, void 0, void 0, function () { var _a, credential, projectId, gce, e_2; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!this.cachedCredential) return [3 /*break*/, 2]; _a = { credential: this.cachedCredential }; return [4 /*yield*/, this.getDefaultProjectIdAsync()]; case 1: return [2 /*return*/, (_a.projectId = _b.sent(), _a)]; case 2: return [4 /*yield*/, this._tryGetApplicationCredentialsFromEnvironmentVariable()]; case 3: // Check for the existence of a local environment variable pointing to the // location of the credential file. This is typically used in local // developer scenarios. credential = _b.sent(); if (!credential) return [3 /*break*/, 5]; this.cachedCredential = credential; return [4 /*yield*/, this.getDefaultProjectId()]; case 4: projectId = _b.sent(); return [2 /*return*/, { credential: credential, projectId: projectId }]; case 5: return [4 /*yield*/, this._tryGetApplicationCredentialsFromWellKnownFile()]; case 6: // Look in the well-known credential file location. credential = _b.sent(); if (!credential) return [3 /*break*/, 8]; this.cachedCredential = credential; return [4 /*yield*/, this.getDefaultProjectId()]; case 7: projectId = _b.sent(); return [2 /*return*/, { credential: credential, projectId: projectId }]; case 8: _b.trys.push([8, 10, , 11]); return [4 /*yield*/, this._checkIsGCE()]; case 9: gce = _b.sent(); if (gce) { // For GCE, just return a default ComputeClient. It will take care of // the rest. // TODO: cache the result return [2 /*return*/, { projectId: null, credential: new computeclient_1.Compute() }]; } else { // We failed to find the default credentials. Bail out with an error. throw new Error('Could not load the default credentials. Browse to https://developers.google.com/accounts/docs/application-default-credentials for more information.'); } return [3 /*break*/, 11]; case 10: e_2 = _b.sent(); throw new Error('Unexpected error while acquiring application default credentials: ' + e_2.message); case 11: return [2 /*return*/]; } }); }); }; /** * Determines whether the auth layer is running on Google Compute Engine. * @returns A promise that resolves with the boolean. * @api private */ GoogleAuth.prototype._checkIsGCE = function (isRetry) { if (isRetry === void 0) { isRetry = false; } return __awaiter(this, void 0, void 0, function () { var res, e_3, isDNSError, ae, is5xx; return __generator(this, function (_a) { switch (_a.label) { case 0: if (this.checkIsGCE !== undefined) { return [2 /*return*/, this.checkIsGCE]; } if (!this.transporter) { this.transporter = new transporters_1.DefaultTransporter(); } _a.label = 1; case 1: _a.trys.push([1, 3, , 8]); return [4 /*yield*/, this.transporter.request({ url: 'http://metadata.google.internal' })]; case 2: res = _a.sent(); this.checkIsGCE = res && res.headers && res.headers['metadata-flavor'] === 'Google'; return [3 /*break*/, 8]; case 3: e_3 = _a.sent(); isDNSError = e_3.code === 'ENOTFOUND'; ae = e_3; is5xx = ae.response && (ae.response.status >= 500 && ae.response.status < 600); if (!is5xx) return [3 /*break*/, 6]; if (!!isRetry) return [3 /*break*/, 5]; return [4 /*yield*/, this._checkIsGCE(true)]; case 4: return [2 /*return*/, _a.sent()]; case 5: throw e_3; case 6: if (!isDNSError) { throw e_3; } _a.label = 7; case 7: this.checkIsGCE = false; return [3 /*break*/, 8]; case 8: return [2 /*return*/, this.checkIsGCE]; } }); }); }; /** * Attempts to load default credentials from the environment variable path.. * @returns Promise that resolves with the OAuth2Client or null. * @api private */ GoogleAuth.prototype._tryGetApplicationCredentialsFromEnvironmentVariable = function () { return __awaiter(this, void 0, void 0, function () { var credentialsPath; return __generator(this, function (_a) { credentialsPath = this._getEnv('GOOGLE_APPLICATION_CREDENTIALS'); if (!credentialsPath || credentialsPath.length === 0) { return [2 /*return*/, null]; } try { return [2 /*return*/, this._getApplicationCredentialsFromFilePath(credentialsPath)]; } catch (e) { throw this.createError('Unable to read the credential file specified by the GOOGLE_APPLICATION_CREDENTIALS environment variable.', e); } return [2 /*return*/]; }); }); }; /** * Attempts to load default credentials from a well-known file location * @return Promise that resolves with the OAuth2Client or null. * @api private */ GoogleAuth.prototype._tryGetApplicationCredentialsFromWellKnownFile = function () { return __awaiter(this, void 0, void 0, function () { var location, home; return __generator(this, function (_a) { location = null; if (this._isWindows()) { // Windows location = this._getEnv('APPDATA'); } else { home = this._getEnv('HOME'); if (home) { location = this._pathJoin(home, '.config'); } } // If we found the root path, expand it. if (location) { location = this._pathJoin(location, 'gcloud'); location = this._pathJoin(location, 'application_default_credentials.json'); location = this._mockWellKnownFilePath(location); // Check whether the file exists. if (!this._fileExists(location)) { location = null; } } // The file does not exist. if (!location) { return [2 /*return*/, null]; } // The file seems to exist. Try to use it. return [2 /*return*/, this._getApplicationCredentialsFromFilePath(location)]; }); }); }; /** * Attempts to load default credentials from a file at the given path.. * @param {string=} filePath The path to the file to read. * @returns Promise that resolves with the OAuth2Client * @api private */ GoogleAuth.prototype._getApplicationCredentialsFromFilePath = function (filePath) { return __awaiter(this, void 0, void 0, function () { var readStream; return __generator(this, function (_a) { // Make sure the path looks like a string. if (!filePath || filePath.length === 0) { throw new Error('The file path is invalid.'); } // Make sure there is a file at the path. lstatSync will throw if there is // nothing there. try { // Resolve path to actual file in case of symlink. Expect a thrown error // if not resolvable. filePath = fs.realpathSync(filePath); if (!fs.lstatSync(filePath).isFile()) { throw new Error(); } } catch (err) { throw this.createError(util.format('The file at %s does not exist, or it is not a file.', filePath), err); } // Now open a read stream on the file, and parse it. try { readStream = this._createReadStream(filePath); return [2 /*return*/, this.fromStream(readStream)]; } catch (err) { throw this.createError(util.format('Unable to read the file at %s.', filePath), err); } return [2 /*return*/]; }); }); }; /** * Create a credentials instance using the given input options. * @param {object=} json The input object. * @returns JWT or UserRefresh Client with data */ GoogleAuth.prototype.fromJSON = function (json) { var client; if (!json) { throw new Error('Must pass in a JSON object containing the Google auth settings.'); } this.jsonContent = json; if (json.type === 'authorized_user') { client = new refreshclient_1.UserRefreshClient(); } else { client = new jwtclient_1.JWT(); } client.fromJSON(json); return client; }; GoogleAuth.prototype.fromStream = function (inputStream, callback) { if (callback) { this.fromStreamAsync(inputStream) .then(function (r) { return callback(null, r); }) .catch(callback); } else { return this.fromStreamAsync(inputStream); } }; GoogleAuth.prototype.fromStreamAsync = function (inputStream) { var _this = this; return new Promise(function (resolve, reject) { if (!inputStream) { throw new Error('Must pass in a stream containing the Google auth settings.'); } var s = ''; inputStream.setEncoding('utf8'); inputStream.on('data', function (chunk) { s += chunk; }); inputStream.on('end', function () { try { var data = JSON.parse(s); var r = _this.fromJSON(data); return resolve(r); } catch (err) { return reject(err); } }); }); }; /** * Create a credentials instance using the given API key string. * @param {string} - The API key string * @returns A JWT loaded from the key */ GoogleAuth.prototype.fromAPIKey = function (apiKey) { var client = new jwtclient_1.JWT(); client.fromAPIKey(apiKey); return client; }; /** * Determines whether the current operating system is Windows. * @api private */ GoogleAuth.prototype._isWindows = function () { var sys = this._osPlatform(); if (sys && sys.length >= 3) { if (sys.substring(0, 3).toLowerCase() === 'win') { return true; } } return false; }; /** * Creates a file stream. Allows mocking. * @api private */ GoogleAuth.prototype._createReadStream = function (filePath) { return fs.createReadStream(filePath); }; /** * Gets the value of the environment variable with the given name. Allows * mocking. * @api private */ GoogleAuth.prototype._getEnv = function (name) { return process.env[name]; }; /** * Gets the current operating system platform. Allows mocking. * @api private */ GoogleAuth.prototype._osPlatform = function () { return os.platform(); }; /** * Determines whether a file exists. Allows mocking. * @api private */ GoogleAuth.prototype._fileExists = function (filePath) { return fs.existsSync(filePath); }; /** * Joins two parts of a path. Allows mocking. * @api private */ GoogleAuth.prototype._pathJoin = function (item1, item2) { return path.join(item1, item2); }; /** * Allows mocking of the path to a well-known file. * @api private */ GoogleAuth.prototype._mockWellKnownFilePath = function (filePath) { return filePath; }; // Creates an Error containing the given message, and includes the message // from the optional err passed in. GoogleAuth.prototype.createError = function (message, err) { var s = message || ''; if (err) { var errorMessage = String(err); if (errorMessage && errorMessage.length > 0) { if (s.length > 0) { s += ' '; } s += errorMessage; } } return Error(s); }; /** * Loads the default project of the Google Cloud SDK. * @api private */ GoogleAuth.prototype.getDefaultServiceProjectId = function () { return __awaiter(this, void 0, void 0, function () { var r, e_4; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, this._getSDKDefaultProjectId()]; case 1: r = _a.sent(); if (r.stdout) { return [2 /*return*/, JSON.parse(r.stdout).configuration.properties.core.project]; } return [3 /*break*/, 3]; case 2: e_4 = _a.sent(); return [3 /*break*/, 3]; case 3: return [2 /*return*/, null]; } }); }); }; /** * Loads the project id from environment variables. * @api private */ GoogleAuth.prototype.getProductionProjectId = function () { return this._getEnv('GCLOUD_PROJECT') || this._getEnv('GOOGLE_CLOUD_PROJECT'); }; /** * Loads the project id from the GOOGLE_APPLICATION_CREDENTIALS json file. * @api private */ GoogleAuth.prototype.getFileProjectId = function () { return __awaiter(this, void 0, void 0, function () { var r; return __generator(this, function (_a) { switch (_a.label) { case 0: if (this.cachedCredential) { // Try to read the project ID from the cached credentials file return [2 /*return*/, this.cachedCredential.projectId]; } return [4 /*yield*/, this._tryGetApplicationCredentialsFromEnvironmentVariable()]; case 1: r = _a.sent(); if (r) { return [2 /*return*/, r.projectId]; } else { return [2 /*return*/, null]; } return [2 /*return*/]; } }); }); }; /** * Gets the Compute Engine project ID if it can be inferred. * Uses 169.254.169.254 for the metadata server to avoid request * latency from DNS lookup. * See https://cloud.google.com/compute/docs/metadata#metadataserver * for information about this IP address. (This IP is also used for * Amazon EC2 instances, so the metadata flavor is crucial.) * See https://github.com/google/oauth2client/issues/93 for context about * DNS latency. * * @api private */ GoogleAuth.prototype.getGCEProjectId = function () { return __awaiter(this, void 0, void 0, function () { var r, e_5; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this.transporter) { this.transporter = new transporters_1.DefaultTransporter(); } _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.transporter.request({ url: 'http://169.254.169.254/computeMetadata/v1/project/project-id', headers: { 'Metadata-Flavor': 'Google' } })]; case 2: r = _a.sent(); return [2 /*return*/, r.data]; case 3: e_5 = _a.sent(); // Ignore any errors return [2 /*return*/, null]; case 4: return [2 /*return*/]; } }); }); }; GoogleAuth.prototype.getCredentials = function (callback) { if (callback) { this.getCredentialsAsync().then(function (r) { return callback(null, r); }).catch(callback); } else { return this.getCredentialsAsync(); } }; GoogleAuth.prototype.getCredentialsAsync = function () { return __awaiter(this, void 0, void 0, function () { var credential_1, isGCE, result, credential; return __generator(this, function (_a) { switch (_a.label) { case 0: if (this.jsonContent) { credential_1 = { client_email: this.jsonContent.client_email, private_key: this.jsonContent.private_key }; return [2 /*return*/, credential_1]; } return [4 /*yield*/, this._checkIsGCE()]; case 1: isGCE = _a.sent(); if (!isGCE) { throw new Error('Unknown error.'); } return [4 /*yield*/, this.transporter.request({ url: 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/?recursive=true', headers: { 'Metadata-Flavor': 'Google' } })]; case 2: result = _a.sent(); if (!result.data || !result.data.default || !result.data.default.email) { throw new Error('Failure from metadata server.'); } credential = { client_email: result.data.default.email }; return [2 /*return*/, credential]; } }); }); }; /** * Export DefaultTransporter as a static property of the class. */ GoogleAuth.DefaultTransporter = transporters_1.DefaultTransporter; return GoogleAuth; }()); exports.GoogleAuth = GoogleAuth; //# sourceMappingURL=googleauth.js.map