From d4c39f385ea715c79f9bae1853748700dc96c152 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Tue, 22 Feb 2022 13:41:46 -0800 Subject: [PATCH] feat: Support `AuthClient` for `authClient` (#732) --- package.json | 2 +- src/service.ts | 12 ++++++------ src/util.ts | 29 +++++++++++++++++++++-------- test/service-object.ts | 1 + test/service.ts | 41 +++++++++++++++++++++++++++++++++++------ test/util.ts | 39 +++++++++++++++++++++++++++++++++++++-- 6 files changed, 101 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 4b401861..6b0918fc 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "duplexify": "^4.1.1", "ent": "^2.2.0", "extend": "^3.0.2", - "google-auth-library": "^7.9.2", + "google-auth-library": "^7.14.0", "retry-request": "^4.2.2", "teeny-request": "^7.0.0" }, diff --git a/src/service.ts b/src/service.ts index c14b4e4b..70936ece 100644 --- a/src/service.ts +++ b/src/service.ts @@ -18,7 +18,7 @@ import arrify = require('arrify'); import * as extend from 'extend'; -import {GoogleAuth, GoogleAuthOptions} from 'google-auth-library'; +import {AuthClient, GoogleAuth, GoogleAuthOptions} from 'google-auth-library'; import * as r from 'teeny-request'; import {Interceptor} from './service-object'; @@ -57,13 +57,13 @@ export interface ServiceConfig { packageJson: PackageJson; /** - * Reuse an existing GoogleAuth client instead of creating a new one. + * Reuse an existing `AuthClient` or `GoogleAuth` client instead of creating a new one. */ - authClient?: GoogleAuth; + authClient?: AuthClient | GoogleAuth; } -export interface ServiceOptions extends GoogleAuthOptions { - authClient?: GoogleAuth; +export interface ServiceOptions extends Omit { + authClient?: AuthClient | GoogleAuth; interceptors_?: Interceptor[]; email?: string; token?: string; @@ -81,7 +81,7 @@ export class Service { private projectIdRequired: boolean; providedUserAgent?: string; makeAuthenticatedRequest: MakeAuthenticatedRequest; - authClient: GoogleAuth; + authClient: GoogleAuth; private getCredentials: {}; readonly apiEndpoint: string; timeout?: number; diff --git a/src/util.ts b/src/util.ts index 21d71cb0..dd7c2f86 100644 --- a/src/util.ts +++ b/src/util.ts @@ -19,7 +19,7 @@ import {replaceProjectIdToken} from '@google-cloud/projectify'; import * as ent from 'ent'; import * as extend from 'extend'; -import {GoogleAuth, GoogleAuthOptions} from 'google-auth-library'; +import {AuthClient, GoogleAuth, GoogleAuthOptions} from 'google-auth-library'; import {CredentialBody} from 'google-auth-library'; import * as r from 'teeny-request'; import * as retryRequest from 'retry-request'; @@ -111,7 +111,7 @@ export interface MakeAuthenticatedRequest { getCredentials: ( callback: (err?: Error | null, credentials?: CredentialBody) => void ) => void; - authClient: GoogleAuth; + authClient: GoogleAuth; } export interface Abortable { @@ -125,7 +125,7 @@ export interface PackageJson { } export interface MakeAuthenticatedRequestFactoryConfig - extends GoogleAuthOptions { + extends Omit { /** * Automatically retry requests if the response is related to rate limits or * certain intermittent server errors. We will exponentially backoff @@ -157,10 +157,10 @@ export interface MakeAuthenticatedRequestFactoryConfig stream?: Duplexify; /** - * A pre-instantiated GoogleAuth client that should be used. + * A pre-instantiated `AuthClient` or `GoogleAuth` client that should be used. * A new will be created if this is not set. */ - authClient?: GoogleAuth; + authClient?: AuthClient | GoogleAuth; } export interface MakeAuthenticatedRequestOptions { @@ -591,8 +591,21 @@ export class Util { if (googleAutoAuthConfig.projectId === '{{projectId}}') { delete googleAutoAuthConfig.projectId; } - const authClient = - googleAutoAuthConfig.authClient || new GoogleAuth(googleAutoAuthConfig); + + let authClient: GoogleAuth; + + if (googleAutoAuthConfig.authClient instanceof GoogleAuth) { + // Use an existing `GoogleAuth` + authClient = googleAutoAuthConfig.authClient; + } else { + // Pass an `AuthClient` to `GoogleAuth`, if available + const config = { + ...googleAutoAuthConfig, + authClient: googleAutoAuthConfig.authClient, + }; + + authClient = new GoogleAuth(config); + } /** * The returned function that will make an authenticated request. @@ -659,7 +672,7 @@ export class Util { // A projectId was required, but we don't have one. // Re-use the "Could not load the default credentials error" if // auto auth failed. - err = err || e; + err = err || (e as Error); } } diff --git a/test/service-object.ts b/test/service-object.ts index 8d44c3b3..96fc36a8 100644 --- a/test/service-object.ts +++ b/test/service-object.ts @@ -23,6 +23,7 @@ import * as extend from 'extend'; import * as proxyquire from 'proxyquire'; import * as r from 'teeny-request'; import * as sinon from 'sinon'; +import {AuthClient, OAuth2Client} from 'google-auth-library'; import {Service} from '../src'; import * as SO from '../src/service-object'; diff --git a/test/service.ts b/test/service.ts index 171ef8ee..43583a4f 100644 --- a/test/service.ts +++ b/test/service.ts @@ -17,6 +17,7 @@ import {describe, it, before, beforeEach, after} from 'mocha'; import * as extend from 'extend'; import * as proxyquire from 'proxyquire'; import {Request} from 'teeny-request'; +import {AuthClient, GoogleAuth, OAuth2Client} from 'google-auth-library'; import {Interceptor} from '../src'; import {ServiceConfig, ServiceOptions} from '../src/service'; @@ -69,7 +70,7 @@ describe('Service', () => { }; const OPTIONS = { - authClient: {getCredentials: () => {}}, + authClient: new GoogleAuth(), credentials: {}, keyFile: {}, email: 'email', @@ -130,11 +131,39 @@ describe('Service', () => { assert.strictEqual(service.authClient, OPTIONS.authClient); }); - it('should allow passing a custom GoogleAuth client', () => { - const authClient = {getCredentials: () => {}}; - const cfg = Object.assign({}, {authClient}, CONFIG); - const service = new Service(cfg); - assert.strictEqual(service.authClient, authClient); + describe('`AuthClient` support', () => { + // Using a custom `AuthClient` to ensure any `AuthClient` would work + class CustomAuthClient extends AuthClient { + async getAccessToken() { + return {token: '', res: undefined}; + } + + async getRequestHeaders() { + return {}; + } + + request = OAuth2Client.prototype.request.bind(this); + } + + it('should accept an `AuthClient` passed to config', async () => { + const authClient = new CustomAuthClient(); + const serviceObject = new Service({...CONFIG, authClient}); + + // The custom `AuthClient` should be passed to `GoogleAuth` and used internally + const client = await serviceObject.authClient.getClient(); + + assert.strictEqual(client, authClient); + }); + + it('should accept an `AuthClient` passed to options', async () => { + const authClient = new CustomAuthClient(); + const serviceObject = new Service(CONFIG, {authClient}); + + // The custom `AuthClient` should be passed to `GoogleAuth` and used internally + const client = await serviceObject.authClient.getClient(); + + assert.strictEqual(client, authClient); + }); }); it('should localize the baseUrl', () => { diff --git a/test/util.ts b/test/util.ts index ef782555..784dcf48 100644 --- a/test/util.ts +++ b/test/util.ts @@ -16,7 +16,12 @@ import {replaceProjectIdToken} from '@google-cloud/projectify'; import * as assert from 'assert'; import {describe, it, before, beforeEach, afterEach} from 'mocha'; import * as extend from 'extend'; -import {GoogleAuth, GoogleAuthOptions} from 'google-auth-library'; +import { + AuthClient, + GoogleAuth, + GoogleAuthOptions, + OAuth2Client, +} from 'google-auth-library'; import * as nock from 'nock'; import * as proxyquire from 'proxyquire'; import * as r from 'teeny-request'; @@ -113,6 +118,18 @@ describe('common/util', () => { } const fakeGoogleAuth = { + // Using a custom `AuthClient` to ensure any `AuthClient` would work + AuthClient: class CustomAuthClient extends AuthClient { + async getAccessToken() { + return {token: '', res: undefined}; + } + + async getRequestHeaders() { + return {}; + } + + request = OAuth2Client.prototype.request.bind(this); + }, GoogleAuth: class { constructor(config?: GoogleAuthOptions) { return new GoogleAuth(config); @@ -590,7 +607,7 @@ describe('common/util', () => { const fakeStream = new stream.Writable(); const error = new Error('Error.'); fakeStream.write = () => false; - dup.end = () => {}; + dup.end = () => dup; stub('handleResp', (err, res, body, callback) => { callback(error); @@ -705,6 +722,24 @@ describe('common/util', () => { it('should create an authClient', done => { const config = {test: true} as MakeAuthenticatedRequestFactoryConfig; + sandbox + .stub(fakeGoogleAuth, 'GoogleAuth') + .callsFake((config_: GoogleAuthOptions) => { + assert.deepStrictEqual(config_, {...config, authClient: undefined}); + setImmediate(done); + return authClient; + }); + + util.makeAuthenticatedRequestFactory(config); + }); + + it('should pass an `AuthClient` to `GoogleAuth` when provided', done => { + const customAuthClient = new fakeGoogleAuth.AuthClient(); + + const config: MakeAuthenticatedRequestFactoryConfig = { + authClient: customAuthClient, + }; + sandbox .stub(fakeGoogleAuth, 'GoogleAuth') .callsFake((config_: GoogleAuthOptions) => {