1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | Object.defineProperty(exports, "__esModule", { value: true });
|
16 | exports.GoogleAuth = exports.CLOUD_SDK_CLIENT_ID = void 0;
|
17 | const child_process_1 = require("child_process");
|
18 | const fs = require("fs");
|
19 | const gcpMetadata = require("gcp-metadata");
|
20 | const os = require("os");
|
21 | const path = require("path");
|
22 | const crypto_1 = require("../crypto/crypto");
|
23 | const transporters_1 = require("../transporters");
|
24 | const computeclient_1 = require("./computeclient");
|
25 | const idtokenclient_1 = require("./idtokenclient");
|
26 | const envDetect_1 = require("./envDetect");
|
27 | const jwtclient_1 = require("./jwtclient");
|
28 | const refreshclient_1 = require("./refreshclient");
|
29 | const externalclient_1 = require("./externalclient");
|
30 | const baseexternalclient_1 = require("./baseexternalclient");
|
31 | exports.CLOUD_SDK_CLIENT_ID = '764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com';
|
32 | class GoogleAuth {
|
33 | constructor(opts) {
|
34 | |
35 |
|
36 |
|
37 |
|
38 |
|
39 | this.checkIsGCE = undefined;
|
40 |
|
41 | this.jsonContent = null;
|
42 | this.cachedCredential = null;
|
43 | opts = opts || {};
|
44 | this._cachedProjectId = opts.projectId || null;
|
45 | this.keyFilename = opts.keyFilename || opts.keyFile;
|
46 | this.scopes = opts.scopes;
|
47 | this.jsonContent = opts.credentials || null;
|
48 | this.clientOptions = opts.clientOptions;
|
49 | }
|
50 |
|
51 |
|
52 | get isGCE() {
|
53 | return this.checkIsGCE;
|
54 | }
|
55 |
|
56 |
|
57 |
|
58 | setGapicJWTValues(client) {
|
59 | client.defaultServicePath = this.defaultServicePath;
|
60 | client.useJWTAccessWithScope = this.useJWTAccessWithScope;
|
61 | client.defaultScopes = this.defaultScopes;
|
62 | }
|
63 | getProjectId(callback) {
|
64 | if (callback) {
|
65 | this.getProjectIdAsync().then(r => callback(null, r), callback);
|
66 | }
|
67 | else {
|
68 | return this.getProjectIdAsync();
|
69 | }
|
70 | }
|
71 | getProjectIdAsync() {
|
72 | if (this._cachedProjectId) {
|
73 | return Promise.resolve(this._cachedProjectId);
|
74 | }
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 | if (!this._getDefaultProjectIdPromise) {
|
82 |
|
83 |
|
84 | this._getDefaultProjectIdPromise = new Promise(
|
85 |
|
86 | async (resolve, reject) => {
|
87 | try {
|
88 | const projectId = this.getProductionProjectId() ||
|
89 | (await this.getFileProjectId()) ||
|
90 | (await this.getDefaultServiceProjectId()) ||
|
91 | (await this.getGCEProjectId()) ||
|
92 | (await this.getExternalAccountClientProjectId());
|
93 | this._cachedProjectId = projectId;
|
94 | if (!projectId) {
|
95 | throw new Error('Unable to detect a Project Id in the current environment. \n' +
|
96 | 'To learn more about authentication and Google APIs, visit: \n' +
|
97 | 'https://cloud.google.com/docs/authentication/getting-started');
|
98 | }
|
99 | resolve(projectId);
|
100 | }
|
101 | catch (e) {
|
102 | reject(e);
|
103 | }
|
104 | });
|
105 | }
|
106 | return this._getDefaultProjectIdPromise;
|
107 | }
|
108 | |
109 |
|
110 |
|
111 |
|
112 | getAnyScopes() {
|
113 | return this.scopes || this.defaultScopes;
|
114 | }
|
115 | getApplicationDefault(optionsOrCallback = {}, callback) {
|
116 | let options;
|
117 | if (typeof optionsOrCallback === 'function') {
|
118 | callback = optionsOrCallback;
|
119 | }
|
120 | else {
|
121 | options = optionsOrCallback;
|
122 | }
|
123 | if (callback) {
|
124 | this.getApplicationDefaultAsync(options).then(r => callback(null, r.credential, r.projectId), callback);
|
125 | }
|
126 | else {
|
127 | return this.getApplicationDefaultAsync(options);
|
128 | }
|
129 | }
|
130 | async getApplicationDefaultAsync(options = {}) {
|
131 |
|
132 | if (this.cachedCredential) {
|
133 | return {
|
134 | credential: this.cachedCredential,
|
135 | projectId: await this.getProjectIdAsync(),
|
136 | };
|
137 | }
|
138 | let credential;
|
139 | let projectId;
|
140 |
|
141 |
|
142 |
|
143 | credential =
|
144 | await this._tryGetApplicationCredentialsFromEnvironmentVariable(options);
|
145 | if (credential) {
|
146 | if (credential instanceof jwtclient_1.JWT) {
|
147 | credential.scopes = this.scopes;
|
148 | }
|
149 | else if (credential instanceof baseexternalclient_1.BaseExternalAccountClient) {
|
150 | credential.scopes = this.getAnyScopes();
|
151 | }
|
152 | this.cachedCredential = credential;
|
153 | projectId = await this.getProjectId();
|
154 | return { credential, projectId };
|
155 | }
|
156 |
|
157 | credential = await this._tryGetApplicationCredentialsFromWellKnownFile(options);
|
158 | if (credential) {
|
159 | if (credential instanceof jwtclient_1.JWT) {
|
160 | credential.scopes = this.scopes;
|
161 | }
|
162 | else if (credential instanceof baseexternalclient_1.BaseExternalAccountClient) {
|
163 | credential.scopes = this.getAnyScopes();
|
164 | }
|
165 | this.cachedCredential = credential;
|
166 | projectId = await this.getProjectId();
|
167 | return { credential, projectId };
|
168 | }
|
169 |
|
170 | let isGCE;
|
171 | try {
|
172 | isGCE = await this._checkIsGCE();
|
173 | }
|
174 | catch (e) {
|
175 | e.message = `Unexpected error determining execution environment: ${e.message}`;
|
176 | throw e;
|
177 | }
|
178 | if (!isGCE) {
|
179 |
|
180 | throw new Error('Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.');
|
181 | }
|
182 |
|
183 |
|
184 | options.scopes = this.getAnyScopes();
|
185 | this.cachedCredential = new computeclient_1.Compute(options);
|
186 | projectId = await this.getProjectId();
|
187 | return { projectId, credential: this.cachedCredential };
|
188 | }
|
189 | |
190 |
|
191 |
|
192 |
|
193 |
|
194 | async _checkIsGCE() {
|
195 | if (this.checkIsGCE === undefined) {
|
196 | this.checkIsGCE = await gcpMetadata.isAvailable();
|
197 | }
|
198 | return this.checkIsGCE;
|
199 | }
|
200 | |
201 |
|
202 |
|
203 |
|
204 |
|
205 | async _tryGetApplicationCredentialsFromEnvironmentVariable(options) {
|
206 | const credentialsPath = process.env['GOOGLE_APPLICATION_CREDENTIALS'] ||
|
207 | process.env['google_application_credentials'];
|
208 | if (!credentialsPath || credentialsPath.length === 0) {
|
209 | return null;
|
210 | }
|
211 | try {
|
212 | return this._getApplicationCredentialsFromFilePath(credentialsPath, options);
|
213 | }
|
214 | catch (e) {
|
215 | e.message = `Unable to read the credential file specified by the GOOGLE_APPLICATION_CREDENTIALS environment variable: ${e.message}`;
|
216 | throw e;
|
217 | }
|
218 | }
|
219 | |
220 |
|
221 |
|
222 |
|
223 |
|
224 | async _tryGetApplicationCredentialsFromWellKnownFile(options) {
|
225 |
|
226 | let location = null;
|
227 | if (this._isWindows()) {
|
228 |
|
229 | location = process.env['APPDATA'];
|
230 | }
|
231 | else {
|
232 |
|
233 | const home = process.env['HOME'];
|
234 | if (home) {
|
235 | location = path.join(home, '.config');
|
236 | }
|
237 | }
|
238 |
|
239 | if (location) {
|
240 | location = path.join(location, 'gcloud', 'application_default_credentials.json');
|
241 | if (!fs.existsSync(location)) {
|
242 | location = null;
|
243 | }
|
244 | }
|
245 |
|
246 | if (!location) {
|
247 | return null;
|
248 | }
|
249 |
|
250 | const client = await this._getApplicationCredentialsFromFilePath(location, options);
|
251 | return client;
|
252 | }
|
253 | |
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 | async _getApplicationCredentialsFromFilePath(filePath, options = {}) {
|
260 |
|
261 | if (!filePath || filePath.length === 0) {
|
262 | throw new Error('The file path is invalid.');
|
263 | }
|
264 |
|
265 |
|
266 | try {
|
267 |
|
268 |
|
269 | filePath = fs.realpathSync(filePath);
|
270 | if (!fs.lstatSync(filePath).isFile()) {
|
271 | throw new Error();
|
272 | }
|
273 | }
|
274 | catch (err) {
|
275 | err.message = `The file at ${filePath} does not exist, or it is not a file. ${err.message}`;
|
276 | throw err;
|
277 | }
|
278 |
|
279 | const readStream = fs.createReadStream(filePath);
|
280 | return this.fromStream(readStream, options);
|
281 | }
|
282 | |
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 | fromJSON(json, options) {
|
289 | let client;
|
290 | if (!json) {
|
291 | throw new Error('Must pass in a JSON object containing the Google auth settings.');
|
292 | }
|
293 | options = options || {};
|
294 | if (json.type === 'authorized_user') {
|
295 | client = new refreshclient_1.UserRefreshClient(options);
|
296 | client.fromJSON(json);
|
297 | }
|
298 | else if (json.type === baseexternalclient_1.EXTERNAL_ACCOUNT_TYPE) {
|
299 | client = externalclient_1.ExternalAccountClient.fromJSON(json, options);
|
300 | client.scopes = this.getAnyScopes();
|
301 | }
|
302 | else {
|
303 | options.scopes = this.scopes;
|
304 | client = new jwtclient_1.JWT(options);
|
305 | this.setGapicJWTValues(client);
|
306 | client.fromJSON(json);
|
307 | }
|
308 | return client;
|
309 | }
|
310 | |
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 | _cacheClientFromJSON(json, options) {
|
318 | let client;
|
319 |
|
320 | options = options || {};
|
321 | if (json.type === 'authorized_user') {
|
322 | client = new refreshclient_1.UserRefreshClient(options);
|
323 | client.fromJSON(json);
|
324 | }
|
325 | else if (json.type === baseexternalclient_1.EXTERNAL_ACCOUNT_TYPE) {
|
326 | client = externalclient_1.ExternalAccountClient.fromJSON(json, options);
|
327 | client.scopes = this.getAnyScopes();
|
328 | }
|
329 | else {
|
330 | options.scopes = this.scopes;
|
331 | client = new jwtclient_1.JWT(options);
|
332 | this.setGapicJWTValues(client);
|
333 | client.fromJSON(json);
|
334 | }
|
335 |
|
336 | this.jsonContent = json;
|
337 | this.cachedCredential = client;
|
338 | return this.cachedCredential;
|
339 | }
|
340 | fromStream(inputStream, optionsOrCallback = {}, callback) {
|
341 | let options = {};
|
342 | if (typeof optionsOrCallback === 'function') {
|
343 | callback = optionsOrCallback;
|
344 | }
|
345 | else {
|
346 | options = optionsOrCallback;
|
347 | }
|
348 | if (callback) {
|
349 | this.fromStreamAsync(inputStream, options).then(r => callback(null, r), callback);
|
350 | }
|
351 | else {
|
352 | return this.fromStreamAsync(inputStream, options);
|
353 | }
|
354 | }
|
355 | fromStreamAsync(inputStream, options) {
|
356 | return new Promise((resolve, reject) => {
|
357 | if (!inputStream) {
|
358 | throw new Error('Must pass in a stream containing the Google auth settings.');
|
359 | }
|
360 | let s = '';
|
361 | inputStream
|
362 | .setEncoding('utf8')
|
363 | .on('error', reject)
|
364 | .on('data', chunk => (s += chunk))
|
365 | .on('end', () => {
|
366 | try {
|
367 | try {
|
368 | const data = JSON.parse(s);
|
369 | const r = this._cacheClientFromJSON(data, options);
|
370 | return resolve(r);
|
371 | }
|
372 | catch (err) {
|
373 |
|
374 |
|
375 | if (!this.keyFilename)
|
376 | throw err;
|
377 | const client = new jwtclient_1.JWT({
|
378 | ...this.clientOptions,
|
379 | keyFile: this.keyFilename,
|
380 | });
|
381 | this.cachedCredential = client;
|
382 | this.setGapicJWTValues(client);
|
383 | return resolve(client);
|
384 | }
|
385 | }
|
386 | catch (err) {
|
387 | return reject(err);
|
388 | }
|
389 | });
|
390 | });
|
391 | }
|
392 | |
393 |
|
394 |
|
395 |
|
396 |
|
397 |
|
398 | fromAPIKey(apiKey, options) {
|
399 | options = options || {};
|
400 | const client = new jwtclient_1.JWT(options);
|
401 | client.fromAPIKey(apiKey);
|
402 | return client;
|
403 | }
|
404 | |
405 |
|
406 |
|
407 |
|
408 | _isWindows() {
|
409 | const sys = os.platform();
|
410 | if (sys && sys.length >= 3) {
|
411 | if (sys.substring(0, 3).toLowerCase() === 'win') {
|
412 | return true;
|
413 | }
|
414 | }
|
415 | return false;
|
416 | }
|
417 | |
418 |
|
419 |
|
420 | async getDefaultServiceProjectId() {
|
421 | return new Promise(resolve => {
|
422 | child_process_1.exec('gcloud config config-helper --format json', (err, stdout) => {
|
423 | if (!err && stdout) {
|
424 | try {
|
425 | const projectId = JSON.parse(stdout).configuration.properties.core.project;
|
426 | resolve(projectId);
|
427 | return;
|
428 | }
|
429 | catch (e) {
|
430 |
|
431 | }
|
432 | }
|
433 | resolve(null);
|
434 | });
|
435 | });
|
436 | }
|
437 | |
438 |
|
439 |
|
440 |
|
441 | getProductionProjectId() {
|
442 | return (process.env['GCLOUD_PROJECT'] ||
|
443 | process.env['GOOGLE_CLOUD_PROJECT'] ||
|
444 | process.env['gcloud_project'] ||
|
445 | process.env['google_cloud_project']);
|
446 | }
|
447 | |
448 |
|
449 |
|
450 |
|
451 | async getFileProjectId() {
|
452 | if (this.cachedCredential) {
|
453 |
|
454 | return this.cachedCredential.projectId;
|
455 | }
|
456 |
|
457 | if (this.keyFilename) {
|
458 | const creds = await this.getClient();
|
459 | if (creds && creds.projectId) {
|
460 | return creds.projectId;
|
461 | }
|
462 | }
|
463 |
|
464 | const r = await this._tryGetApplicationCredentialsFromEnvironmentVariable();
|
465 | if (r) {
|
466 | return r.projectId;
|
467 | }
|
468 | else {
|
469 | return null;
|
470 | }
|
471 | }
|
472 | |
473 |
|
474 |
|
475 | async getExternalAccountClientProjectId() {
|
476 | if (!this.jsonContent || this.jsonContent.type !== baseexternalclient_1.EXTERNAL_ACCOUNT_TYPE) {
|
477 | return null;
|
478 | }
|
479 | const creds = await this.getClient();
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 |
|
490 |
|
491 | return await creds.getProjectId();
|
492 | }
|
493 | |
494 |
|
495 |
|
496 | async getGCEProjectId() {
|
497 | try {
|
498 | const r = await gcpMetadata.project('project-id');
|
499 | return r;
|
500 | }
|
501 | catch (e) {
|
502 |
|
503 | return null;
|
504 | }
|
505 | }
|
506 | getCredentials(callback) {
|
507 | if (callback) {
|
508 | this.getCredentialsAsync().then(r => callback(null, r), callback);
|
509 | }
|
510 | else {
|
511 | return this.getCredentialsAsync();
|
512 | }
|
513 | }
|
514 | async getCredentialsAsync() {
|
515 | await this.getClient();
|
516 | if (this.jsonContent) {
|
517 | const credential = {
|
518 | client_email: this.jsonContent.client_email,
|
519 | private_key: this.jsonContent.private_key,
|
520 | };
|
521 | return credential;
|
522 | }
|
523 | const isGCE = await this._checkIsGCE();
|
524 | if (!isGCE) {
|
525 | throw new Error('Unknown error.');
|
526 | }
|
527 |
|
528 |
|
529 |
|
530 |
|
531 | const data = await gcpMetadata.instance({
|
532 | property: 'service-accounts/',
|
533 | params: { recursive: 'true' },
|
534 | });
|
535 | if (!data || !data.default || !data.default.email) {
|
536 | throw new Error('Failure from metadata server.');
|
537 | }
|
538 | return { client_email: data.default.email };
|
539 | }
|
540 | |
541 |
|
542 |
|
543 |
|
544 | async getClient(options) {
|
545 | if (options) {
|
546 | throw new Error('Passing options to getClient is forbidden in v5.0.0. Use new GoogleAuth(opts) instead.');
|
547 | }
|
548 | if (!this.cachedCredential) {
|
549 | if (this.jsonContent) {
|
550 | this._cacheClientFromJSON(this.jsonContent, this.clientOptions);
|
551 | }
|
552 | else if (this.keyFilename) {
|
553 | const filePath = path.resolve(this.keyFilename);
|
554 | const stream = fs.createReadStream(filePath);
|
555 | await this.fromStreamAsync(stream, this.clientOptions);
|
556 | }
|
557 | else {
|
558 | await this.getApplicationDefaultAsync(this.clientOptions);
|
559 | }
|
560 | }
|
561 | return this.cachedCredential;
|
562 | }
|
563 | |
564 |
|
565 |
|
566 |
|
567 |
|
568 | async getIdTokenClient(targetAudience) {
|
569 | const client = await this.getClient();
|
570 | if (!('fetchIdToken' in client)) {
|
571 | throw new Error('Cannot fetch ID token in this environment, use GCE or set the GOOGLE_APPLICATION_CREDENTIALS environment variable to a service account credentials JSON file.');
|
572 | }
|
573 | return new idtokenclient_1.IdTokenClient({ targetAudience, idTokenProvider: client });
|
574 | }
|
575 | |
576 |
|
577 |
|
578 |
|
579 | async getAccessToken() {
|
580 | const client = await this.getClient();
|
581 | return (await client.getAccessToken()).token;
|
582 | }
|
583 | |
584 |
|
585 |
|
586 |
|
587 | async getRequestHeaders(url) {
|
588 | const client = await this.getClient();
|
589 | return client.getRequestHeaders(url);
|
590 | }
|
591 | |
592 |
|
593 |
|
594 |
|
595 |
|
596 | async authorizeRequest(opts) {
|
597 | opts = opts || {};
|
598 | const url = opts.url || opts.uri;
|
599 | const client = await this.getClient();
|
600 | const headers = await client.getRequestHeaders(url);
|
601 | opts.headers = Object.assign(opts.headers || {}, headers);
|
602 | return opts;
|
603 | }
|
604 | |
605 |
|
606 |
|
607 |
|
608 |
|
609 |
|
610 | async request(opts) {
|
611 | const client = await this.getClient();
|
612 | return client.request(opts);
|
613 | }
|
614 | |
615 |
|
616 |
|
617 | getEnv() {
|
618 | return envDetect_1.getEnv();
|
619 | }
|
620 | |
621 |
|
622 |
|
623 |
|
624 |
|
625 | async sign(data) {
|
626 | const client = await this.getClient();
|
627 | const crypto = crypto_1.createCrypto();
|
628 | if (client instanceof jwtclient_1.JWT && client.key) {
|
629 | const sign = await crypto.sign(client.key, data);
|
630 | return sign;
|
631 | }
|
632 |
|
633 |
|
634 |
|
635 |
|
636 |
|
637 |
|
638 | if (client instanceof baseexternalclient_1.BaseExternalAccountClient &&
|
639 | client.getServiceAccountEmail()) {
|
640 | return this.signBlob(crypto, client.getServiceAccountEmail(), data);
|
641 | }
|
642 | const projectId = await this.getProjectId();
|
643 | if (!projectId) {
|
644 | throw new Error('Cannot sign data without a project ID.');
|
645 | }
|
646 | const creds = await this.getCredentials();
|
647 | if (!creds.client_email) {
|
648 | throw new Error('Cannot sign data without `client_email`.');
|
649 | }
|
650 | return this.signBlob(crypto, creds.client_email, data);
|
651 | }
|
652 | async signBlob(crypto, emailOrUniqueId, data) {
|
653 | const url = 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/' +
|
654 | `${emailOrUniqueId}:signBlob`;
|
655 | const res = await this.request({
|
656 | method: 'POST',
|
657 | url,
|
658 | data: {
|
659 | payload: crypto.encodeBase64StringUtf8(data),
|
660 | },
|
661 | });
|
662 | return res.data.signedBlob;
|
663 | }
|
664 | }
|
665 | exports.GoogleAuth = GoogleAuth;
|
666 |
|
667 |
|
668 |
|
669 | GoogleAuth.DefaultTransporter = transporters_1.DefaultTransporter;
|
670 |
|
\ | No newline at end of file |