Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Unify Base AuthClient Options #1663

Merged
merged 31 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
17a0bb2
feat: Expose `Gaxios` instance and default
d-goog Oct 5, 2023
e46e52d
feat: Unify Base `AuthClient` Options
d-goog Oct 5, 2023
5a76083
docs: clean up documentation
d-goog Oct 5, 2023
4537900
chore: Discourage external use via `@internal`
d-goog Oct 5, 2023
2990ab4
refactor: minor
d-goog Oct 5, 2023
efb563e
refactor: clean up `transporter` interface, options, and docs
d-goog Oct 5, 2023
5d54c94
Merge branch 'main' into authclient-enhancements
d-goog Oct 6, 2023
33898c5
Merge branch 'main' of github.com:googleapis/google-auth-library-node…
d-goog Oct 9, 2023
0c9df61
Merge branch 'main' of github.com:googleapis/google-auth-library-node…
d-goog Oct 10, 2023
690b6c7
Merge branch 'main' of github.com:googleapis/google-auth-library-node…
d-goog Oct 10, 2023
9803cfe
test: Init tests for `AuthClient`
d-goog Oct 10, 2023
e126bf3
fix: Use entire `JWT` to create accurate `createScoped`
d-goog Oct 11, 2023
d8e817b
chore: update `typescript` in fixtures
d-goog Oct 11, 2023
fad54d5
test: Use Sinon Fake Timer to Avoid Flaky Time Issue (#1667)
d-goog Oct 11, 2023
c1e28fb
Merge branch 'main' into authclient-enhancements
d-goog Oct 12, 2023
ef6aa2e
Merge branch 'main' into authclient-enhancements
d-goog Oct 12, 2023
f950465
docs(sample): Improve `keepAlive` sample with `transporterOptions`
d-goog Oct 16, 2023
2cbc9c4
docs: Docs-deprecate `additionalOptions`
d-goog Oct 20, 2023
d09348f
refactor: un-alias `getOriginalOrCamel`
d-goog Oct 20, 2023
88edee3
chore: remove confusing duplicate interface
d-goog Oct 20, 2023
aca07c6
docs: nit
d-goog Oct 20, 2023
52ba511
Merge branch 'main' into authclient-enhancements
d-goog Oct 23, 2023
26d5300
Merge branch 'main' of github.com:googleapis/google-auth-library-node…
d-goog Oct 25, 2023
aaf3545
docs: Improve camelCased option documentation
d-goog Oct 25, 2023
cac4b73
refactor: Unify `OriginalAndCamel` & `SnakeToCamelObject`
d-goog Oct 25, 2023
9af953f
docs: Add issue tracking link
d-goog Oct 25, 2023
f4c1cc8
refactor: Replace `getOriginalOrCamel` with a cleaner API
d-goog Oct 26, 2023
6a54219
feat: Allow optional `obj`
d-goog Oct 26, 2023
364af4d
refactor: re-add `SnakeToCamelObject`
d-goog Oct 26, 2023
8ea14f0
refactor: dedupe interface for now
d-goog Oct 26, 2023
99e1204
feat: Add camelCase options for `IdentityPoolClient`
d-goog Oct 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 97 additions & 13 deletions src/auth/authclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,91 @@
// 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, getOriginalOrCamel as getOpt} 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)
*/
export interface CredentialsClient {
export interface AuthJSONOptions {
/**
* The project ID corresponding to the current credentials if available.
*/
projectId?: string | null;
project_id: string | null;

/**
* The expiration threshold in milliseconds before forcing token refresh.
* 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}
*/
eagerRefreshThresholdMillis: number;
quota_project_id: string;

/**
* Whether to force refresh on failure when making an authorization request.
* The default service domain for a given Cloud universe
*/
forceRefreshOnFailure: boolean;
universe_domain: string;
}

/**
* Base Auth Client configuration
*/
export type AuthClientOptions = Partial<
OriginalAndCamel<AuthJSONOptions> & {
aeitzman marked this conversation as resolved.
Show resolved Hide resolved
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 of
* unexpired tokens.
*/
eagerRefreshThresholdMillis: number;

/**
* 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;
}
>;

/**
* 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 +148,40 @@ 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();

// Shared auth options
this.projectId = getOpt(opts, 'project_id') ?? null;
danielbankhead marked this conversation as resolved.
Show resolved Hide resolved
this.quotaProjectId = getOpt(opts, 'quota_project_id');
this.credentials = getOpt(opts, 'credentials') ?? {};
this.universeDomain = getOpt(opts, 'universe_domain') ?? DEFAULT_UNIVERSE;

// Shared client options
this.transporter = opts.transporter ?? new DefaultTransporter();
aeitzman marked this conversation as resolved.
Show resolved Hide resolved

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
8 changes: 6 additions & 2 deletions src/auth/awsclient.ts
Original file line number Diff line number Diff line change
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 @@ -85,7 +86,10 @@ export class AwsClient extends BaseExternalAccountClient {
* 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
41 changes: 9 additions & 32 deletions src/auth/baseexternalclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import {
import * as stream from 'stream';

import {Credentials} from './credentials';
import {AuthClient} from './authclient';
import {AuthClient, AuthClientOptions} from './authclient';
import {BodyResponseCallback} from '../transporters';
import {GetAccessTokenResponse, Headers, RefreshOptions} from './oauth2client';
import {GetAccessTokenResponse, Headers} from './oauth2client';
import * as sts from './stscredentials';
import {ClientAuthentication} from './oauth2common';

Expand Down Expand Up @@ -63,18 +63,13 @@ const WORKFORCE_AUDIENCE_PATTERN =
const pkg = require('../../../package.json');

/**
* The default cloud universe
* For backwards compatibility.
*/
export const DEFAULT_UNIVERSE = 'googleapis.com';
export {DEFAULT_UNIVERSE} from './authclient';

export interface SharedExternalAccountClientOptions {
export interface SharedExternalAccountClientOptions extends AuthClientOptions {
audience: string;
token_url: string;
quota_project_id?: string;
/**
* universe domain is the default service domain for a given Cloud universe
*/
universe_domain?: string;
}

/**
Expand Down Expand Up @@ -151,11 +146,7 @@ export abstract class BaseExternalAccountClient extends AuthClient {
private readonly stsCredential: sts.StsCredentials;
private readonly clientAuth?: ClientAuthentication;
private readonly workforcePoolUserProject?: string;
public universeDomain = DEFAULT_UNIVERSE;
public projectId: string | null;
public projectNumber: string | null;
public readonly eagerRefreshThresholdMillis: number;
public readonly forceRefreshOnFailure: boolean;
private readonly configLifetimeRequested: boolean;
protected credentialSourceType?: string;
/**
Expand All @@ -169,9 +160,10 @@ export abstract class BaseExternalAccountClient extends AuthClient {
*/
constructor(
options: BaseExternalAccountClientOptions,
additionalOptions?: RefreshOptions
additionalOptions?: AuthClientOptions
aeitzman marked this conversation as resolved.
Show resolved Hide resolved
) {
super();
super({...options, ...additionalOptions});

if (options.type !== EXTERNAL_ACCOUNT_TYPE) {
throw new Error(
`Expected "${EXTERNAL_ACCOUNT_TYPE}" type but ` +
Expand All @@ -194,7 +186,6 @@ export abstract class BaseExternalAccountClient extends AuthClient {
this.cachedAccessToken = null;
this.audience = options.audience;
this.subjectTokenType = options.subject_token_type;
this.quotaProjectId = options.quota_project_id;
this.workforcePoolUserProject = options.workforce_pool_user_project;
const workforceAudiencePattern = new RegExp(WORKFORCE_AUDIENCE_PATTERN);
if (
Expand All @@ -217,22 +208,8 @@ export abstract class BaseExternalAccountClient extends AuthClient {
this.configLifetimeRequested = false;
this.serviceAccountImpersonationLifetime = DEFAULT_TOKEN_LIFESPAN;
}
// As threshold could be zero,
// eagerRefreshThresholdMillis || EXPIRATION_TIME_OFFSET will override the
// zero value.
if (typeof additionalOptions?.eagerRefreshThresholdMillis !== 'number') {
this.eagerRefreshThresholdMillis = EXPIRATION_TIME_OFFSET;
} else {
this.eagerRefreshThresholdMillis = additionalOptions!
.eagerRefreshThresholdMillis as number;
}
this.forceRefreshOnFailure = !!additionalOptions?.forceRefreshOnFailure;
this.projectId = null;
this.projectNumber = this.getProjectNumber(this.audience);

if (options.universe_domain) {
this.universeDomain = options.universe_domain;
}
this.projectNumber = this.getProjectNumber(this.audience);
}

/** The service account email to be impersonated, if available. */
Expand Down
8 changes: 6 additions & 2 deletions src/auth/computeclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ import {GaxiosError} from 'gaxios';
import * as gcpMetadata from 'gcp-metadata';

import {CredentialRequest, Credentials} from './credentials';
import {GetTokenResponse, OAuth2Client, RefreshOptions} from './oauth2client';
import {
GetTokenResponse,
OAuth2Client,
OAuth2ClientOptions,
} from './oauth2client';

export interface ComputeOptions extends RefreshOptions {
export interface ComputeOptions extends OAuth2ClientOptions {
/**
* The service account email to use, or 'default'. A Compute Engine instance
* may have multiple service accounts.
Expand Down
24 changes: 5 additions & 19 deletions src/auth/downscopedclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ import * as stream from 'stream';

import {BodyResponseCallback} from '../transporters';
import {Credentials} from './credentials';
import {AuthClient} from './authclient';
import {AuthClient, AuthClientOptions} from './authclient';

import {GetAccessTokenResponse, Headers, RefreshOptions} from './oauth2client';
import {GetAccessTokenResponse, Headers} from './oauth2client';
import * as sts from './stscredentials';

/**
Expand Down Expand Up @@ -107,8 +107,6 @@ interface AvailabilityCondition {
export class DownscopedClient extends AuthClient {
private cachedDownscopedAccessToken: CredentialsWithResponse | null;
private readonly stsCredential: sts.StsCredentials;
public readonly eagerRefreshThresholdMillis: number;
public readonly forceRefreshOnFailure: boolean;

/**
* Instantiates a downscoped client object using the provided source
Expand All @@ -126,18 +124,17 @@ export class DownscopedClient extends AuthClient {
* permissions that are available on that resource and an optional
* condition to further restrict permissions.
* @param additionalOptions Optional additional behavior customization
* options. These currently customize expiration threshold time and
* whether to retry on 401/403 API request errors.
* options.
* @param quotaProjectId Optional quota project id for setting up in the
* x-goog-user-project header.
*/
constructor(
private readonly authClient: AuthClient,
private readonly credentialAccessBoundary: CredentialAccessBoundary,
additionalOptions?: RefreshOptions,
additionalOptions?: AuthClientOptions,
quotaProjectId?: string
) {
super();
super({...additionalOptions, quotaProjectId});
// Check 1-10 Access Boundary Rules are defined within Credential Access
// Boundary.
if (
Expand Down Expand Up @@ -167,17 +164,6 @@ export class DownscopedClient extends AuthClient {

this.stsCredential = new sts.StsCredentials(STS_ACCESS_TOKEN_URL);
this.cachedDownscopedAccessToken = null;
// As threshold could be zero,
// eagerRefreshThresholdMillis || EXPIRATION_TIME_OFFSET will override the
// zero value.
if (typeof additionalOptions?.eagerRefreshThresholdMillis !== 'number') {
this.eagerRefreshThresholdMillis = EXPIRATION_TIME_OFFSET;
} else {
this.eagerRefreshThresholdMillis = additionalOptions!
.eagerRefreshThresholdMillis as number;
}
this.forceRefreshOnFailure = !!additionalOptions?.forceRefreshOnFailure;
this.quotaProjectId = quotaProjectId;
}

/**
Expand Down
10 changes: 4 additions & 6 deletions src/auth/externalAccountAuthorizedUserClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {AuthClient} from './authclient';
import {Headers, RefreshOptions} from './oauth2client';
import {AuthClient, AuthClientOptions} from './authclient';
import {Headers} from './oauth2client';
import {
ClientAuthentication,
getErrorFromOAuthErrorResponse,
Expand All @@ -30,7 +30,6 @@ import {
import {Credentials} from './credentials';
import * as stream from 'stream';
import {
DEFAULT_UNIVERSE,
EXPIRATION_TIME_OFFSET,
SharedExternalAccountClientOptions,
} from './baseexternalclient';
Expand Down Expand Up @@ -155,7 +154,6 @@ export class ExternalAccountAuthorizedUserClient extends AuthClient {
private cachedAccessToken: CredentialsWithResponse | null;
private readonly externalAccountAuthorizedUserHandler: ExternalAccountAuthorizedUserHandler;
private refreshToken: string;
public universeDomain = DEFAULT_UNIVERSE;

/**
* Instantiates an ExternalAccountAuthorizedUserClient instances using the
Expand All @@ -169,9 +167,9 @@ export class ExternalAccountAuthorizedUserClient extends AuthClient {
*/
constructor(
options: ExternalAccountAuthorizedUserClientOptions,
additionalOptions?: RefreshOptions
additionalOptions?: AuthClientOptions
) {
super();
super({...options, ...additionalOptions});
this.refreshToken = options.refresh_token;
const clientAuth = {
confidentialClientType: 'basic',
Expand Down
4 changes: 2 additions & 2 deletions src/auth/externalclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {RefreshOptions} from './oauth2client';
import {
BaseExternalAccountClient,
// This is the identifier in the JSON config for the type of credential.
Expand All @@ -33,6 +32,7 @@ import {
PluggableAuthClient,
PluggableAuthClientOptions,
} from './pluggable-auth-client';
import {AuthClientOptions} from './authclient';

export type ExternalAccountClientOptions =
| IdentityPoolClientOptions
Expand Down Expand Up @@ -68,7 +68,7 @@ export class ExternalAccountClient {
*/
static fromJSON(
options: ExternalAccountClientOptions,
additionalOptions?: RefreshOptions
additionalOptions?: AuthClientOptions
): BaseExternalAccountClient | null {
if (options && options.type === EXTERNAL_ACCOUNT_TYPE) {
if ((options as AwsClientOptions).credential_source?.environment_id) {
Expand Down
Loading
Loading