From b37489b6bc17645d3ea23fbceb2326adb296240b Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Wed, 31 Aug 2022 09:54:12 -0700 Subject: [PATCH] feat: Support Not Requiring `projectId` When Not Required (#1448) * feat: Support Not Requiring `projectId` When Not Required * test: Add test for `null` projectId support --- src/auth/googleauth.ts | 49 ++++++++++++++++++++++++++++++++--------- test/test.googleauth.ts | 23 +++++++++++++++++++ 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/auth/googleauth.ts b/src/auth/googleauth.ts index f7718754..415e5875 100644 --- a/src/auth/googleauth.ts +++ b/src/auth/googleauth.ts @@ -116,6 +116,13 @@ export interface GoogleAuthOptions { export const CLOUD_SDK_CLIENT_ID = '764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com'; +const GoogleAuthExceptionMessages = { + NO_PROJECT_ID_FOUND: + 'Unable to detect a Project Id in the current environment. \n' + + 'To learn more about authentication and Google APIs, visit: \n' + + 'https://cloud.google.com/docs/authentication/getting-started', +} as const; + export class GoogleAuth { transporter?: Transporter; @@ -192,6 +199,29 @@ export class GoogleAuth { } /** + * A temporary method for internal `getProjectId` usages where `null` is + * acceptable. In a future major release, `getProjectId` should return `null` + * (as the `Promise` base signature describes) and this private + * method should be removed. + * + * @returns Promise that resolves with project id (or `null`) + */ + async #getProjectIdOptional(): Promise { + try { + return await this.getProjectId(); + } catch (e) { + if ( + e instanceof Error && + e.message === GoogleAuthExceptionMessages.NO_PROJECT_ID_FOUND + ) { + return null; + } else { + throw e; + } + } + } + + /* * A private method for finding and caching a projectId. * * Supports environments in order of precedence: @@ -215,17 +245,13 @@ export class GoogleAuth { this._cachedProjectId = projectId; return projectId; } else { - throw new Error( - 'Unable to detect a Project Id in the current environment. \n' + - 'To learn more about authentication and Google APIs, visit: \n' + - 'https://cloud.google.com/docs/authentication/getting-started' - ); + throw new Error(GoogleAuthExceptionMessages.NO_PROJECT_ID_FOUND); } } - private getProjectIdAsync(): Promise { + private async getProjectIdAsync(): Promise { if (this._cachedProjectId) { - return Promise.resolve(this._cachedProjectId); + return this._cachedProjectId; } if (!this._findProjectIdPromise) { @@ -279,7 +305,7 @@ export class GoogleAuth { if (this.cachedCredential) { return { credential: this.cachedCredential, - projectId: await this.getProjectIdAsync(), + projectId: await this.#getProjectIdOptional(), }; } @@ -297,7 +323,8 @@ export class GoogleAuth { credential.scopes = this.getAnyScopes(); } this.cachedCredential = credential; - projectId = await this.getProjectId(); + projectId = await this.#getProjectIdOptional(); + return {credential, projectId}; } @@ -312,7 +339,7 @@ export class GoogleAuth { credential.scopes = this.getAnyScopes(); } this.cachedCredential = credential; - projectId = await this.getProjectId(); + projectId = await this.#getProjectIdOptional(); return {credential, projectId}; } @@ -339,7 +366,7 @@ export class GoogleAuth { // the rest. (options as ComputeOptions).scopes = this.getAnyScopes(); this.cachedCredential = new Compute(options); - projectId = await this.getProjectId(); + projectId = await this.#getProjectIdOptional(); return {projectId, credential: this.cachedCredential}; } diff --git a/test/test.googleauth.ts b/test/test.googleauth.ts index 2f3f990a..ca584254 100644 --- a/test/test.googleauth.ts +++ b/test/test.googleauth.ts @@ -1998,6 +1998,29 @@ describe('googleauth', () => { ); scopes.forEach(s => s.done()); }); + + it('should return `null` for `projectId` when on cannot be found', async () => { + // Environment variable is set up to point to external-account-cred.json + mockEnvVar( + 'GOOGLE_APPLICATION_CREDENTIALS', + './test/fixtures/external-account-cred.json' + ); + + const auth = new GoogleAuth(); + + sandbox + .stub( + auth as {} as { + getProjectIdAsync: Promise; + }, + 'getProjectIdAsync' + ) + .resolves(null); + + const res = await auth.getApplicationDefault(); + + assert.equal(res.projectId, null); + }); }); describe('getApplicationCredentialsFromFilePath()', () => {