Skip to content

Commit dbcc44b

Browse files
authored
refactor!: Remove Transporter (#1937)
* refactor: remove `Transporter` * chore: Migrate and deprecate `BodyResponseCallback` * refactor!: Remove `Transporter` * refactor: Use Gaxios Interceptors for default Auth headers * refactor: keep old name to reduce refactoring downstream (if used) * test: Add explicit tests for uniform auth headers * style: lint
1 parent 84d91b9 commit dbcc44b

20 files changed

+333
-409
lines changed

samples/test/externalclient.test.js

+11-10
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,7 @@ const {assert} = require('chai');
7272
const {describe, it, before, afterEach} = require('mocha');
7373
const fs = require('fs');
7474
const {promisify} = require('util');
75-
const {
76-
GoogleAuth,
77-
DefaultTransporter,
78-
IdentityPoolClient,
79-
} = require('google-auth-library');
75+
const {GoogleAuth, IdentityPoolClient, gaxios} = require('google-auth-library');
8076
const os = require('os');
8177
const path = require('path');
8278
const http = require('http');
@@ -158,11 +154,16 @@ const assumeRoleWithWebIdentity = async (
158154
// been configured:
159155
// https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html
160156
const oidcToken = await generateGoogleIdToken(auth, aud, clientEmail);
161-
const transporter = new DefaultTransporter();
162-
const url =
163-
'https://sts.amazonaws.com/?Action=AssumeRoleWithWebIdentity' +
164-
'&Version=2011-06-15&DurationSeconds=3600&RoleSessionName=nodejs-test' +
165-
`&RoleArn=${awsRoleArn}&WebIdentityToken=${oidcToken}`;
157+
const transporter = new gaxios.Gaxios();
158+
159+
const url = new URL('https://sts.amazonaws.com/');
160+
url.searchParams.append('Action', 'AssumeRoleWithWebIdentity');
161+
url.searchParams.append('Version', '2011-06-15');
162+
url.searchParams.append('DurationSeconds', '3600');
163+
url.searchParams.append('RoleSessionName', 'nodejs-test');
164+
url.searchParams.append('RoleArn', awsRoleArn);
165+
url.searchParams.append('WebIdentityToken', oidcToken);
166+
166167
// The response is in XML format but we will parse it as text.
167168
const response = await transporter.request({url, responseType: 'text'});
168169
const rawXml = response.data;

src/auth/authclient.ts

+65-28
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
import {EventEmitter} from 'events';
1616
import {Gaxios, GaxiosOptions, GaxiosPromise, GaxiosResponse} from 'gaxios';
1717

18-
import {DefaultTransporter, Transporter} from '../transporters';
1918
import {Credentials} from './credentials';
2019
import {OriginalAndCamel, originalOrCamelOptions} from '../util';
2120

21+
import {PRODUCT_NAME, USER_AGENT} from '../shared.cjs';
22+
2223
/**
2324
* Base auth configurations (e.g. from JWT or `.json` files) with conventional
2425
* camelCased options.
@@ -81,13 +82,17 @@ export interface AuthClientOptions
8182
credentials?: Credentials;
8283

8384
/**
84-
* A `Gaxios` or `Transporter` instance to use for `AuthClient` requests.
85+
* The {@link Gaxios `Gaxios`} instance used for making requests.
86+
*
87+
* @see {@link AuthClientOptions.useAuthRequestParameters}
8588
*/
86-
transporter?: Gaxios | Transporter;
89+
transporter?: Gaxios;
8790

8891
/**
8992
* Provides default options to the transporter, such as {@link GaxiosOptions.agent `agent`} or
9093
* {@link GaxiosOptions.retryConfig `retryConfig`}.
94+
*
95+
* This option is ignored if {@link AuthClientOptions.transporter `gaxios`} has been provided
9196
*/
9297
transporterOptions?: GaxiosOptions;
9398

@@ -103,6 +108,19 @@ export interface AuthClientOptions
103108
* on the expiry_date.
104109
*/
105110
forceRefreshOnFailure?: boolean;
111+
112+
/**
113+
* Enables/disables the adding of the AuthClient's default interceptor.
114+
*
115+
* @see {@link AuthClientOptions.transporter}
116+
*
117+
* @remarks
118+
*
119+
* Disabling is useful for debugging and experimentation.
120+
*
121+
* @default true
122+
*/
123+
useAuthRequestParameters?: boolean;
106124
}
107125

108126
/**
@@ -183,7 +201,10 @@ export abstract class AuthClient
183201
* See {@link https://cloud.google.com/docs/quota Working with quotas}
184202
*/
185203
quotaProjectId?: string;
186-
transporter: Transporter;
204+
/**
205+
* The {@link Gaxios `Gaxios`} instance used for making requests.
206+
*/
207+
transporter: Gaxios;
187208
credentials: Credentials = {};
188209
eagerRefreshThresholdMillis = DEFAULT_EAGER_REFRESH_THRESHOLD_MILLIS;
189210
forceRefreshOnFailure = false;
@@ -202,10 +223,12 @@ export abstract class AuthClient
202223
this.universeDomain = options.get('universe_domain') ?? DEFAULT_UNIVERSE;
203224

204225
// Shared client options
205-
this.transporter = opts.transporter ?? new DefaultTransporter();
226+
this.transporter = opts.transporter ?? new Gaxios(opts.transporterOptions);
206227

207-
if (opts.transporterOptions) {
208-
this.transporter.defaults = opts.transporterOptions;
228+
if (options.get('useAuthRequestParameters') !== false) {
229+
this.transporter.interceptors.request.add(
230+
AuthClient.DEFAULT_REQUEST_INTERCEPTOR
231+
);
209232
}
210233

211234
if (opts.eagerRefreshThresholdMillis) {
@@ -216,29 +239,11 @@ export abstract class AuthClient
216239
}
217240

218241
/**
219-
* Return the {@link Gaxios `Gaxios`} instance from the {@link AuthClient.transporter}.
242+
* The public request API in which credentials may be added to the request.
220243
*
221-
* @expiremental
222-
*/
223-
get gaxios(): Gaxios | null {
224-
if (this.transporter instanceof Gaxios) {
225-
return this.transporter;
226-
} else if (this.transporter instanceof DefaultTransporter) {
227-
return this.transporter.instance;
228-
} else if (
229-
'instance' in this.transporter &&
230-
this.transporter.instance instanceof Gaxios
231-
) {
232-
return this.transporter.instance;
233-
}
234-
235-
return null;
236-
}
237-
238-
/**
239-
* Provides an alternative Gaxios request implementation with auth credentials
244+
* @param options options for `gaxios`
240245
*/
241-
abstract request<T>(opts: GaxiosOptions): GaxiosPromise<T>;
246+
abstract request<T>(options: GaxiosOptions): GaxiosPromise<T>;
242247

243248
/**
244249
* The main authentication interface. It takes an optional url which when
@@ -288,6 +293,31 @@ export abstract class AuthClient
288293
return headers;
289294
}
290295

296+
static readonly DEFAULT_REQUEST_INTERCEPTOR: Parameters<
297+
Gaxios['interceptors']['request']['add']
298+
>[0] = {
299+
resolved: async config => {
300+
const headers = config.headers || {};
301+
302+
// Set `x-goog-api-client`, if not already set
303+
if (!headers['x-goog-api-client']) {
304+
const nodeVersion = process.version.replace(/^v/, '');
305+
headers['x-goog-api-client'] = `gl-node/${nodeVersion}`;
306+
}
307+
308+
// Set `User-Agent`
309+
if (!headers['User-Agent']) {
310+
headers['User-Agent'] = USER_AGENT;
311+
} else if (!headers['User-Agent'].includes(`${PRODUCT_NAME}/`)) {
312+
headers['User-Agent'] = `${headers['User-Agent']} ${USER_AGENT}`;
313+
}
314+
315+
config.headers = headers;
316+
317+
return config;
318+
},
319+
};
320+
291321
/**
292322
* Retry config for Auth-related requests.
293323
*
@@ -315,3 +345,10 @@ export interface GetAccessTokenResponse {
315345
token?: string | null;
316346
res?: GaxiosResponse | null;
317347
}
348+
349+
/**
350+
* @deprecated - use the Promise API instead
351+
*/
352+
export interface BodyResponseCallback<T> {
353+
(err: Error | null, res?: GaxiosResponse<T> | null): void;
354+
}

src/auth/baseexternalclient.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import {
2727
AuthClientOptions,
2828
GetAccessTokenResponse,
2929
Headers,
30+
BodyResponseCallback,
3031
} from './authclient';
31-
import {BodyResponseCallback, Transporter} from '../transporters';
3232
import * as sts from './stscredentials';
3333
import {ClientAuthentication} from './oauth2common';
3434
import {SnakeToCamelObject, originalOrCamelOptions} from '../util';
@@ -110,10 +110,11 @@ export interface ExternalAccountSupplierContext {
110110
* * "urn:ietf:params:oauth:token-type:id_token"
111111
*/
112112
subjectTokenType: string;
113-
/** The {@link Gaxios} or {@link Transporter} instance from
114-
* the calling external account to use for requests.
113+
/**
114+
* The {@link Gaxios} instance for calling external account
115+
* to use for requests.
115116
*/
116-
transporter: Transporter | Gaxios;
117+
transporter: Gaxios;
117118
}
118119

119120
/**
@@ -312,7 +313,10 @@ export abstract class BaseExternalAccountClient extends AuthClient {
312313
};
313314
}
314315

315-
this.stsCredential = new sts.StsCredentials(tokenUrl, this.clientAuth);
316+
this.stsCredential = new sts.StsCredentials({
317+
tokenExchangeEndpoint: tokenUrl,
318+
clientAuthentication: this.clientAuth,
319+
});
316320
this.scopes = opts.get('scopes') || [DEFAULT_OAUTH_SCOPE];
317321
this.cachedAccessToken = null;
318322
this.audience = opts.get('audience');

src/auth/defaultawssecuritycredentialssupplier.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import {ExternalAccountSupplierContext} from './baseexternalclient';
1616
import {Gaxios, GaxiosOptions} from 'gaxios';
17-
import {Transporter} from '../transporters';
1817
import {AwsSecurityCredentialsSupplier} from './awsclient';
1918
import {AwsSecurityCredentials} from './awsrequestsigner';
2019
import {Headers} from './authclient';
@@ -183,9 +182,7 @@ export class DefaultAwsSecurityCredentialsSupplier
183182
* @param transporter The transporter to use for requests.
184183
* @return A promise that resolves with the IMDSv2 Session Token.
185184
*/
186-
async #getImdsV2SessionToken(
187-
transporter: Transporter | Gaxios
188-
): Promise<string> {
185+
async #getImdsV2SessionToken(transporter: Gaxios): Promise<string> {
189186
const opts: GaxiosOptions = {
190187
...this.additionalGaxiosOptions,
191188
url: this.imdsV2SessionTokenUrl,
@@ -205,7 +202,7 @@ export class DefaultAwsSecurityCredentialsSupplier
205202
*/
206203
async #getAwsRoleName(
207204
headers: Headers,
208-
transporter: Transporter | Gaxios
205+
transporter: Gaxios
209206
): Promise<string> {
210207
if (!this.securityCredentialsUrl) {
211208
throw new Error(
@@ -236,7 +233,7 @@ export class DefaultAwsSecurityCredentialsSupplier
236233
async #retrieveAwsSecurityCredentials(
237234
roleName: string,
238235
headers: Headers,
239-
transporter: Transporter | Gaxios
236+
transporter: Gaxios
240237
): Promise<AwsSecurityCredentialsResponse> {
241238
const response = await transporter.request<AwsSecurityCredentialsResponse>({
242239
...this.additionalGaxiosOptions,

src/auth/downscopedclient.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ import {
2020
} from 'gaxios';
2121
import * as stream from 'stream';
2222

23-
import {BodyResponseCallback} from '../transporters';
2423
import {Credentials} from './credentials';
2524
import {
2625
AuthClient,
2726
AuthClientOptions,
2827
GetAccessTokenResponse,
2928
Headers,
29+
BodyResponseCallback,
3030
} from './authclient';
3131

3232
import * as sts from './stscredentials';
@@ -189,9 +189,9 @@ export class DownscopedClient extends AuthClient {
189189
}
190190
}
191191

192-
this.stsCredential = new sts.StsCredentials(
193-
`https://sts.${this.universeDomain}/v1/token`
194-
);
192+
this.stsCredential = new sts.StsCredentials({
193+
tokenExchangeEndpoint: `https://sts.${this.universeDomain}/v1/token`,
194+
});
195195

196196
this.cachedDownscopedAccessToken = null;
197197
}

src/auth/externalAccountAuthorizedUserClient.ts

+24-15
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import {AuthClient, Headers} from './authclient';
15+
import {AuthClient, Headers, BodyResponseCallback} from './authclient';
1616
import {
1717
ClientAuthentication,
1818
getErrorFromOAuthErrorResponse,
1919
OAuthClientAuthHandler,
20+
OAuthClientAuthHandlerOptions,
2021
OAuthErrorResponse,
2122
} from './oauth2common';
22-
import {BodyResponseCallback, Transporter} from '../transporters';
2323
import {
2424
GaxiosError,
2525
GaxiosOptions,
@@ -69,24 +69,32 @@ interface TokenRefreshResponse {
6969
res?: GaxiosResponse | null;
7070
}
7171

72+
interface ExternalAccountAuthorizedUserHandlerOptions
73+
extends OAuthClientAuthHandlerOptions {
74+
/**
75+
* The URL of the token refresh endpoint.
76+
*/
77+
tokenRefreshEndpoint: string | URL;
78+
}
79+
7280
/**
7381
* Handler for token refresh requests sent to the token_url endpoint for external
7482
* authorized user credentials.
7583
*/
7684
class ExternalAccountAuthorizedUserHandler extends OAuthClientAuthHandler {
85+
#tokenRefreshEndpoint: string | URL;
86+
7787
/**
7888
* Initializes an ExternalAccountAuthorizedUserHandler instance.
7989
* @param url The URL of the token refresh endpoint.
8090
* @param transporter The transporter to use for the refresh request.
8191
* @param clientAuthentication The client authentication credentials to use
8292
* for the refresh request.
8393
*/
84-
constructor(
85-
private readonly url: string,
86-
private readonly transporter: Transporter,
87-
clientAuthentication?: ClientAuthentication
88-
) {
89-
super(clientAuthentication);
94+
constructor(options: ExternalAccountAuthorizedUserHandlerOptions) {
95+
super(options);
96+
97+
this.#tokenRefreshEndpoint = options.tokenRefreshEndpoint;
9098
}
9199

92100
/**
@@ -114,7 +122,7 @@ class ExternalAccountAuthorizedUserHandler extends OAuthClientAuthHandler {
114122

115123
const opts: GaxiosOptions = {
116124
...ExternalAccountAuthorizedUserHandler.RETRY_CONFIG,
117-
url: this.url,
125+
url: this.#tokenRefreshEndpoint,
118126
method: 'POST',
119127
headers,
120128
data: values.toString(),
@@ -169,18 +177,19 @@ export class ExternalAccountAuthorizedUserClient extends AuthClient {
169177
this.universeDomain = options.universe_domain;
170178
}
171179
this.refreshToken = options.refresh_token;
172-
const clientAuth = {
180+
const clientAuthentication = {
173181
confidentialClientType: 'basic',
174182
clientId: options.client_id,
175183
clientSecret: options.client_secret,
176184
} as ClientAuthentication;
177185
this.externalAccountAuthorizedUserHandler =
178-
new ExternalAccountAuthorizedUserHandler(
179-
options.token_url ??
186+
new ExternalAccountAuthorizedUserHandler({
187+
tokenRefreshEndpoint:
188+
options.token_url ??
180189
DEFAULT_TOKEN_URL.replace('{universeDomain}', this.universeDomain),
181-
this.transporter,
182-
clientAuth
183-
);
190+
transporter: this.transporter,
191+
clientAuthentication,
192+
});
184193

185194
this.cachedAccessToken = null;
186195
this.quotaProjectId = options.quota_project_id;

src/auth/oauth2client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ import * as stream from 'stream';
2323
import * as formatEcdsa from 'ecdsa-sig-formatter';
2424

2525
import {createCrypto, JwkCertificate, hasBrowserCrypto} from '../crypto/crypto';
26-
import {BodyResponseCallback} from '../transporters';
2726

2827
import {
2928
AuthClient,
3029
AuthClientOptions,
3130
GetAccessTokenResponse,
3231
Headers,
32+
BodyResponseCallback,
3333
} from './authclient';
3434
import {CredentialRequest, Credentials} from './credentials';
3535
import {LoginTicket, TokenPayload} from './loginticket';

0 commit comments

Comments
 (0)