Skip to content

Commit

Permalink
refactor!: Remove Transporter
Browse files Browse the repository at this point in the history
  • Loading branch information
d-goog committed Feb 5, 2025
1 parent 377a0fa commit 8bdeec9
Show file tree
Hide file tree
Showing 16 changed files with 130 additions and 366 deletions.
6 changes: 3 additions & 3 deletions src/auth/authclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export abstract class AuthClient
/**
* The {@link Gaxios `Gaxios`} instance used for making requests.
*/
gaxios: Gaxios;
transporter: Gaxios;
credentials: Credentials = {};
eagerRefreshThresholdMillis = DEFAULT_EAGER_REFRESH_THRESHOLD_MILLIS;
forceRefreshOnFailure = false;
Expand All @@ -208,7 +208,7 @@ export abstract class AuthClient
this.universeDomain = options.get('universe_domain') ?? DEFAULT_UNIVERSE;

// Shared client options
this.gaxios = opts.gaxios ?? new Gaxios(opts.transporterOptions);
this.transporter = opts.gaxios ?? new Gaxios(opts.transporterOptions);

if (opts.eagerRefreshThresholdMillis) {
this.eagerRefreshThresholdMillis = opts.eagerRefreshThresholdMillis;
Expand Down Expand Up @@ -252,7 +252,7 @@ export abstract class AuthClient

options.headers = headers;

return this.gaxios.request<T>(options);
return this.transporter.request<T>(options);
}

/**
Expand Down
13 changes: 8 additions & 5 deletions src/auth/baseexternalclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
Headers,
BodyResponseCallback,
} from './authclient';
import {Transporter} from '../transporters';
import * as sts from './stscredentials';
import {ClientAuthentication} from './oauth2common';
import {SnakeToCamelObject, originalOrCamelOptions} from '../util';
Expand Down Expand Up @@ -111,10 +110,11 @@ export interface ExternalAccountSupplierContext {
* * "urn:ietf:params:oauth:token-type:id_token"
*/
subjectTokenType: string;
/** The {@link Gaxios} or {@link Transporter} instance from
* the calling external account to use for requests.
/**
* The {@link Gaxios} instance for calling external account
* to use for requests.
*/
transporter: Transporter | Gaxios;
transporter: Gaxios;
}

