Skip to content

Commit

Permalink
feat: Unify Base AuthClient Options (#1663)
Browse files Browse the repository at this point in the history
* feat: Expose `Gaxios` instance and default

* feat: Unify Base `AuthClient` Options

* docs: clean up documentation

* chore: Discourage external use via `@internal`

* refactor: minor

* refactor: clean up `transporter` interface, options, and docs

* Merge branch 'main' of github.com:googleapis/google-auth-library-nodejs into authclient-enhancements

* Merge branch 'main' of github.com:googleapis/google-auth-library-nodejs into authclient-enhancements

* test: Init tests for `AuthClient`

* fix: Use entire `JWT` to create accurate `createScoped`

* chore: update `typescript` in fixtures

* test: Use Sinon Fake Timer to Avoid Flaky Time Issue (#1667)

* test: Use Sinon Fake Timer to Avoid Flaky Time Issue

* chore: change order for clarity

* docs(sample): Improve `keepAlive` sample with `transporterOptions`

* docs: Docs-deprecate `additionalOptions`

* refactor: un-alias `getOriginalOrCamel`

* chore: remove confusing duplicate interface

* docs: nit

* docs: Improve camelCased option documentation

We can refactor once microsoft/TypeScript#50715 has been resolved.

* refactor: Unify `OriginalAndCamel` & `SnakeToCamelObject`

+ make recursive

* docs: Add issue tracking link

* refactor: Replace `getOriginalOrCamel` with a cleaner API

* feat: Allow optional `obj`

* refactor: re-add `SnakeToCamelObject`

* refactor: dedupe interface for now

* feat: Add camelCase options for `IdentityPoolClient`
  • Loading branch information
danielbankhead committed Oct 26, 2023
1 parent edb9401 commit 5ac6705
Show file tree
Hide file tree
Showing 22 changed files with 566 additions and 218 deletions.
17 changes: 11 additions & 6 deletions samples/keepalive.js
Expand Up @@ -30,21 +30,26 @@ const https = require('https');
* Acquire a client, and make a request to an API that's enabled by default.
*/
async function main() {
// create a new agent with keepAlive enabled.
const agent = new https.Agent({keepAlive: true});

const auth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/cloud-platform',
clientOptions: {
transporterOptions: {
agent,
},
},
});
const client = await auth.getClient();
const projectId = await auth.getProjectId();
const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`;

// create a new agent with keepAlive enabled
const agent = new https.Agent({keepAlive: true});

// use the agent as an Axios config param to make the request
const res = await client.request({url, agent});
// the agent uses the provided agent.
const res = await client.request({url});
console.log(res.data);

// Re-use the same agent to make the next request over the same connection
// Can also use another agent per-request.
const res2 = await client.request({url, agent});
console.log(res2.data);
}
Expand Down
137 changes: 124 additions & 13 deletions src/auth/authclient.ts
Expand Up @@ -13,31 +13,116 @@
// limitations under the License.

import {EventEmitter} from 'events';
import {GaxiosOptions, GaxiosPromise, GaxiosResponse} from 'gaxios';
import {Gaxios, GaxiosOptions, GaxiosPromise, GaxiosResponse} from 'gaxios';

import {DefaultTransporter, Transporter} from '../transporters';
import {Credentials} from './credentials';
import {Headers} from './oauth2client';
import {OriginalAndCamel, originalOrCamelOptions} from '../util';

/**
* Defines the root interface for all clients that generate credentials
* for calling Google APIs. All clients should implement this interface.
* Base auth configurations (e.g. from JWT or `.json` files) with conventional
* camelCased options.
*
* @privateRemarks
*
* This interface is purposely not exported so that it can be removed once
* {@link https://github.com/microsoft/TypeScript/issues/50715} has been
* resolved. Then, we can use {@link OriginalAndCamel} to shrink this interface.
*
* Tracking: {@link https://github.com/googleapis/google-auth-library-nodejs/issues/1686}
*/
export interface CredentialsClient {
interface AuthJSONOptions {
/**
* The project ID corresponding to the current credentials if available.
*/
projectId?: string | null;
project_id: string | null;
/**
* An alias for {@link AuthJSONOptions.project_id `project_id`}.
*/
projectId: AuthJSONOptions['project_id'];

/**
* The quota project ID. The quota project can be used by client libraries for the billing purpose.
* See {@link https://cloud.google.com/docs/quota Working with quotas}
*/
quota_project_id: string;

/**
* An alias for {@link AuthJSONOptions.quota_project_id `quota_project_id`}.
*/
quotaProjectId: AuthJSONOptions['quota_project_id'];

/**
* The default service domain for a given Cloud universe.
*/
universe_domain: string;

/**
* An alias for {@link AuthJSONOptions.universe_domain `universe_domain`}.
*/
universeDomain: AuthJSONOptions['universe_domain'];
}

/**
* Base `AuthClient` configuration.
*
* The camelCased options are aliases of the snake_cased options, supporting both
* JSON API and JS conventions.
*/
export interface AuthClientOptions
extends Partial<OriginalAndCamel<AuthJSONOptions>> {
credentials?: Credentials;

/**
* A `Gaxios` or `Transporter` instance to use for `AuthClient` requests.
*/
transporter?: Gaxios | Transporter;

/**
* Provides default options to the transporter, such as {@link GaxiosOptions.agent `agent`} or
* {@link GaxiosOptions.retryConfig `retryConfig`}.
*/
transporterOptions?: GaxiosOptions;

/**
* The expiration threshold in milliseconds before forcing token refresh.
* The expiration threshold in milliseconds before forcing token refresh of
* unexpired tokens.
*/
eagerRefreshThresholdMillis: number;
eagerRefreshThresholdMillis?: number;

/**
* Whether to force refresh on failure when making an authorization request.
* Whether to attempt to refresh tokens on status 401/403 responses
* even if an attempt is made to refresh the token preemptively based
* on the expiry_date.
*/
forceRefreshOnFailure: boolean;
forceRefreshOnFailure?: boolean;
}

/**
* The default cloud universe
*
* @see {@link AuthJSONOptions.universe_domain}
*/
export const DEFAULT_UNIVERSE = 'googleapis.com';

/**
* The default {@link AuthClientOptions.eagerRefreshThresholdMillis}
*/
export const DEFAULT_EAGER_REFRESH_THRESHOLD_MILLIS = 5 * 60 * 1000;

/**
* Defines the root interface for all clients that generate credentials
* for calling Google APIs. All clients should implement this interface.
*/
export interface CredentialsClient {
projectId?: AuthClientOptions['projectId'];
eagerRefreshThresholdMillis: NonNullable<
AuthClientOptions['eagerRefreshThresholdMillis']
>;
forceRefreshOnFailure: NonNullable<
AuthClientOptions['forceRefreshOnFailure']
>;

/**
* @return A promise that resolves with the current GCP access token
Expand Down Expand Up @@ -88,16 +173,42 @@ export abstract class AuthClient
extends EventEmitter
implements CredentialsClient
{
projectId?: string | null;
/**
* The quota project ID. The quota project can be used by client libraries for the billing purpose.
* See {@link https://cloud.google.com/docs/quota| Working with quotas}
* See {@link https://cloud.google.com/docs/quota Working with quotas}
*/
quotaProjectId?: string;
transporter: Transporter = new DefaultTransporter();
transporter: Transporter;
credentials: Credentials = {};
projectId?: string | null;
eagerRefreshThresholdMillis = 5 * 60 * 1000;
eagerRefreshThresholdMillis = DEFAULT_EAGER_REFRESH_THRESHOLD_MILLIS;
forceRefreshOnFailure = false;
universeDomain = DEFAULT_UNIVERSE;

constructor(opts: AuthClientOptions = {}) {
super();

const options = originalOrCamelOptions(opts);

// Shared auth options
this.projectId = options.get('project_id') ?? null;
this.quotaProjectId = options.get('quota_project_id');
this.credentials = options.get('credentials') ?? {};
this.universeDomain = options.get('universe_domain') ?? DEFAULT_UNIVERSE;

// Shared client options
this.transporter = opts.transporter ?? new DefaultTransporter();

if (opts.transporterOptions) {
this.transporter.defaults = opts.transporterOptions;
}

if (opts.eagerRefreshThresholdMillis) {
this.eagerRefreshThresholdMillis = opts.eagerRefreshThresholdMillis;
}

this.forceRefreshOnFailure = opts.forceRefreshOnFailure ?? false;
}

/**
* Provides an alternative Gaxios request implementation with auth credentials
Expand Down
15 changes: 10 additions & 5 deletions src/auth/awsclient.ts
Expand Up @@ -19,7 +19,8 @@ import {
BaseExternalAccountClient,
BaseExternalAccountClientOptions,
} from './baseexternalclient';
import {RefreshOptions, Headers} from './oauth2client';
import {Headers} from './oauth2client';
import {AuthClientOptions} from './authclient';

/**
* AWS credentials JSON interface. This is used for AWS workloads.
Expand Down Expand Up @@ -81,11 +82,15 @@ export class AwsClient extends BaseExternalAccountClient {
* An error is thrown if the credential is not a valid AWS credential.
* @param options The external account options object typically loaded
* from the external account JSON credential file.
* @param additionalOptions Optional additional behavior customization
* options. These currently customize expiration threshold time and
* whether to retry on 401/403 API request errors.
* @param additionalOptions **DEPRECATED, all options are available in the
* `options` parameter.** Optional additional behavior customization options.
* These currently customize expiration threshold time and whether to retry
* on 401/403 API request errors.
*/
constructor(options: AwsClientOptions, additionalOptions?: RefreshOptions) {
constructor(
options: AwsClientOptions,
additionalOptions?: AuthClientOptions
) {
super(options, additionalOptions);
this.environmentId = options.credential_source.environment_id;
// This is only required if the AWS region is not available in the
Expand Down

0 comments on commit 5ac6705

Please sign in to comment.