3 * Copyright 2019 Google LLC. All Rights Reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18 return new (P || (P = Promise))(function (resolve, reject) {
19 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
20 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
22 step((generator = generator.apply(thisArg, _arguments || [])).next());
25 Object.defineProperty(exports, "__esModule", { value: true });
26 const child_process_1 = require("child_process");
27 const fs = require("fs");
28 const gcpMetadata = require("gcp-metadata");
29 const os = require("os");
30 const path = require("path");
31 const crypto_1 = require("../crypto/crypto");
32 const isbrowser_1 = require("../isbrowser");
33 const messages = require("../messages");
34 const transporters_1 = require("../transporters");
35 const computeclient_1 = require("./computeclient");
36 const envDetect_1 = require("./envDetect");
37 const jwtclient_1 = require("./jwtclient");
38 const refreshclient_1 = require("./refreshclient");
39 exports.CLOUD_SDK_CLIENT_ID = '764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com';
43 * Caches a value indicating whether the auth layer is running on Google
47 this.checkIsGCE = undefined;
48 // To save the contents of the JSON credential file
49 this.jsonContent = null;
50 this.cachedCredential = null;
52 this._cachedProjectId = opts.projectId || null;
53 this.keyFilename = opts.keyFilename || opts.keyFile;
54 this.scopes = opts.scopes;
55 this.jsonContent = opts.credentials || null;
56 this.clientOptions = opts.clientOptions;
58 // Note: this properly is only public to satisify unit tests.
59 // https://github.com/Microsoft/TypeScript/issues/5228
61 return this.checkIsGCE;
63 getDefaultProjectId(callback) {
64 messages.warn(messages.DEFAULT_PROJECT_ID_DEPRECATED);
66 this.getProjectIdAsync().then(r => callback(null, r), callback);
69 return this.getProjectIdAsync();
72 getProjectId(callback) {
74 this.getProjectIdAsync().then(r => callback(null, r), callback);
77 return this.getProjectIdAsync();
81 if (this._cachedProjectId) {
82 return Promise.resolve(this._cachedProjectId);
84 // In implicit case, supports three environments. In order of precedence,
85 // the implicit environments are:
86 // - GCLOUD_PROJECT or GOOGLE_CLOUD_PROJECT environment variable
87 // - GOOGLE_APPLICATION_CREDENTIALS JSON file
88 // - Cloud SDK: `gcloud config config-helper --format json`
89 // - GCE project ID from metadata server)
90 if (!this._getDefaultProjectIdPromise) {
91 this._getDefaultProjectIdPromise =
92 new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
94 const projectId = this.getProductionProjectId() ||
95 (yield this.getFileProjectId()) ||
96 (yield this.getDefaultServiceProjectId()) ||
97 (yield this.getGCEProjectId());
98 this._cachedProjectId = projectId;
106 return this._getDefaultProjectIdPromise;
108 getApplicationDefault(optionsOrCallback = {}, callback) {
110 if (typeof optionsOrCallback === 'function') {
111 callback = optionsOrCallback;
114 options = optionsOrCallback;
117 this.getApplicationDefaultAsync(options).then(r => callback(null, r.credential, r.projectId), callback);
120 return this.getApplicationDefaultAsync(options);
123 getApplicationDefaultAsync(options) {
124 return __awaiter(this, void 0, void 0, function* () {
125 // If we've already got a cached credential, just return it.
126 if (this.cachedCredential) {
128 credential: this.cachedCredential,
129 projectId: yield this.getProjectIdAsync()
134 // Check for the existence of a local environment variable pointing to the
135 // location of the credential file. This is typically used in local
136 // developer scenarios.
138 yield this._tryGetApplicationCredentialsFromEnvironmentVariable(options);
140 if (credential instanceof jwtclient_1.JWT) {
141 credential.scopes = this.scopes;
143 this.cachedCredential = credential;
144 projectId = yield this.getProjectId();
145 return { credential, projectId };
147 // Look in the well-known credential file location.
149 yield this._tryGetApplicationCredentialsFromWellKnownFile(options);
151 if (credential instanceof jwtclient_1.JWT) {
152 credential.scopes = this.scopes;
154 this.cachedCredential = credential;
155 projectId = yield this.getProjectId();
156 return { credential, projectId };
158 // Determine if we're running on GCE.
161 isGCE = yield this._checkIsGCE();
164 throw new Error('Unexpected error determining execution environment: ' + e.message);
167 // We failed to find the default credentials. Bail out with an error.
168 throw new Error('Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.');
170 // For GCE, just return a default ComputeClient. It will take care of
172 this.cachedCredential = new computeclient_1.Compute(options);
173 projectId = yield this.getProjectId();
174 return { projectId, credential: this.cachedCredential };
178 * Determines whether the auth layer is running on Google Compute Engine.
179 * @returns A promise that resolves with the boolean.
183 return __awaiter(this, void 0, void 0, function* () {
184 if (this.checkIsGCE === undefined) {
185 this.checkIsGCE = yield gcpMetadata.isAvailable();
187 return this.checkIsGCE;
191 * Attempts to load default credentials from the environment variable path..
192 * @returns Promise that resolves with the OAuth2Client or null.
195 _tryGetApplicationCredentialsFromEnvironmentVariable(options) {
196 return __awaiter(this, void 0, void 0, function* () {
197 const credentialsPath = process.env['GOOGLE_APPLICATION_CREDENTIALS'] ||
198 process.env['google_application_credentials'];
199 if (!credentialsPath || credentialsPath.length === 0) {
203 return this._getApplicationCredentialsFromFilePath(credentialsPath, options);
206 throw this.createError('Unable to read the credential file specified by the GOOGLE_APPLICATION_CREDENTIALS environment variable.', e);
211 * Attempts to load default credentials from a well-known file location
212 * @return Promise that resolves with the OAuth2Client or null.
215 _tryGetApplicationCredentialsFromWellKnownFile(options) {
216 return __awaiter(this, void 0, void 0, function* () {
217 // First, figure out the location of the file, depending upon the OS type.
219 if (this._isWindows()) {
221 location = process.env['APPDATA'];
225 const home = process.env['HOME'];
227 location = this._pathJoin(home, '.config');
230 // If we found the root path, expand it.
232 location = this._pathJoin(location, 'gcloud');
234 this._pathJoin(location, 'application_default_credentials.json');
235 location = this._mockWellKnownFilePath(location);
236 // Check whether the file exists.
237 if (!this._fileExists(location)) {
241 // The file does not exist.
245 // The file seems to exist. Try to use it.
246 const client = yield this._getApplicationCredentialsFromFilePath(location, options);
247 this.warnOnProblematicCredentials(client);
252 * Attempts to load default credentials from a file at the given path..
253 * @param filePath The path to the file to read.
254 * @returns Promise that resolves with the OAuth2Client
257 _getApplicationCredentialsFromFilePath(filePath, options = {}) {
258 return __awaiter(this, void 0, void 0, function* () {
259 // Make sure the path looks like a string.
260 if (!filePath || filePath.length === 0) {
261 throw new Error('The file path is invalid.');
263 // Make sure there is a file at the path. lstatSync will throw if there is
266 // Resolve path to actual file in case of symlink. Expect a thrown error
267 // if not resolvable.
268 filePath = fs.realpathSync(filePath);
269 if (!fs.lstatSync(filePath).isFile()) {
274 throw this.createError(`The file at ${filePath} does not exist, or it is not a file.`, err);
276 // Now open a read stream on the file, and parse it.
278 const readStream = this._createReadStream(filePath);
279 return this.fromStream(readStream, options);
282 throw this.createError(`Unable to read the file at ${filePath}.`, err);
287 * Credentials from the Cloud SDK that are associated with Cloud SDK's project
288 * are problematic because they may not have APIs enabled and have limited
289 * quota. If this is the case, warn about it.
291 warnOnProblematicCredentials(client) {
292 if (client.email === exports.CLOUD_SDK_CLIENT_ID) {
293 messages.warn(messages.PROBLEMATIC_CREDENTIALS_WARNING);
297 * Create a credentials instance using the given input options.
298 * @param json The input object.
299 * @param options The JWT or UserRefresh options for the client
300 * @returns JWT or UserRefresh Client with data
302 fromJSON(json, options) {
305 throw new Error('Must pass in a JSON object containing the Google auth settings.');
307 this.jsonContent = json;
308 options = options || {};
309 if (json.type === 'authorized_user') {
310 client = new refreshclient_1.UserRefreshClient(options);
313 options.scopes = this.scopes;
314 client = new jwtclient_1.JWT(options);
316 client.fromJSON(json);
319 fromStream(inputStream, optionsOrCallback = {}, callback) {
321 if (typeof optionsOrCallback === 'function') {
322 callback = optionsOrCallback;
325 options = optionsOrCallback;
328 this.fromStreamAsync(inputStream, options)
329 .then(r => callback(null, r), callback);
332 return this.fromStreamAsync(inputStream, options);
335 fromStreamAsync(inputStream, options) {
336 return new Promise((resolve, reject) => {
338 throw new Error('Must pass in a stream containing the Google auth settings.');
341 inputStream.setEncoding('utf8')
343 .on('data', (chunk) => s += chunk)
346 const data = JSON.parse(s);
347 const r = this.fromJSON(data, options);
357 * Create a credentials instance using the given API key string.
358 * @param apiKey The API key string
359 * @param options An optional options object.
360 * @returns A JWT loaded from the key
362 fromAPIKey(apiKey, options) {
363 options = options || {};
364 const client = new jwtclient_1.JWT(options);
365 client.fromAPIKey(apiKey);
369 * Determines whether the current operating system is Windows.
373 const sys = this._osPlatform();
374 if (sys && sys.length >= 3) {
375 if (sys.substring(0, 3).toLowerCase() === 'win') {
382 * Creates a file stream. Allows mocking.
385 _createReadStream(filePath) {
386 return fs.createReadStream(filePath);
389 * Gets the current operating system platform. Allows mocking.
393 return os.platform();
396 * Determines whether a file exists. Allows mocking.
399 _fileExists(filePath) {
400 return fs.existsSync(filePath);
403 * Joins two parts of a path. Allows mocking.
406 _pathJoin(item1, item2) {
407 return path.join(item1, item2);
410 * Allows mocking of the path to a well-known file.
413 _mockWellKnownFilePath(filePath) {
416 // Creates an Error containing the given message, and includes the message
417 // from the optional err passed in.
418 createError(message, err) {
419 let s = message || '';
421 const errorMessage = String(err);
422 if (errorMessage && errorMessage.length > 0) {
432 * Run the Google Cloud SDK command that prints the default project ID
434 getDefaultServiceProjectId() {
435 return __awaiter(this, void 0, void 0, function* () {
436 return new Promise(resolve => {
437 child_process_1.exec('gcloud config config-helper --format json', (err, stdout, stderr) => {
438 if (!err && stdout) {
440 const projectId = JSON.parse(stdout).configuration.properties.core.project;
454 * Loads the project id from environment variables.
457 getProductionProjectId() {
458 return process.env['GCLOUD_PROJECT'] ||
459 process.env['GOOGLE_CLOUD_PROJECT'] || process.env['gcloud_project'] ||
460 process.env['google_cloud_project'];
463 * Loads the project id from the GOOGLE_APPLICATION_CREDENTIALS json file.
467 return __awaiter(this, void 0, void 0, function* () {
468 if (this.cachedCredential) {
469 // Try to read the project ID from the cached credentials file
470 return this.cachedCredential.projectId;
472 // Ensure the projectId is loaded from the keyFile if available.
473 if (this.keyFilename) {
474 const creds = yield this.getClient();
475 if (creds && creds.projectId) {
476 return creds.projectId;
479 // Try to load a credentials file and read its project ID
480 const r = yield this._tryGetApplicationCredentialsFromEnvironmentVariable();
490 * Gets the Compute Engine project ID if it can be inferred.
493 return __awaiter(this, void 0, void 0, function* () {
495 const r = yield gcpMetadata.project('project-id');
504 getCredentials(callback) {
506 this.getCredentialsAsync().then(r => callback(null, r), callback);
509 return this.getCredentialsAsync();
512 getCredentialsAsync() {
513 return __awaiter(this, void 0, void 0, function* () {
514 yield this.getClient();
515 if (this.jsonContent) {
517 client_email: this.jsonContent.client_email,
518 private_key: this.jsonContent.private_key
522 const isGCE = yield this._checkIsGCE();
524 throw new Error('Unknown error.');
526 // For GCE, return the service account details from the metadata server
527 // NOTE: The trailing '/' at the end of service-accounts/ is very important!
528 // The GCF metadata server doesn't respect querystring params if this / is
530 const data = yield gcpMetadata.instance({ property: 'service-accounts/', params: { recursive: 'true' } });
531 if (!data || !data.default || !data.default.email) {
532 throw new Error('Failure from metadata server.');
534 return { client_email: data.default.email };
538 * Automatically obtain a client based on the provided configuration. If no
539 * options were passed, use Application Default Credentials.
542 return __awaiter(this, void 0, void 0, function* () {
545 options.keyFilename || options.keyFile || this.keyFilename;
546 this.scopes = options.scopes || this.scopes;
547 this.jsonContent = options.credentials || this.jsonContent;
548 this.clientOptions = options.clientOptions;
550 if (!this.cachedCredential) {
551 if (this.jsonContent) {
552 this.cachedCredential =
553 yield this.fromJSON(this.jsonContent, this.clientOptions);
555 else if (this.keyFilename) {
556 const filePath = path.resolve(this.keyFilename);
557 const stream = fs.createReadStream(filePath);
558 this.cachedCredential =
559 yield this.fromStreamAsync(stream, this.clientOptions);
562 yield this.getApplicationDefaultAsync(this.clientOptions);
565 return this.cachedCredential;
569 * Automatically obtain application default credentials, and return
570 * an access token for making requests.
573 return __awaiter(this, void 0, void 0, function* () {
574 const client = yield this.getClient();
575 return (yield client.getAccessToken()).token;
579 * Obtain the HTTP headers that will provide authorization for a given
582 getRequestHeaders(url) {
583 return __awaiter(this, void 0, void 0, function* () {
584 const client = yield this.getClient();
585 return client.getRequestHeaders(url);
589 * Obtain credentials for a request, then attach the appropriate headers to
590 * the request options.
591 * @param opts Axios or Request options on which to attach the headers
593 authorizeRequest(opts) {
594 return __awaiter(this, void 0, void 0, function* () {
596 const url = opts.url || opts.uri;
597 const client = yield this.getClient();
598 const headers = yield client.getRequestHeaders(url);
599 opts.headers = Object.assign(opts.headers || {}, headers);
604 * Automatically obtain application default credentials, and make an
605 * HTTP request using the given options.
606 * @param opts Axios request options for the HTTP request.
608 // tslint:disable-next-line no-any
610 return __awaiter(this, void 0, void 0, function* () {
611 const client = yield this.getClient();
612 return client.request(opts);
616 * Determine the compute environment in which the code is running.
619 return envDetect_1.getEnv();
622 * Sign the given data with the current private key, or go out
623 * to the IAM API to sign it.
624 * @param data The data to be signed.
627 return __awaiter(this, void 0, void 0, function* () {
628 const client = yield this.getClient();
629 const crypto = crypto_1.createCrypto();
630 if (client instanceof jwtclient_1.JWT && client.key && !isbrowser_1.isBrowser()) {
631 const sign = crypto.createSign('RSA-SHA256');
633 return sign.sign(client.key, 'base64');
635 const projectId = yield this.getProjectId();
637 throw new Error('Cannot sign data without a project ID.');
639 const creds = yield this.getCredentials();
640 if (!creds.client_email) {
641 throw new Error('Cannot sign data without `client_email`.');
643 const id = `projects/${projectId}/serviceAccounts/${creds.client_email}`;
644 const res = yield this.request({
646 url: `https://iam.googleapis.com/v1/${id}:signBlob`,
647 data: { bytesToSign: crypto.encodeBase64StringUtf8(data) }
649 return res.data.signature;
654 * Export DefaultTransporter as a static property of the class.
656 GoogleAuth.DefaultTransporter = transporters_1.DefaultTransporter;
657 exports.GoogleAuth = GoogleAuth;
658 //# sourceMappingURL=googleauth.js.map