/**
Expand Down Expand Up @@ -313,7 +313,10 @@ export abstract class BaseExternalAccountClient extends AuthClient {
};
}

this.stsCredential = new sts.StsCredentials(tokenUrl, this.clientAuth);
this.stsCredential = new sts.StsCredentials({
tokenExchangeEndpoint: tokenUrl,
clientAuthentication: this.clientAuth,
});
this.scopes = opts.get('scopes') || [DEFAULT_OAUTH_SCOPE];
this.cachedAccessToken = null;
this.audience = opts.get('audience');
Expand Down
9 changes: 3 additions & 6 deletions src/auth/defaultawssecuritycredentialssupplier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import {ExternalAccountSupplierContext} from './baseexternalclient';
import {Gaxios, GaxiosOptions} from 'gaxios';
import {Transporter} from '../transporters';
import {AwsSecurityCredentialsSupplier} from './awsclient';
import {AwsSecurityCredentials} from './awsrequestsigner';
import {Headers} from './authclient';
Expand Down Expand Up @@ -183,9 +182,7 @@ export class DefaultAwsSecurityCredentialsSupplier
* @param transporter The transporter to use for requests.
* @return A promise that resolves with the IMDSv2 Session Token.
*/
async #getImdsV2SessionToken(
transporter: Transporter | Gaxios
): Promise<string> {
async #getImdsV2SessionToken(transporter: Gaxios): Promise<string> {
const opts: GaxiosOptions = {
...this.additionalGaxiosOptions,
url: this.imdsV2SessionTokenUrl,
Expand All @@ -205,7 +202,7 @@ export class DefaultAwsSecurityCredentialsSupplier
*/
async #getAwsRoleName(
headers: Headers,
transporter: Transporter | Gaxios
transporter: Gaxios
): Promise<string> {
if (!this.securityCredentialsUrl) {
throw new Error(
Expand Down Expand Up @@ -236,7 +233,7 @@ export class DefaultAwsSecurityCredentialsSupplier
async #retrieveAwsSecurityCredentials(
roleName: string,
headers: Headers,
transporter: Transporter | Gaxios
transporter: Gaxios
): Promise<AwsSecurityCredentialsResponse> {
const response = await transporter.request<AwsSecurityCredentialsResponse>({
...this.additionalGaxiosOptions,
Expand Down
6 changes: 3 additions & 3 deletions src/auth/downscopedclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,9 @@ export class DownscopedClient extends AuthClient {
}
}

this.stsCredential = new sts.StsCredentials(
`https://sts.${this.universeDomain}/v1/token`
);
this.stsCredential = new sts.StsCredentials({
tokenExchangeEndpoint: `https://sts.${this.universeDomain}/v1/token`,
});

this.cachedDownscopedAccessToken = null;
}
Expand Down
37 changes: 23 additions & 14 deletions src/auth/externalAccountAuthorizedUserClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import {
ClientAuthentication,
getErrorFromOAuthErrorResponse,
OAuthClientAuthHandler,
OAuthClientAuthHandlerOptions,
OAuthErrorResponse,
} from './oauth2common';
import {Transporter} from '../transporters';
import {
GaxiosError,
GaxiosOptions,
Expand Down Expand Up @@ -69,24 +69,32 @@ interface TokenRefreshResponse {
res?: GaxiosResponse | null;
}

interface ExternalAccountAuthorizedUserHandlerOptions
extends OAuthClientAuthHandlerOptions {
/**
* The URL of the token refresh endpoint.
*/
tokenRefreshEndpoint: string | URL;
}

/**
* Handler for token refresh requests sent to the token_url endpoint for external
* authorized user credentials.
*/
class ExternalAccountAuthorizedUserHandler extends OAuthClientAuthHandler {
#tokenRefreshEndpoint: string | URL;

/**
* Initializes an ExternalAccountAuthorizedUserHandler instance.
* @param url The URL of the token refresh endpoint.
* @param transporter The transporter to use for the refresh request.
* @param clientAuthentication The client authentication credentials to use
* for the refresh request.
*/
constructor(
private readonly url: string,
private readonly transporter: Transporter,
clientAuthentication?: ClientAuthentication
) {
super(clientAuthentication);
constructor(options: ExternalAccountAuthorizedUserHandlerOptions) {
super(options);

this.#tokenRefreshEndpoint = options.tokenRefreshEndpoint;
}

/**
Expand Down Expand Up @@ -114,7 +122,7 @@ class ExternalAccountAuthorizedUserHandler extends OAuthClientAuthHandler {

const opts: GaxiosOptions = {
...ExternalAccountAuthorizedUserHandler.RETRY_CONFIG,
url: this.url,
url: this.#tokenRefreshEndpoint,
method: 'POST',
headers,
data: values.toString(),
Expand Down Expand Up @@ -169,18 +177,19 @@ export class ExternalAccountAuthorizedUserClient extends AuthClient {
this.universeDomain = options.universe_domain;
}
this.refreshToken = options.refresh_token;
const clientAuth = {
const clientAuthentication = {
confidentialClientType: 'basic',
clientId: options.client_id,
clientSecret: options.client_secret,
} as ClientAuthentication;
this.externalAccountAuthorizedUserHandler =
new ExternalAccountAuthorizedUserHandler(
options.token_url ??
new ExternalAccountAuthorizedUserHandler({
tokenRefreshEndpoint:
options.token_url ??
DEFAULT_TOKEN_URL.replace('{universeDomain}', this.universeDomain),
this.transporter,
clientAuth
);
transporter: this.transporter,
clientAuthentication,
});

this.cachedAccessToken = null;
this.quotaProjectId = options.quota_project_id;
Expand Down
52 changes: 36 additions & 16 deletions src/auth/oauth2common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {GaxiosOptions} from 'gaxios';
import {Gaxios, GaxiosOptions} from 'gaxios';
import * as querystring from 'querystring';

import {Crypto, createCrypto} from '../crypto/crypto';

Check warning on line 18 in src/auth/oauth2common.ts

View workflow job for this annotation

GitHub Actions / lint

'Crypto' is defined but never used
Expand Down Expand Up @@ -60,6 +60,18 @@ export interface ClientAuthentication {
clientSecret?: string;
}

export interface OAuthClientAuthHandlerOptions {
/**
* Defines the client authentication credentials for basic and request-body
* credentials.
*/
clientAuthentication?: ClientAuthentication;
/**
* An optional transporter to use.
*/
transporter?: Gaxios;
}

/**
* Abstract class for handling client authentication in OAuth-based
* operations.
Expand All @@ -68,14 +80,22 @@ export interface ClientAuthentication {
* request bodies are supported.
*/
export abstract class OAuthClientAuthHandler {
private crypto: Crypto;
#crypto = createCrypto();
#clientAuthentication?: ClientAuthentication;
protected transporter: Gaxios;

/**
* Instantiates an OAuth client authentication handler.
* @param clientAuthentication The client auth credentials.
* @param options The OAuth Client Auth Handler instance options. Passing an `ClientAuthentication` directly is **@DEPRECATED**.
*/
constructor(private readonly clientAuthentication?: ClientAuthentication) {
this.crypto = createCrypto();
constructor(options?: ClientAuthentication | OAuthClientAuthHandlerOptions) {
if (options && 'clientId' in options) {
this.#clientAuthentication = options;
this.transporter = new Gaxios();
} else {
this.#clientAuthentication = options?.clientAuthentication;
this.transporter = options?.transporter || new Gaxios();
}
}

/**
Expand Down Expand Up @@ -117,11 +137,11 @@ export abstract class OAuthClientAuthHandler {
Object.assign(opts.headers, {
Authorization: `Bearer ${bearerToken}}`,
});
} else if (this.clientAuthentication?.confidentialClientType === 'basic') {
} else if (this.#clientAuthentication?.confidentialClientType === 'basic') {
opts.headers = opts.headers || {};
const clientId = this.clientAuthentication!.clientId;
const clientSecret = this.clientAuthentication!.clientSecret || '';
const base64EncodedCreds = this.crypto.encodeBase64StringUtf8(
const clientId = this.#clientAuthentication!.clientId;
const clientSecret = this.#clientAuthentication!.clientSecret || '';
const base64EncodedCreds = this.#crypto.encodeBase64StringUtf8(
`${clientId}:${clientSecret}`
);
Object.assign(opts.headers, {
Expand All @@ -138,7 +158,7 @@ export abstract class OAuthClientAuthHandler {
* depending on the client authentication mechanism to be used.
*/
private injectAuthenticatedRequestBody(opts: GaxiosOptions) {
if (this.clientAuthentication?.confidentialClientType === 'request-body') {
if (this.#clientAuthentication?.confidentialClientType === 'request-body') {
const method = (opts.method || 'GET').toUpperCase();
// Inject authenticated request body.
if (METHODS_SUPPORTING_REQUEST_BODY.indexOf(method) !== -1) {
Expand All @@ -155,27 +175,27 @@ export abstract class OAuthClientAuthHandler {
opts.data = opts.data || '';
const data = querystring.parse(opts.data);
Object.assign(data, {
client_id: this.clientAuthentication!.clientId,
client_secret: this.clientAuthentication!.clientSecret || '',
client_id: this.#clientAuthentication!.clientId,
client_secret: this.#clientAuthentication!.clientSecret || '',
});
opts.data = querystring.stringify(data);
} else if (contentType === 'application/json') {
opts.data = opts.data || {};
Object.assign(opts.data, {
client_id: this.clientAuthentication!.clientId,
client_secret: this.clientAuthentication!.clientSecret || '',
client_id: this.#clientAuthentication!.clientId,
client_secret: this.#clientAuthentication!.clientSecret || '',
});
} else {
throw new Error(
`${contentType} content-types are not supported with ` +
`${this.clientAuthentication!.confidentialClientType} ` +
`${this.#clientAuthentication!.confidentialClientType} ` +
'client authentication'
);
}
} else {
throw new Error(
`${method} HTTP method does not support ` +
`${this.clientAuthentication!.confidentialClientType} ` +
`${this.#clientAuthentication!.confidentialClientType} ` +
'client authentication'
);
}
Expand Down
46 changes: 35 additions & 11 deletions src/auth/stscredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {GaxiosError, GaxiosOptions, GaxiosResponse} from 'gaxios';
import {Gaxios, GaxiosError, GaxiosOptions, GaxiosResponse} from 'gaxios';

Check warning on line 15 in src/auth/stscredentials.ts

View workflow job for this annotation

GitHub Actions / lint

'Gaxios' is defined but never used
import * as querystring from 'querystring';

import {DefaultTransporter, Transporter} from '../transporters';
import {Headers} from './authclient';
import {
ClientAuthentication,
OAuthClientAuthHandler,
OAuthClientAuthHandlerOptions,
OAuthErrorResponse,
getErrorFromOAuthErrorResponse,
} from './oauth2common';
Expand Down Expand Up @@ -126,25 +125,50 @@ export interface StsSuccessfulResponse {
res?: GaxiosResponse | null;
}

export interface StsCredentialsConstructionOptions
extends OAuthClientAuthHandlerOptions {
/**
* The client authentication credentials if available.
*/
clientAuthentication?: ClientAuthentication;
/**
* The token exchange endpoint.
*/
tokenExchangeEndpoint: string | URL;
}

/**
* Implements the OAuth 2.0 token exchange based on
* https://tools.ietf.org/html/rfc8693
*/
export class StsCredentials extends OAuthClientAuthHandler {
private transporter: Transporter;
readonly #tokenExchangeEndpoint: string | URL;

/**
* Initializes an STS credentials instance.
* @param tokenExchangeEndpoint The token exchange endpoint.
* @param clientAuthentication The client authentication credentials if
* available.
*
* @param options The STS credentials instance options. Passing an `tokenExchangeEndpoint` directly is **@DEPRECATED**.
* @param clientAuthentication **@DEPRECATED**. Provide a {@link StsCredentialsConstructionOptions `StsCredentialsConstructionOptions`} object in the first parameter instead.
*/
constructor(
private readonly tokenExchangeEndpoint: string | URL,
options: StsCredentialsConstructionOptions | string | URL = {
tokenExchangeEndpoint: '',
},
/**
* @deprecated - provide a {@link StsCredentialsConstructionOptions `StsCredentialsConstructionOptions`} object in the first parameter instead
*/
clientAuthentication?: ClientAuthentication
) {
super(clientAuthentication);
this.transporter = new DefaultTransporter();
if (typeof options !== 'object' || options instanceof URL) {
options = {
tokenExchangeEndpoint: options,
clientAuthentication,
};
}

super(options);

this.#tokenExchangeEndpoint = options.tokenExchangeEndpoint;
}

/**
Expand Down Expand Up @@ -196,7 +220,7 @@ export class StsCredentials extends OAuthClientAuthHandler {

const opts: GaxiosOptions = {
...StsCredentials.RETRY_CONFIG,
url: this.tokenExchangeEndpoint.toString(),
url: this.#tokenExchangeEndpoint.toString(),
method: 'POST',
headers,
data: querystring.stringify(
Expand Down
Loading

0 comments on commit 8bdeec9

Please sign in to comment.