Initial commit
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
/// <reference types="node" />
|
||||
import { EventEmitter } from 'events';
|
||||
import { GaxiosOptions, GaxiosPromise, GaxiosResponse } from 'gaxios';
|
||||
import { DefaultTransporter } from '../transporters';
|
||||
import { Credentials } from './credentials';
|
||||
import { Headers } from './oauth2client';
|
||||
/**
|
||||
* Defines the root interface for all clients that generate credentials
|
||||
* for calling Google APIs. All clients should implement this interface.
|
||||
*/
|
||||
export interface CredentialsClient {
|
||||
/**
|
||||
* The project ID corresponding to the current credentials if available.
|
||||
*/
|
||||
projectId?: string | null;
|
||||
/**
|
||||
* The expiration threshold in milliseconds before forcing token refresh.
|
||||
*/
|
||||
eagerRefreshThresholdMillis: number;
|
||||
/**
|
||||
* Whether to force refresh on failure when making an authorization request.
|
||||
*/
|
||||
forceRefreshOnFailure: boolean;
|
||||
/**
|
||||
* @return A promise that resolves with the current GCP access token
|
||||
* response. If the current credential is expired, a new one is retrieved.
|
||||
*/
|
||||
getAccessToken(): Promise<{
|
||||
token?: string | null;
|
||||
res?: GaxiosResponse | null;
|
||||
}>;
|
||||
/**
|
||||
* The main authentication interface. It takes an optional url which when
|
||||
* present is the endpoint being accessed, and returns a Promise which
|
||||
* resolves with authorization header fields.
|
||||
*
|
||||
* The result has the form:
|
||||
* { Authorization: 'Bearer <access_token_value>' }
|
||||
* @param url The URI being authorized.
|
||||
*/
|
||||
getRequestHeaders(url?: string): Promise<Headers>;
|
||||
/**
|
||||
* Provides an alternative Gaxios request implementation with auth credentials
|
||||
*/
|
||||
request<T>(opts: GaxiosOptions): GaxiosPromise<T>;
|
||||
/**
|
||||
* Sets the auth credentials.
|
||||
*/
|
||||
setCredentials(credentials: Credentials): void;
|
||||
/**
|
||||
* Subscribes a listener to the tokens event triggered when a token is
|
||||
* generated.
|
||||
*
|
||||
* @param event The tokens event to subscribe to.
|
||||
* @param listener The listener that triggers on event trigger.
|
||||
* @return The current client instance.
|
||||
*/
|
||||
on(event: 'tokens', listener: (tokens: Credentials) => void): this;
|
||||
}
|
||||
export declare interface AuthClient {
|
||||
on(event: 'tokens', listener: (tokens: Credentials) => void): this;
|
||||
}
|
||||
export declare abstract class AuthClient extends EventEmitter implements CredentialsClient {
|
||||
protected quotaProjectId?: string;
|
||||
transporter: DefaultTransporter;
|
||||
credentials: Credentials;
|
||||
projectId?: string | null;
|
||||
eagerRefreshThresholdMillis: number;
|
||||
forceRefreshOnFailure: boolean;
|
||||
/**
|
||||
* Provides an alternative Gaxios request implementation with auth credentials
|
||||
*/
|
||||
abstract request<T>(opts: GaxiosOptions): GaxiosPromise<T>;
|
||||
/**
|
||||
* The main authentication interface. It takes an optional url which when
|
||||
* present is the endpoint being accessed, and returns a Promise which
|
||||
* resolves with authorization header fields.
|
||||
*
|
||||
* The result has the form:
|
||||
* { Authorization: 'Bearer <access_token_value>' }
|
||||
* @param url The URI being authorized.
|
||||
*/
|
||||
abstract getRequestHeaders(url?: string): Promise<Headers>;
|
||||
/**
|
||||
* @return A promise that resolves with the current GCP access token
|
||||
* response. If the current credential is expired, a new one is retrieved.
|
||||
*/
|
||||
abstract getAccessToken(): Promise<{
|
||||
token?: string | null;
|
||||
res?: GaxiosResponse | null;
|
||||
}>;
|
||||
/**
|
||||
* Sets the auth credentials.
|
||||
*/
|
||||
setCredentials(credentials: Credentials): void;
|
||||
/**
|
||||
* Append additional headers, e.g., x-goog-user-project, shared across the
|
||||
* classes inheriting AuthClient. This method should be used by any method
|
||||
* that overrides getRequestMetadataAsync(), which is a shared helper for
|
||||
* setting request information in both gRPC and HTTP API calls.
|
||||
*
|
||||
* @param headers objedcdt to append additional headers to.
|
||||
*/
|
||||
protected addSharedMetadataHeaders(headers: Headers): Headers;
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
// Copyright 2012 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthClient = void 0;
|
||||
const events_1 = require("events");
|
||||
const transporters_1 = require("../transporters");
|
||||
class AuthClient extends events_1.EventEmitter {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.transporter = new transporters_1.DefaultTransporter();
|
||||
this.credentials = {};
|
||||
this.eagerRefreshThresholdMillis = 5 * 60 * 1000;
|
||||
this.forceRefreshOnFailure = false;
|
||||
}
|
||||
/**
|
||||
* Sets the auth credentials.
|
||||
*/
|
||||
setCredentials(credentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
/**
|
||||
* Append additional headers, e.g., x-goog-user-project, shared across the
|
||||
* classes inheriting AuthClient. This method should be used by any method
|
||||
* that overrides getRequestMetadataAsync(), which is a shared helper for
|
||||
* setting request information in both gRPC and HTTP API calls.
|
||||
*
|
||||
* @param headers objedcdt to append additional headers to.
|
||||
*/
|
||||
addSharedMetadataHeaders(headers) {
|
||||
// quota_project_id, stored in application_default_credentials.json, is set in
|
||||
// the x-goog-user-project header, to indicate an alternate account for
|
||||
// billing and quota:
|
||||
if (!headers['x-goog-user-project'] && // don't override a value the user sets.
|
||||
this.quotaProjectId) {
|
||||
headers['x-goog-user-project'] = this.quotaProjectId;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
exports.AuthClient = AuthClient;
|
||||
//# sourceMappingURL=authclient.js.map
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { BaseExternalAccountClient, BaseExternalAccountClientOptions } from './baseexternalclient';
|
||||
import { RefreshOptions } from './oauth2client';
|
||||
/**
|
||||
* AWS credentials JSON interface. This is used for AWS workloads.
|
||||
*/
|
||||
export interface AwsClientOptions extends BaseExternalAccountClientOptions {
|
||||
credential_source: {
|
||||
environment_id: string;
|
||||
region_url?: string;
|
||||
url?: string;
|
||||
regional_cred_verification_url: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* AWS external account client. This is used for AWS workloads, where
|
||||
* AWS STS GetCallerIdentity serialized signed requests are exchanged for
|
||||
* GCP access token.
|
||||
*/
|
||||
export declare class AwsClient extends BaseExternalAccountClient {
|
||||
private readonly environmentId;
|
||||
private readonly regionUrl?;
|
||||
private readonly securityCredentialsUrl?;
|
||||
private readonly regionalCredVerificationUrl;
|
||||
private awsRequestSigner;
|
||||
private region;
|
||||
/**
|
||||
* Instantiates an AwsClient instance using the provided JSON
|
||||
* object loaded from an external account credentials file.
|
||||
* 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.
|
||||
*/
|
||||
constructor(options: AwsClientOptions, additionalOptions?: RefreshOptions);
|
||||
/**
|
||||
* Triggered when an external subject token is needed to be exchanged for a
|
||||
* GCP access token via GCP STS endpoint.
|
||||
* This uses the `options.credential_source` object to figure out how
|
||||
* to retrieve the token using the current environment. In this case,
|
||||
* this uses a serialized AWS signed request to the STS GetCallerIdentity
|
||||
* endpoint.
|
||||
* The logic is summarized as:
|
||||
* 1. Retrieve AWS region from availability-zone.
|
||||
* 2a. Check AWS credentials in environment variables. If not found, get
|
||||
* from security-credentials endpoint.
|
||||
* 2b. Get AWS credentials from security-credentials endpoint. In order
|
||||
* to retrieve this, the AWS role needs to be determined by calling
|
||||
* security-credentials endpoint without any argument. Then the
|
||||
* credentials can be retrieved via: security-credentials/role_name
|
||||
* 3. Generate the signed request to AWS STS GetCallerIdentity action.
|
||||
* 4. Inject x-goog-cloud-target-resource into header and serialize the
|
||||
* signed request. This will be the subject-token to pass to GCP STS.
|
||||
* @return A promise that resolves with the external subject token.
|
||||
*/
|
||||
retrieveSubjectToken(): Promise<string>;
|
||||
/**
|
||||
* @return A promise that resolves with the current AWS region.
|
||||
*/
|
||||
private getAwsRegion;
|
||||
/**
|
||||
* @return A promise that resolves with the assigned role to the current
|
||||
* AWS VM. This is needed for calling the security-credentials endpoint.
|
||||
*/
|
||||
private getAwsRoleName;
|
||||
/**
|
||||
* Retrieves the temporary AWS credentials by calling the security-credentials
|
||||
* endpoint as specified in the `credential_source` object.
|
||||
* @param roleName The role attached to the current VM.
|
||||
* @return A promise that resolves with the temporary AWS credentials
|
||||
* needed for creating the GetCallerIdentity signed request.
|
||||
*/
|
||||
private getAwsSecurityCredentials;
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
"use strict";
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AwsClient = void 0;
|
||||
const awsrequestsigner_1 = require("./awsrequestsigner");
|
||||
const baseexternalclient_1 = require("./baseexternalclient");
|
||||
/**
|
||||
* AWS external account client. This is used for AWS workloads, where
|
||||
* AWS STS GetCallerIdentity serialized signed requests are exchanged for
|
||||
* GCP access token.
|
||||
*/
|
||||
class AwsClient extends baseexternalclient_1.BaseExternalAccountClient {
|
||||
/**
|
||||
* Instantiates an AwsClient instance using the provided JSON
|
||||
* object loaded from an external account credentials file.
|
||||
* 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.
|
||||
*/
|
||||
constructor(options, additionalOptions) {
|
||||
var _a;
|
||||
super(options, additionalOptions);
|
||||
this.environmentId = options.credential_source.environment_id;
|
||||
// This is only required if the AWS region is not available in the
|
||||
// AWS_REGION or AWS_DEFAULT_REGION environment variables.
|
||||
this.regionUrl = options.credential_source.region_url;
|
||||
// This is only required if AWS security credentials are not available in
|
||||
// environment variables.
|
||||
this.securityCredentialsUrl = options.credential_source.url;
|
||||
this.regionalCredVerificationUrl =
|
||||
options.credential_source.regional_cred_verification_url;
|
||||
const match = (_a = this.environmentId) === null || _a === void 0 ? void 0 : _a.match(/^(aws)(\d+)$/);
|
||||
if (!match || !this.regionalCredVerificationUrl) {
|
||||
throw new Error('No valid AWS "credential_source" provided');
|
||||
}
|
||||
else if (parseInt(match[2], 10) !== 1) {
|
||||
throw new Error(`aws version "${match[2]}" is not supported in the current build.`);
|
||||
}
|
||||
this.awsRequestSigner = null;
|
||||
this.region = '';
|
||||
}
|
||||
/**
|
||||
* Triggered when an external subject token is needed to be exchanged for a
|
||||
* GCP access token via GCP STS endpoint.
|
||||
* This uses the `options.credential_source` object to figure out how
|
||||
* to retrieve the token using the current environment. In this case,
|
||||
* this uses a serialized AWS signed request to the STS GetCallerIdentity
|
||||
* endpoint.
|
||||
* The logic is summarized as:
|
||||
* 1. Retrieve AWS region from availability-zone.
|
||||
* 2a. Check AWS credentials in environment variables. If not found, get
|
||||
* from security-credentials endpoint.
|
||||
* 2b. Get AWS credentials from security-credentials endpoint. In order
|
||||
* to retrieve this, the AWS role needs to be determined by calling
|
||||
* security-credentials endpoint without any argument. Then the
|
||||
* credentials can be retrieved via: security-credentials/role_name
|
||||
* 3. Generate the signed request to AWS STS GetCallerIdentity action.
|
||||
* 4. Inject x-goog-cloud-target-resource into header and serialize the
|
||||
* signed request. This will be the subject-token to pass to GCP STS.
|
||||
* @return A promise that resolves with the external subject token.
|
||||
*/
|
||||
async retrieveSubjectToken() {
|
||||
// Initialize AWS request signer if not already initialized.
|
||||
if (!this.awsRequestSigner) {
|
||||
this.region = await this.getAwsRegion();
|
||||
this.awsRequestSigner = new awsrequestsigner_1.AwsRequestSigner(async () => {
|
||||
// Check environment variables for permanent credentials first.
|
||||
// https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
|
||||
if (process.env['AWS_ACCESS_KEY_ID'] &&
|
||||
process.env['AWS_SECRET_ACCESS_KEY']) {
|
||||
return {
|
||||
accessKeyId: process.env['AWS_ACCESS_KEY_ID'],
|
||||
secretAccessKey: process.env['AWS_SECRET_ACCESS_KEY'],
|
||||
// This is normally not available for permanent credentials.
|
||||
token: process.env['AWS_SESSION_TOKEN'],
|
||||
};
|
||||
}
|
||||
// Since the role on a VM can change, we don't need to cache it.
|
||||
const roleName = await this.getAwsRoleName();
|
||||
// Temporary credentials typically last for several hours.
|
||||
// Expiration is returned in response.
|
||||
// Consider future optimization of this logic to cache AWS tokens
|
||||
// until their natural expiration.
|
||||
const awsCreds = await this.getAwsSecurityCredentials(roleName);
|
||||
return {
|
||||
accessKeyId: awsCreds.AccessKeyId,
|
||||
secretAccessKey: awsCreds.SecretAccessKey,
|
||||
token: awsCreds.Token,
|
||||
};
|
||||
}, this.region);
|
||||
}
|
||||
// Generate signed request to AWS STS GetCallerIdentity API.
|
||||
// Use the required regional endpoint. Otherwise, the request will fail.
|
||||
const options = await this.awsRequestSigner.getRequestOptions({
|
||||
url: this.regionalCredVerificationUrl.replace('{region}', this.region),
|
||||
method: 'POST',
|
||||
});
|
||||
// The GCP STS endpoint expects the headers to be formatted as:
|
||||
// [
|
||||
// {key: 'x-amz-date', value: '...'},
|
||||
// {key: 'Authorization', value: '...'},
|
||||
// ...
|
||||
// ]
|
||||
// And then serialized as:
|
||||
// encodeURIComponent(JSON.stringify({
|
||||
// url: '...',
|
||||
// method: 'POST',
|
||||
// headers: [{key: 'x-amz-date', value: '...'}, ...]
|
||||
// }))
|
||||
const reformattedHeader = [];
|
||||
const extendedHeaders = Object.assign({
|
||||
// The full, canonical resource name of the workload identity pool
|
||||
// provider, with or without the HTTPS prefix.
|
||||
// Including this header as part of the signature is recommended to
|
||||
// ensure data integrity.
|
||||
'x-goog-cloud-target-resource': this.audience,
|
||||
}, options.headers);
|
||||
// Reformat header to GCP STS expected format.
|
||||
for (const key in extendedHeaders) {
|
||||
reformattedHeader.push({
|
||||
key,
|
||||
value: extendedHeaders[key],
|
||||
});
|
||||
}
|
||||
// Serialize the reformatted signed request.
|
||||
return encodeURIComponent(JSON.stringify({
|
||||
url: options.url,
|
||||
method: options.method,
|
||||
headers: reformattedHeader,
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* @return A promise that resolves with the current AWS region.
|
||||
*/
|
||||
async getAwsRegion() {
|
||||
// Priority order for region determination:
|
||||
// AWS_REGION > AWS_DEFAULT_REGION > metadata server.
|
||||
if (process.env['AWS_REGION'] || process.env['AWS_DEFAULT_REGION']) {
|
||||
return (process.env['AWS_REGION'] || process.env['AWS_DEFAULT_REGION']);
|
||||
}
|
||||
if (!this.regionUrl) {
|
||||
throw new Error('Unable to determine AWS region due to missing ' +
|
||||
'"options.credential_source.region_url"');
|
||||
}
|
||||
const opts = {
|
||||
url: this.regionUrl,
|
||||
method: 'GET',
|
||||
responseType: 'text',
|
||||
};
|
||||
const response = await this.transporter.request(opts);
|
||||
// Remove last character. For example, if us-east-2b is returned,
|
||||
// the region would be us-east-2.
|
||||
return response.data.substr(0, response.data.length - 1);
|
||||
}
|
||||
/**
|
||||
* @return A promise that resolves with the assigned role to the current
|
||||
* AWS VM. This is needed for calling the security-credentials endpoint.
|
||||
*/
|
||||
async getAwsRoleName() {
|
||||
if (!this.securityCredentialsUrl) {
|
||||
throw new Error('Unable to determine AWS role name due to missing ' +
|
||||
'"options.credential_source.url"');
|
||||
}
|
||||
const opts = {
|
||||
url: this.securityCredentialsUrl,
|
||||
method: 'GET',
|
||||
responseType: 'text',
|
||||
};
|
||||
const response = await this.transporter.request(opts);
|
||||
return response.data;
|
||||
}
|
||||
/**
|
||||
* Retrieves the temporary AWS credentials by calling the security-credentials
|
||||
* endpoint as specified in the `credential_source` object.
|
||||
* @param roleName The role attached to the current VM.
|
||||
* @return A promise that resolves with the temporary AWS credentials
|
||||
* needed for creating the GetCallerIdentity signed request.
|
||||
*/
|
||||
async getAwsSecurityCredentials(roleName) {
|
||||
const response = await this.transporter.request({
|
||||
url: `${this.securityCredentialsUrl}/${roleName}`,
|
||||
responseType: 'json',
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
exports.AwsClient = AwsClient;
|
||||
//# sourceMappingURL=awsclient.js.map
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { GaxiosOptions } from 'gaxios';
|
||||
/**
|
||||
* Interface defining AWS security credentials.
|
||||
* These are either determined from AWS security_credentials endpoint or
|
||||
* AWS environment variables.
|
||||
*/
|
||||
interface AwsSecurityCredentials {
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
token?: string;
|
||||
}
|
||||
/**
|
||||
* Implements an AWS API request signer based on the AWS Signature Version 4
|
||||
* signing process.
|
||||
* https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
||||
*/
|
||||
export declare class AwsRequestSigner {
|
||||
private readonly getCredentials;
|
||||
private readonly region;
|
||||
private readonly crypto;
|
||||
/**
|
||||
* Instantiates an AWS API request signer used to send authenticated signed
|
||||
* requests to AWS APIs based on the AWS Signature Version 4 signing process.
|
||||
* This also provides a mechanism to generate the signed request without
|
||||
* sending it.
|
||||
* @param getCredentials A mechanism to retrieve AWS security credentials
|
||||
* when needed.
|
||||
* @param region The AWS region to use.
|
||||
*/
|
||||
constructor(getCredentials: () => Promise<AwsSecurityCredentials>, region: string);
|
||||
/**
|
||||
* Generates the signed request for the provided HTTP request for calling
|
||||
* an AWS API. This follows the steps described at:
|
||||
* https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
|
||||
* @param amzOptions The AWS request options that need to be signed.
|
||||
* @return A promise that resolves with the GaxiosOptions containing the
|
||||
* signed HTTP request parameters.
|
||||
*/
|
||||
getRequestOptions(amzOptions: GaxiosOptions): Promise<GaxiosOptions>;
|
||||
}
|
||||
export {};
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
"use strict";
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AwsRequestSigner = void 0;
|
||||
const crypto_1 = require("../crypto/crypto");
|
||||
/** AWS Signature Version 4 signing algorithm identifier. */
|
||||
const AWS_ALGORITHM = 'AWS4-HMAC-SHA256';
|
||||
/**
|
||||
* The termination string for the AWS credential scope value as defined in
|
||||
* https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
||||
*/
|
||||
const AWS_REQUEST_TYPE = 'aws4_request';
|
||||
/**
|
||||
* Implements an AWS API request signer based on the AWS Signature Version 4
|
||||
* signing process.
|
||||
* https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
||||
*/
|
||||
class AwsRequestSigner {
|
||||
/**
|
||||
* Instantiates an AWS API request signer used to send authenticated signed
|
||||
* requests to AWS APIs based on the AWS Signature Version 4 signing process.
|
||||
* This also provides a mechanism to generate the signed request without
|
||||
* sending it.
|
||||
* @param getCredentials A mechanism to retrieve AWS security credentials
|
||||
* when needed.
|
||||
* @param region The AWS region to use.
|
||||
*/
|
||||
constructor(getCredentials, region) {
|
||||
this.getCredentials = getCredentials;
|
||||
this.region = region;
|
||||
this.crypto = crypto_1.createCrypto();
|
||||
}
|
||||
/**
|
||||
* Generates the signed request for the provided HTTP request for calling
|
||||
* an AWS API. This follows the steps described at:
|
||||
* https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
|
||||
* @param amzOptions The AWS request options that need to be signed.
|
||||
* @return A promise that resolves with the GaxiosOptions containing the
|
||||
* signed HTTP request parameters.
|
||||
*/
|
||||
async getRequestOptions(amzOptions) {
|
||||
if (!amzOptions.url) {
|
||||
throw new Error('"url" is required in "amzOptions"');
|
||||
}
|
||||
// Stringify JSON requests. This will be set in the request body of the
|
||||
// generated signed request.
|
||||
const requestPayloadData = typeof amzOptions.data === 'object'
|
||||
? JSON.stringify(amzOptions.data)
|
||||
: amzOptions.data;
|
||||
const url = amzOptions.url;
|
||||
const method = amzOptions.method || 'GET';
|
||||
const requestPayload = amzOptions.body || requestPayloadData;
|
||||
const additionalAmzHeaders = amzOptions.headers;
|
||||
const awsSecurityCredentials = await this.getCredentials();
|
||||
const uri = new URL(url);
|
||||
const headerMap = await generateAuthenticationHeaderMap({
|
||||
crypto: this.crypto,
|
||||
host: uri.host,
|
||||
canonicalUri: uri.pathname,
|
||||
canonicalQuerystring: uri.search.substr(1),
|
||||
method,
|
||||
region: this.region,
|
||||
securityCredentials: awsSecurityCredentials,
|
||||
requestPayload,
|
||||
additionalAmzHeaders,
|
||||
});
|
||||
// Append additional optional headers, eg. X-Amz-Target, Content-Type, etc.
|
||||
const headers = Object.assign(
|
||||
// Add x-amz-date if available.
|
||||
headerMap.amzDate ? { 'x-amz-date': headerMap.amzDate } : {}, {
|
||||
Authorization: headerMap.authorizationHeader,
|
||||
host: uri.host,
|
||||
}, additionalAmzHeaders || {});
|
||||
if (awsSecurityCredentials.token) {
|
||||
Object.assign(headers, {
|
||||
'x-amz-security-token': awsSecurityCredentials.token,
|
||||
});
|
||||
}
|
||||
const awsSignedReq = {
|
||||
url,
|
||||
method: method,
|
||||
headers,
|
||||
};
|
||||
if (typeof requestPayload !== 'undefined') {
|
||||
awsSignedReq.body = requestPayload;
|
||||
}
|
||||
return awsSignedReq;
|
||||
}
|
||||
}
|
||||
exports.AwsRequestSigner = AwsRequestSigner;
|
||||
/**
|
||||
* Creates the HMAC-SHA256 hash of the provided message using the
|
||||
* provided key.
|
||||
*
|
||||
* @param crypto The crypto instance used to facilitate cryptographic
|
||||
* operations.
|
||||
* @param key The HMAC-SHA256 key to use.
|
||||
* @param msg The message to hash.
|
||||
* @return The computed hash bytes.
|
||||
*/
|
||||
async function sign(crypto, key, msg) {
|
||||
return await crypto.signWithHmacSha256(key, msg);
|
||||
}
|
||||
/**
|
||||
* Calculates the signing key used to calculate the signature for
|
||||
* AWS Signature Version 4 based on:
|
||||
* https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
|
||||
*
|
||||
* @param crypto The crypto instance used to facilitate cryptographic
|
||||
* operations.
|
||||
* @param key The AWS secret access key.
|
||||
* @param dateStamp The '%Y%m%d' date format.
|
||||
* @param region The AWS region.
|
||||
* @param serviceName The AWS service name, eg. sts.
|
||||
* @return The signing key bytes.
|
||||
*/
|
||||
async function getSigningKey(crypto, key, dateStamp, region, serviceName) {
|
||||
const kDate = await sign(crypto, `AWS4${key}`, dateStamp);
|
||||
const kRegion = await sign(crypto, kDate, region);
|
||||
const kService = await sign(crypto, kRegion, serviceName);
|
||||
const kSigning = await sign(crypto, kService, 'aws4_request');
|
||||
return kSigning;
|
||||
}
|
||||
/**
|
||||
* Generates the authentication header map needed for generating the AWS
|
||||
* Signature Version 4 signed request.
|
||||
*
|
||||
* @param option The options needed to compute the authentication header map.
|
||||
* @return The AWS authentication header map which constitutes of the following
|
||||
* components: amz-date, authorization header and canonical query string.
|
||||
*/
|
||||
async function generateAuthenticationHeaderMap(options) {
|
||||
const additionalAmzHeaders = options.additionalAmzHeaders || {};
|
||||
const requestPayload = options.requestPayload || '';
|
||||
// iam.amazonaws.com host => iam service.
|
||||
// sts.us-east-2.amazonaws.com => sts service.
|
||||
const serviceName = options.host.split('.')[0];
|
||||
const now = new Date();
|
||||
// Format: '%Y%m%dT%H%M%SZ'.
|
||||
const amzDate = now
|
||||
.toISOString()
|
||||
.replace(/[-:]/g, '')
|
||||
.replace(/\.[0-9]+/, '');
|
||||
// Format: '%Y%m%d'.
|
||||
const dateStamp = now.toISOString().replace(/[-]/g, '').replace(/T.*/, '');
|
||||
// Change all additional headers to be lower case.
|
||||
const reformattedAdditionalAmzHeaders = {};
|
||||
Object.keys(additionalAmzHeaders).forEach(key => {
|
||||
reformattedAdditionalAmzHeaders[key.toLowerCase()] =
|
||||
additionalAmzHeaders[key];
|
||||
});
|
||||
// Add AWS token if available.
|
||||
if (options.securityCredentials.token) {
|
||||
reformattedAdditionalAmzHeaders['x-amz-security-token'] =
|
||||
options.securityCredentials.token;
|
||||
}
|
||||
// Header keys need to be sorted alphabetically.
|
||||
const amzHeaders = Object.assign({
|
||||
host: options.host,
|
||||
},
|
||||
// Previously the date was not fixed with x-amz- and could be provided manually.
|
||||
// https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req
|
||||
reformattedAdditionalAmzHeaders.date ? {} : { 'x-amz-date': amzDate }, reformattedAdditionalAmzHeaders);
|
||||
let canonicalHeaders = '';
|
||||
const signedHeadersList = Object.keys(amzHeaders).sort();
|
||||
signedHeadersList.forEach(key => {
|
||||
canonicalHeaders += `${key}:${amzHeaders[key]}\n`;
|
||||
});
|
||||
const signedHeaders = signedHeadersList.join(';');
|
||||
const payloadHash = await options.crypto.sha256DigestHex(requestPayload);
|
||||
// https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||
const canonicalRequest = `${options.method}\n` +
|
||||
`${options.canonicalUri}\n` +
|
||||
`${options.canonicalQuerystring}\n` +
|
||||
`${canonicalHeaders}\n` +
|
||||
`${signedHeaders}\n` +
|
||||
`${payloadHash}`;
|
||||
const credentialScope = `${dateStamp}/${options.region}/${serviceName}/${AWS_REQUEST_TYPE}`;
|
||||
// https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
||||
const stringToSign = `${AWS_ALGORITHM}\n` +
|
||||
`${amzDate}\n` +
|
||||
`${credentialScope}\n` +
|
||||
(await options.crypto.sha256DigestHex(canonicalRequest));
|
||||
// https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
|
||||
const signingKey = await getSigningKey(options.crypto, options.securityCredentials.secretAccessKey, dateStamp, options.region, serviceName);
|
||||
const signature = await sign(options.crypto, signingKey, stringToSign);
|
||||
// https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
|
||||
const authorizationHeader = `${AWS_ALGORITHM} Credential=${options.securityCredentials.accessKeyId}/` +
|
||||
`${credentialScope}, SignedHeaders=${signedHeaders}, ` +
|
||||
`Signature=${crypto_1.fromArrayBufferToHex(signature)}`;
|
||||
return {
|
||||
// Do not return x-amz-date if date is available.
|
||||
amzDate: reformattedAdditionalAmzHeaders.date ? undefined : amzDate,
|
||||
authorizationHeader,
|
||||
canonicalQuerystring: options.canonicalQuerystring,
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=awsrequestsigner.js.map
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
import { GaxiosOptions, GaxiosPromise, GaxiosResponse } from 'gaxios';
|
||||
import { Credentials } from './credentials';
|
||||
import { AuthClient } from './authclient';
|
||||
import { BodyResponseCallback } from '../transporters';
|
||||
import { GetAccessTokenResponse, Headers, RefreshOptions } from './oauth2client';
|
||||
/**
|
||||
* Offset to take into account network delays and server clock skews.
|
||||
*/
|
||||
export declare const EXPIRATION_TIME_OFFSET: number;
|
||||
/**
|
||||
* The credentials JSON file type for external account clients.
|
||||
* There are 3 types of JSON configs:
|
||||
* 1. authorized_user => Google end user credential
|
||||
* 2. service_account => Google service account credential
|
||||
* 3. external_Account => non-GCP service (eg. AWS, Azure, K8s)
|
||||
*/
|
||||
export declare const EXTERNAL_ACCOUNT_TYPE = "external_account";
|
||||
/** Cloud resource manager URL used to retrieve project information. */
|
||||
export declare const CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/";
|
||||
/**
|
||||
* Base external account credentials json interface.
|
||||
*/
|
||||
export interface BaseExternalAccountClientOptions {
|
||||
type: string;
|
||||
audience: string;
|
||||
subject_token_type: string;
|
||||
service_account_impersonation_url?: string;
|
||||
token_url: string;
|
||||
token_info_url?: string;
|
||||
client_id?: string;
|
||||
client_secret?: string;
|
||||
quota_project_id?: string;
|
||||
workforce_pool_user_project?: string;
|
||||
}
|
||||
/**
|
||||
* Interface defining the successful response for iamcredentials
|
||||
* generateAccessToken API.
|
||||
* https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
|
||||
*/
|
||||
export interface IamGenerateAccessTokenResponse {
|
||||
accessToken: string;
|
||||
expireTime: string;
|
||||
}
|
||||
/**
|
||||
* Interface defining the project information response returned by the cloud
|
||||
* resource manager.
|
||||
* https://cloud.google.com/resource-manager/reference/rest/v1/projects#Project
|
||||
*/
|
||||
export interface ProjectInfo {
|
||||
projectNumber: string;
|
||||
projectId: string;
|
||||
lifecycleState: string;
|
||||
name: string;
|
||||
createTime?: string;
|
||||
parent: {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Internal interface for tracking the access token expiration time.
|
||||
*/
|
||||
interface CredentialsWithResponse extends Credentials {
|
||||
res?: GaxiosResponse | null;
|
||||
}
|
||||
/**
|
||||
* Base external account client. This is used to instantiate AuthClients for
|
||||
* exchanging external account credentials for GCP access token and authorizing
|
||||
* requests to GCP APIs.
|
||||
* The base class implements common logic for exchanging various type of
|
||||
* external credentials for GCP access token. The logic of determining and
|
||||
* retrieving the external credential based on the environment and
|
||||
* credential_source will be left for the subclasses.
|
||||
*/
|
||||
export declare abstract class BaseExternalAccountClient extends AuthClient {
|
||||
/**
|
||||
* OAuth scopes for the GCP access token to use. When not provided,
|
||||
* the default https://www.googleapis.com/auth/cloud-platform is
|
||||
* used.
|
||||
*/
|
||||
scopes?: string | string[];
|
||||
private cachedAccessToken;
|
||||
protected readonly audience: string;
|
||||
private readonly subjectTokenType;
|
||||
private readonly serviceAccountImpersonationUrl?;
|
||||
private readonly stsCredential;
|
||||
private readonly clientAuth?;
|
||||
private readonly workforcePoolUserProject?;
|
||||
projectId: string | null;
|
||||
projectNumber: string | null;
|
||||
readonly eagerRefreshThresholdMillis: number;
|
||||
readonly forceRefreshOnFailure: boolean;
|
||||
/**
|
||||
* Instantiate a BaseExternalAccountClient instance using the provided JSON
|
||||
* object loaded from an external account credentials file.
|
||||
* @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.
|
||||
*/
|
||||
constructor(options: BaseExternalAccountClientOptions, additionalOptions?: RefreshOptions);
|
||||
/** The service account email to be impersonated, if available. */
|
||||
getServiceAccountEmail(): string | null;
|
||||
/**
|
||||
* Provides a mechanism to inject GCP access tokens directly.
|
||||
* When the provided credential expires, a new credential, using the
|
||||
* external account options, is retrieved.
|
||||
* @param credentials The Credentials object to set on the current client.
|
||||
*/
|
||||
setCredentials(credentials: Credentials): void;
|
||||
/**
|
||||
* Triggered when a external subject token is needed to be exchanged for a GCP
|
||||
* access token via GCP STS endpoint.
|
||||
* This abstract method needs to be implemented by subclasses depending on
|
||||
* the type of external credential used.
|
||||
* @return A promise that resolves with the external subject token.
|
||||
*/
|
||||
abstract retrieveSubjectToken(): Promise<string>;
|
||||
/**
|
||||
* @return A promise that resolves with the current GCP access token
|
||||
* response. If the current credential is expired, a new one is retrieved.
|
||||
*/
|
||||
getAccessToken(): Promise<GetAccessTokenResponse>;
|
||||
/**
|
||||
* The main authentication interface. It takes an optional url which when
|
||||
* present is the endpoint being accessed, and returns a Promise which
|
||||
* resolves with authorization header fields.
|
||||
*
|
||||
* The result has the form:
|
||||
* { Authorization: 'Bearer <access_token_value>' }
|
||||
*/
|
||||
getRequestHeaders(): Promise<Headers>;
|
||||
/**
|
||||
* Provides a request implementation with OAuth 2.0 flow. In cases of
|
||||
* HTTP 401 and 403 responses, it automatically asks for a new access token
|
||||
* and replays the unsuccessful request.
|
||||
* @param opts Request options.
|
||||
* @param callback callback.
|
||||
* @return A promise that resolves with the HTTP response when no callback is
|
||||
* provided.
|
||||
*/
|
||||
request<T>(opts: GaxiosOptions): GaxiosPromise<T>;
|
||||
request<T>(opts: GaxiosOptions, callback: BodyResponseCallback<T>): void;
|
||||
/**
|
||||
* @return A promise that resolves with the project ID corresponding to the
|
||||
* current workload identity pool or current workforce pool if
|
||||
* determinable. For workforce pool credential, it returns the project ID
|
||||
* corresponding to the workforcePoolUserProject.
|
||||
* This is introduced to match the current pattern of using the Auth
|
||||
* library:
|
||||
* const projectId = await auth.getProjectId();
|
||||
* const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`;
|
||||
* const res = await client.request({ url });
|
||||
* The resource may not have permission
|
||||
* (resourcemanager.projects.get) to call this API or the required
|
||||
* scopes may not be selected:
|
||||
* https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes
|
||||
*/
|
||||
getProjectId(): Promise<string | null>;
|
||||
/**
|
||||
* Authenticates the provided HTTP request, processes it and resolves with the
|
||||
* returned response.
|
||||
* @param opts The HTTP request options.
|
||||
* @param retry Whether the current attempt is a retry after a failed attempt.
|
||||
* @return A promise that resolves with the successful response.
|
||||
*/
|
||||
protected requestAsync<T>(opts: GaxiosOptions, retry?: boolean): Promise<GaxiosResponse<T>>;
|
||||
/**
|
||||
* Forces token refresh, even if unexpired tokens are currently cached.
|
||||
* External credentials are exchanged for GCP access tokens via the token
|
||||
* exchange endpoint and other settings provided in the client options
|
||||
* object.
|
||||
* If the service_account_impersonation_url is provided, an additional
|
||||
* step to exchange the external account GCP access token for a service
|
||||
* account impersonated token is performed.
|
||||
* @return A promise that resolves with the fresh GCP access tokens.
|
||||
*/
|
||||
protected refreshAccessTokenAsync(): Promise<CredentialsWithResponse>;
|
||||
/**
|
||||
* Returns the workload identity pool project number if it is determinable
|
||||
* from the audience resource name.
|
||||
* @param audience The STS audience used to determine the project number.
|
||||
* @return The project number associated with the workload identity pool, if
|
||||
* this can be determined from the STS audience field. Otherwise, null is
|
||||
* returned.
|
||||
*/
|
||||
private getProjectNumber;
|
||||
/**
|
||||
* Exchanges an external account GCP access token for a service
|
||||
* account impersonated access token using iamcredentials
|
||||
* GenerateAccessToken API.
|
||||
* @param token The access token to exchange for a service account access
|
||||
* token.
|
||||
* @return A promise that resolves with the service account impersonated
|
||||
* credentials response.
|
||||
*/
|
||||
private getImpersonatedAccessToken;
|
||||
/**
|
||||
* Returns whether the provided credentials are expired or not.
|
||||
* If there is no expiry time, assumes the token is not expired or expiring.
|
||||
* @param accessToken The credentials to check for expiration.
|
||||
* @return Whether the credentials are expired or not.
|
||||
*/
|
||||
private isExpired;
|
||||
/**
|
||||
* @return The list of scopes for the requested GCP access token.
|
||||
*/
|
||||
private getScopesArray;
|
||||
/**
|
||||
* Checks whether Google APIs URL is valid.
|
||||
* @param apiName The apiName of url.
|
||||
* @param url The Google API URL to validate.
|
||||
* @return Whether the URL is valid or not.
|
||||
*/
|
||||
private validateGoogleAPIsUrl;
|
||||
}
|
||||
export {};
|
||||
+453
@@ -0,0 +1,453 @@
|
||||
"use strict";
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BaseExternalAccountClient = exports.CLOUD_RESOURCE_MANAGER = exports.EXTERNAL_ACCOUNT_TYPE = exports.EXPIRATION_TIME_OFFSET = void 0;
|
||||
const stream = require("stream");
|
||||
const authclient_1 = require("./authclient");
|
||||
const sts = require("./stscredentials");
|
||||
/**
|
||||
* The required token exchange grant_type: rfc8693#section-2.1
|
||||
*/
|
||||
const STS_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:token-exchange';
|
||||
/**
|
||||
* The requested token exchange requested_token_type: rfc8693#section-2.1
|
||||
*/
|
||||
const STS_REQUEST_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:access_token';
|
||||
/** The default OAuth scope to request when none is provided. */
|
||||
const DEFAULT_OAUTH_SCOPE = 'https://www.googleapis.com/auth/cloud-platform';
|
||||
/** The google apis domain pattern. */
|
||||
const GOOGLE_APIS_DOMAIN_PATTERN = '\\.googleapis\\.com$';
|
||||
/** The variable portion pattern in a Google APIs domain. */
|
||||
const VARIABLE_PORTION_PATTERN = '[^\\.\\s\\/\\\\]+';
|
||||
/**
|
||||
* Offset to take into account network delays and server clock skews.
|
||||
*/
|
||||
exports.EXPIRATION_TIME_OFFSET = 5 * 60 * 1000;
|
||||
/**
|
||||
* The credentials JSON file type for external account clients.
|
||||
* There are 3 types of JSON configs:
|
||||
* 1. authorized_user => Google end user credential
|
||||
* 2. service_account => Google service account credential
|
||||
* 3. external_Account => non-GCP service (eg. AWS, Azure, K8s)
|
||||
*/
|
||||
exports.EXTERNAL_ACCOUNT_TYPE = 'external_account';
|
||||
/** Cloud resource manager URL used to retrieve project information. */
|
||||
exports.CLOUD_RESOURCE_MANAGER = 'https://cloudresourcemanager.googleapis.com/v1/projects/';
|
||||
/** The workforce audience pattern. */
|
||||
const WORKFORCE_AUDIENCE_PATTERN = '//iam.googleapis.com/locations/[^/]+/workforcePools/[^/]+/providers/.+';
|
||||
/**
|
||||
* Base external account client. This is used to instantiate AuthClients for
|
||||
* exchanging external account credentials for GCP access token and authorizing
|
||||
* requests to GCP APIs.
|
||||
* The base class implements common logic for exchanging various type of
|
||||
* external credentials for GCP access token. The logic of determining and
|
||||
* retrieving the external credential based on the environment and
|
||||
* credential_source will be left for the subclasses.
|
||||
*/
|
||||
class BaseExternalAccountClient extends authclient_1.AuthClient {
|
||||
/**
|
||||
* Instantiate a BaseExternalAccountClient instance using the provided JSON
|
||||
* object loaded from an external account credentials file.
|
||||
* @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.
|
||||
*/
|
||||
constructor(options, additionalOptions) {
|
||||
super();
|
||||
if (options.type !== exports.EXTERNAL_ACCOUNT_TYPE) {
|
||||
throw new Error(`Expected "${exports.EXTERNAL_ACCOUNT_TYPE}" type but ` +
|
||||
`received "${options.type}"`);
|
||||
}
|
||||
this.clientAuth = options.client_id
|
||||
? {
|
||||
confidentialClientType: 'basic',
|
||||
clientId: options.client_id,
|
||||
clientSecret: options.client_secret,
|
||||
}
|
||||
: undefined;
|
||||
if (!this.validateGoogleAPIsUrl('sts', options.token_url)) {
|
||||
throw new Error(`"${options.token_url}" is not a valid token url.`);
|
||||
}
|
||||
this.stsCredential = new sts.StsCredentials(options.token_url, this.clientAuth);
|
||||
// Default OAuth scope. This could be overridden via public property.
|
||||
this.scopes = [DEFAULT_OAUTH_SCOPE];
|
||||
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 (this.workforcePoolUserProject &&
|
||||
!this.audience.match(workforceAudiencePattern)) {
|
||||
throw new Error('workforcePoolUserProject should not be set for non-workforce pool ' +
|
||||
'credentials.');
|
||||
}
|
||||
if (typeof options.service_account_impersonation_url !== 'undefined' &&
|
||||
!this.validateGoogleAPIsUrl('iamcredentials', options.service_account_impersonation_url)) {
|
||||
throw new Error(`"${options.service_account_impersonation_url}" is ` +
|
||||
'not a valid service account impersonation url.');
|
||||
}
|
||||
this.serviceAccountImpersonationUrl =
|
||||
options.service_account_impersonation_url;
|
||||
// As threshold could be zero,
|
||||
// eagerRefreshThresholdMillis || EXPIRATION_TIME_OFFSET will override the
|
||||
// zero value.
|
||||
if (typeof (additionalOptions === null || additionalOptions === void 0 ? void 0 : additionalOptions.eagerRefreshThresholdMillis) !== 'number') {
|
||||
this.eagerRefreshThresholdMillis = exports.EXPIRATION_TIME_OFFSET;
|
||||
}
|
||||
else {
|
||||
this.eagerRefreshThresholdMillis = additionalOptions
|
||||
.eagerRefreshThresholdMillis;
|
||||
}
|
||||
this.forceRefreshOnFailure = !!(additionalOptions === null || additionalOptions === void 0 ? void 0 : additionalOptions.forceRefreshOnFailure);
|
||||
this.projectId = null;
|
||||
this.projectNumber = this.getProjectNumber(this.audience);
|
||||
}
|
||||
/** The service account email to be impersonated, if available. */
|
||||
getServiceAccountEmail() {
|
||||
var _a;
|
||||
if (this.serviceAccountImpersonationUrl) {
|
||||
// Parse email from URL. The formal looks as follows:
|
||||
// https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateAccessToken
|
||||
const re = /serviceAccounts\/(?<email>[^:]+):generateAccessToken$/;
|
||||
const result = re.exec(this.serviceAccountImpersonationUrl);
|
||||
return ((_a = result === null || result === void 0 ? void 0 : result.groups) === null || _a === void 0 ? void 0 : _a.email) || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Provides a mechanism to inject GCP access tokens directly.
|
||||
* When the provided credential expires, a new credential, using the
|
||||
* external account options, is retrieved.
|
||||
* @param credentials The Credentials object to set on the current client.
|
||||
*/
|
||||
setCredentials(credentials) {
|
||||
super.setCredentials(credentials);
|
||||
this.cachedAccessToken = credentials;
|
||||
}
|
||||
/**
|
||||
* @return A promise that resolves with the current GCP access token
|
||||
* response. If the current credential is expired, a new one is retrieved.
|
||||
*/
|
||||
async getAccessToken() {
|
||||
// If cached access token is unavailable or expired, force refresh.
|
||||
if (!this.cachedAccessToken || this.isExpired(this.cachedAccessToken)) {
|
||||
await this.refreshAccessTokenAsync();
|
||||
}
|
||||
// Return GCP access token in GetAccessTokenResponse format.
|
||||
return {
|
||||
token: this.cachedAccessToken.access_token,
|
||||
res: this.cachedAccessToken.res,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* The main authentication interface. It takes an optional url which when
|
||||
* present is the endpoint being accessed, and returns a Promise which
|
||||
* resolves with authorization header fields.
|
||||
*
|
||||
* The result has the form:
|
||||
* { Authorization: 'Bearer <access_token_value>' }
|
||||
*/
|
||||
async getRequestHeaders() {
|
||||
const accessTokenResponse = await this.getAccessToken();
|
||||
const headers = {
|
||||
Authorization: `Bearer ${accessTokenResponse.token}`,
|
||||
};
|
||||
return this.addSharedMetadataHeaders(headers);
|
||||
}
|
||||
request(opts, callback) {
|
||||
if (callback) {
|
||||
this.requestAsync(opts).then(r => callback(null, r), e => {
|
||||
return callback(e, e.response);
|
||||
});
|
||||
}
|
||||
else {
|
||||
return this.requestAsync(opts);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return A promise that resolves with the project ID corresponding to the
|
||||
* current workload identity pool or current workforce pool if
|
||||
* determinable. For workforce pool credential, it returns the project ID
|
||||
* corresponding to the workforcePoolUserProject.
|
||||
* This is introduced to match the current pattern of using the Auth
|
||||
* library:
|
||||
* const projectId = await auth.getProjectId();
|
||||
* const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`;
|
||||
* const res = await client.request({ url });
|
||||
* The resource may not have permission
|
||||
* (resourcemanager.projects.get) to call this API or the required
|
||||
* scopes may not be selected:
|
||||
* https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes
|
||||
*/
|
||||
async getProjectId() {
|
||||
const projectNumber = this.projectNumber || this.workforcePoolUserProject;
|
||||
if (this.projectId) {
|
||||
// Return previously determined project ID.
|
||||
return this.projectId;
|
||||
}
|
||||
else if (projectNumber) {
|
||||
// Preferable not to use request() to avoid retrial policies.
|
||||
const headers = await this.getRequestHeaders();
|
||||
const response = await this.transporter.request({
|
||||
headers,
|
||||
url: `${exports.CLOUD_RESOURCE_MANAGER}${projectNumber}`,
|
||||
responseType: 'json',
|
||||
});
|
||||
this.projectId = response.data.projectId;
|
||||
return this.projectId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Authenticates the provided HTTP request, processes it and resolves with the
|
||||
* returned response.
|
||||
* @param opts The HTTP request options.
|
||||
* @param retry Whether the current attempt is a retry after a failed attempt.
|
||||
* @return A promise that resolves with the successful response.
|
||||
*/
|
||||
async requestAsync(opts, retry = false) {
|
||||
let response;
|
||||
try {
|
||||
const requestHeaders = await this.getRequestHeaders();
|
||||
opts.headers = opts.headers || {};
|
||||
if (requestHeaders && requestHeaders['x-goog-user-project']) {
|
||||
opts.headers['x-goog-user-project'] =
|
||||
requestHeaders['x-goog-user-project'];
|
||||
}
|
||||
if (requestHeaders && requestHeaders.Authorization) {
|
||||
opts.headers.Authorization = requestHeaders.Authorization;
|
||||
}
|
||||
response = await this.transporter.request(opts);
|
||||
}
|
||||
catch (e) {
|
||||
const res = e.response;
|
||||
if (res) {
|
||||
const statusCode = res.status;
|
||||
// Retry the request for metadata if the following criteria are true:
|
||||
// - We haven't already retried. It only makes sense to retry once.
|
||||
// - The response was a 401 or a 403
|
||||
// - The request didn't send a readableStream
|
||||
// - forceRefreshOnFailure is true
|
||||
const isReadableStream = res.config.data instanceof stream.Readable;
|
||||
const isAuthErr = statusCode === 401 || statusCode === 403;
|
||||
if (!retry &&
|
||||
isAuthErr &&
|
||||
!isReadableStream &&
|
||||
this.forceRefreshOnFailure) {
|
||||
await this.refreshAccessTokenAsync();
|
||||
return await this.requestAsync(opts, true);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
/**
|
||||
* Forces token refresh, even if unexpired tokens are currently cached.
|
||||
* External credentials are exchanged for GCP access tokens via the token
|
||||
* exchange endpoint and other settings provided in the client options
|
||||
* object.
|
||||
* If the service_account_impersonation_url is provided, an additional
|
||||
* step to exchange the external account GCP access token for a service
|
||||
* account impersonated token is performed.
|
||||
* @return A promise that resolves with the fresh GCP access tokens.
|
||||
*/
|
||||
async refreshAccessTokenAsync() {
|
||||
// Retrieve the external credential.
|
||||
const subjectToken = await this.retrieveSubjectToken();
|
||||
// Construct the STS credentials options.
|
||||
const stsCredentialsOptions = {
|
||||
grantType: STS_GRANT_TYPE,
|
||||
audience: this.audience,
|
||||
requestedTokenType: STS_REQUEST_TOKEN_TYPE,
|
||||
subjectToken,
|
||||
subjectTokenType: this.subjectTokenType,
|
||||
// generateAccessToken requires the provided access token to have
|
||||
// scopes:
|
||||
// https://www.googleapis.com/auth/iam or
|
||||
// https://www.googleapis.com/auth/cloud-platform
|
||||
// The new service account access token scopes will match the user
|
||||
// provided ones.
|
||||
scope: this.serviceAccountImpersonationUrl
|
||||
? [DEFAULT_OAUTH_SCOPE]
|
||||
: this.getScopesArray(),
|
||||
};
|
||||
// Exchange the external credentials for a GCP access token.
|
||||
// Client auth is prioritized over passing the workforcePoolUserProject
|
||||
// parameter for STS token exchange.
|
||||
const additionalOptions = !this.clientAuth && this.workforcePoolUserProject
|
||||
? { userProject: this.workforcePoolUserProject }
|
||||
: undefined;
|
||||
const stsResponse = await this.stsCredential.exchangeToken(stsCredentialsOptions, undefined, additionalOptions);
|
||||
if (this.serviceAccountImpersonationUrl) {
|
||||
this.cachedAccessToken = await this.getImpersonatedAccessToken(stsResponse.access_token);
|
||||
}
|
||||
else if (stsResponse.expires_in) {
|
||||
// Save response in cached access token.
|
||||
this.cachedAccessToken = {
|
||||
access_token: stsResponse.access_token,
|
||||
expiry_date: new Date().getTime() + stsResponse.expires_in * 1000,
|
||||
res: stsResponse.res,
|
||||
};
|
||||
}
|
||||
else {
|
||||
// Save response in cached access token.
|
||||
this.cachedAccessToken = {
|
||||
access_token: stsResponse.access_token,
|
||||
res: stsResponse.res,
|
||||
};
|
||||
}
|
||||
// Save credentials.
|
||||
this.credentials = {};
|
||||
Object.assign(this.credentials, this.cachedAccessToken);
|
||||
delete this.credentials.res;
|
||||
// Trigger tokens event to notify external listeners.
|
||||
this.emit('tokens', {
|
||||
refresh_token: null,
|
||||
expiry_date: this.cachedAccessToken.expiry_date,
|
||||
access_token: this.cachedAccessToken.access_token,
|
||||
token_type: 'Bearer',
|
||||
id_token: null,
|
||||
});
|
||||
// Return the cached access token.
|
||||
return this.cachedAccessToken;
|
||||
}
|
||||
/**
|
||||
* Returns the workload identity pool project number if it is determinable
|
||||
* from the audience resource name.
|
||||
* @param audience The STS audience used to determine the project number.
|
||||
* @return The project number associated with the workload identity pool, if
|
||||
* this can be determined from the STS audience field. Otherwise, null is
|
||||
* returned.
|
||||
*/
|
||||
getProjectNumber(audience) {
|
||||
// STS audience pattern:
|
||||
// //iam.googleapis.com/projects/$PROJECT_NUMBER/locations/...
|
||||
const match = audience.match(/\/projects\/([^/]+)/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
/**
|
||||
* Exchanges an external account GCP access token for a service
|
||||
* account impersonated access token using iamcredentials
|
||||
* GenerateAccessToken API.
|
||||
* @param token The access token to exchange for a service account access
|
||||
* token.
|
||||
* @return A promise that resolves with the service account impersonated
|
||||
* credentials response.
|
||||
*/
|
||||
async getImpersonatedAccessToken(token) {
|
||||
const opts = {
|
||||
url: this.serviceAccountImpersonationUrl,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
data: {
|
||||
scope: this.getScopesArray(),
|
||||
},
|
||||
responseType: 'json',
|
||||
};
|
||||
const response = await this.transporter.request(opts);
|
||||
const successResponse = response.data;
|
||||
return {
|
||||
access_token: successResponse.accessToken,
|
||||
// Convert from ISO format to timestamp.
|
||||
expiry_date: new Date(successResponse.expireTime).getTime(),
|
||||
res: response,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Returns whether the provided credentials are expired or not.
|
||||
* If there is no expiry time, assumes the token is not expired or expiring.
|
||||
* @param accessToken The credentials to check for expiration.
|
||||
* @return Whether the credentials are expired or not.
|
||||
*/
|
||||
isExpired(accessToken) {
|
||||
const now = new Date().getTime();
|
||||
return accessToken.expiry_date
|
||||
? now >= accessToken.expiry_date - this.eagerRefreshThresholdMillis
|
||||
: false;
|
||||
}
|
||||
/**
|
||||
* @return The list of scopes for the requested GCP access token.
|
||||
*/
|
||||
getScopesArray() {
|
||||
// Since scopes can be provided as string or array, the type should
|
||||
// be normalized.
|
||||
if (typeof this.scopes === 'string') {
|
||||
return [this.scopes];
|
||||
}
|
||||
else if (typeof this.scopes === 'undefined') {
|
||||
return [DEFAULT_OAUTH_SCOPE];
|
||||
}
|
||||
else {
|
||||
return this.scopes;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks whether Google APIs URL is valid.
|
||||
* @param apiName The apiName of url.
|
||||
* @param url The Google API URL to validate.
|
||||
* @return Whether the URL is valid or not.
|
||||
*/
|
||||
validateGoogleAPIsUrl(apiName, url) {
|
||||
let parsedUrl;
|
||||
// Return false if error is thrown during parsing URL.
|
||||
try {
|
||||
parsedUrl = new URL(url);
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
const urlDomain = parsedUrl.hostname;
|
||||
// Check the protocol is https.
|
||||
if (parsedUrl.protocol !== 'https:') {
|
||||
return false;
|
||||
}
|
||||
const googleAPIsDomainPatterns = [
|
||||
new RegExp('^' +
|
||||
VARIABLE_PORTION_PATTERN +
|
||||
'\\.' +
|
||||
apiName +
|
||||
GOOGLE_APIS_DOMAIN_PATTERN),
|
||||
new RegExp('^' + apiName + GOOGLE_APIS_DOMAIN_PATTERN),
|
||||
new RegExp('^' +
|
||||
apiName +
|
||||
'\\.' +
|
||||
VARIABLE_PORTION_PATTERN +
|
||||
GOOGLE_APIS_DOMAIN_PATTERN),
|
||||
new RegExp('^' +
|
||||
VARIABLE_PORTION_PATTERN +
|
||||
'\\-' +
|
||||
apiName +
|
||||
GOOGLE_APIS_DOMAIN_PATTERN),
|
||||
];
|
||||
for (const googleAPIsDomainPattern of googleAPIsDomainPatterns) {
|
||||
if (urlDomain.match(googleAPIsDomainPattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
exports.BaseExternalAccountClient = BaseExternalAccountClient;
|
||||
//# sourceMappingURL=baseexternalclient.js.map
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { GaxiosError } from 'gaxios';
|
||||
import { GetTokenResponse, OAuth2Client, RefreshOptions } from './oauth2client';
|
||||
export interface ComputeOptions extends RefreshOptions {
|
||||
/**
|
||||
* The service account email to use, or 'default'. A Compute Engine instance
|
||||
* may have multiple service accounts.
|
||||
*/
|
||||
serviceAccountEmail?: string;
|
||||
/**
|
||||
* The scopes that will be requested when acquiring service account
|
||||
* credentials. Only applicable to modern App Engine and Cloud Function
|
||||
* runtimes as of March 2019.
|
||||
*/
|
||||
scopes?: string | string[];
|
||||
}
|
||||
export declare class Compute extends OAuth2Client {
|
||||
private serviceAccountEmail;
|
||||
scopes: string[];
|
||||
/**
|
||||
* Google Compute Engine service account credentials.
|
||||
*
|
||||
* Retrieve access token from the metadata server.
|
||||
* See: https://developers.google.com/compute/docs/authentication
|
||||
*/
|
||||
constructor(options?: ComputeOptions);
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
* @param refreshToken Unused parameter
|
||||
*/
|
||||
protected refreshTokenNoCache(refreshToken?: string | null): Promise<GetTokenResponse>;
|
||||
/**
|
||||
* Fetches an ID token.
|
||||
* @param targetAudience the audience for the fetched ID token.
|
||||
*/
|
||||
fetchIdToken(targetAudience: string): Promise<string>;
|
||||
protected wrapError(e: GaxiosError): void;
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
"use strict";
|
||||
// Copyright 2013 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Compute = void 0;
|
||||
const arrify = require("arrify");
|
||||
const gcpMetadata = require("gcp-metadata");
|
||||
const oauth2client_1 = require("./oauth2client");
|
||||
class Compute extends oauth2client_1.OAuth2Client {
|
||||
/**
|
||||
* Google Compute Engine service account credentials.
|
||||
*
|
||||
* Retrieve access token from the metadata server.
|
||||
* See: https://developers.google.com/compute/docs/authentication
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
// Start with an expired refresh token, which will automatically be
|
||||
// refreshed before the first API call is made.
|
||||
this.credentials = { expiry_date: 1, refresh_token: 'compute-placeholder' };
|
||||
this.serviceAccountEmail = options.serviceAccountEmail || 'default';
|
||||
this.scopes = arrify(options.scopes);
|
||||
}
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
* @param refreshToken Unused parameter
|
||||
*/
|
||||
async refreshTokenNoCache(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
refreshToken) {
|
||||
const tokenPath = `service-accounts/${this.serviceAccountEmail}/token`;
|
||||
let data;
|
||||
try {
|
||||
const instanceOptions = {
|
||||
property: tokenPath,
|
||||
};
|
||||
if (this.scopes.length > 0) {
|
||||
instanceOptions.params = {
|
||||
scopes: this.scopes.join(','),
|
||||
};
|
||||
}
|
||||
data = await gcpMetadata.instance(instanceOptions);
|
||||
}
|
||||
catch (e) {
|
||||
e.message = `Could not refresh access token: ${e.message}`;
|
||||
this.wrapError(e);
|
||||
throw e;
|
||||
}
|
||||
const tokens = data;
|
||||
if (data && data.expires_in) {
|
||||
tokens.expiry_date = new Date().getTime() + data.expires_in * 1000;
|
||||
delete tokens.expires_in;
|
||||
}
|
||||
this.emit('tokens', tokens);
|
||||
return { tokens, res: null };
|
||||
}
|
||||
/**
|
||||
* Fetches an ID token.
|
||||
* @param targetAudience the audience for the fetched ID token.
|
||||
*/
|
||||
async fetchIdToken(targetAudience) {
|
||||
const idTokenPath = `service-accounts/${this.serviceAccountEmail}/identity` +
|
||||
`?format=full&audience=${targetAudience}`;
|
||||
let idToken;
|
||||
try {
|
||||
const instanceOptions = {
|
||||
property: idTokenPath,
|
||||
};
|
||||
idToken = await gcpMetadata.instance(instanceOptions);
|
||||
}
|
||||
catch (e) {
|
||||
e.message = `Could not fetch ID token: ${e.message}`;
|
||||
throw e;
|
||||
}
|
||||
return idToken;
|
||||
}
|
||||
wrapError(e) {
|
||||
const res = e.response;
|
||||
if (res && res.status) {
|
||||
e.code = res.status.toString();
|
||||
if (res.status === 403) {
|
||||
e.message =
|
||||
'A Forbidden error was returned while attempting to retrieve an access ' +
|
||||
'token for the Compute Engine built-in service account. This may be because the Compute ' +
|
||||
'Engine instance does not have the correct permission scopes specified: ' +
|
||||
e.message;
|
||||
}
|
||||
else if (res.status === 404) {
|
||||
e.message =
|
||||
'A Not Found error was returned while attempting to retrieve an access' +
|
||||
'token for the Compute Engine built-in service account. This may be because the Compute ' +
|
||||
'Engine instance does not have any permission scopes specified: ' +
|
||||
e.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.Compute = Compute;
|
||||
//# sourceMappingURL=computeclient.js.map
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
export interface Credentials {
|
||||
/**
|
||||
* This field is only present if the access_type parameter was set to offline in the authentication request. For details, see Refresh tokens.
|
||||
*/
|
||||
refresh_token?: string | null;
|
||||
/**
|
||||
* The time in ms at which this token is thought to expire.
|
||||
*/
|
||||
expiry_date?: number | null;
|
||||
/**
|
||||
* A token that can be sent to a Google API.
|
||||
*/
|
||||
access_token?: string | null;
|
||||
/**
|
||||
* Identifies the type of token returned. At this time, this field always has the value Bearer.
|
||||
*/
|
||||
token_type?: string | null;
|
||||
/**
|
||||
* A JWT that contains identity information about the user that is digitally signed by Google.
|
||||
*/
|
||||
id_token?: string | null;
|
||||
/**
|
||||
* The scopes of access granted by the access_token expressed as a list of space-delimited, case-sensitive strings.
|
||||
*/
|
||||
scope?: string;
|
||||
}
|
||||
export interface CredentialRequest {
|
||||
/**
|
||||
* This field is only present if the access_type parameter was set to offline in the authentication request. For details, see Refresh tokens.
|
||||
*/
|
||||
refresh_token?: string;
|
||||
/**
|
||||
* A token that can be sent to a Google API.
|
||||
*/
|
||||
access_token?: string;
|
||||
/**
|
||||
* Identifies the type of token returned. At this time, this field always has the value Bearer.
|
||||
*/
|
||||
token_type?: string;
|
||||
/**
|
||||
* The remaining lifetime of the access token in seconds.
|
||||
*/
|
||||
expires_in?: number;
|
||||
/**
|
||||
* A JWT that contains identity information about the user that is digitally signed by Google.
|
||||
*/
|
||||
id_token?: string;
|
||||
/**
|
||||
* The scopes of access granted by the access_token expressed as a list of space-delimited, case-sensitive strings.
|
||||
*/
|
||||
scope?: string;
|
||||
}
|
||||
export interface JWTInput {
|
||||
type?: string;
|
||||
client_email?: string;
|
||||
private_key?: string;
|
||||
private_key_id?: string;
|
||||
project_id?: string;
|
||||
client_id?: string;
|
||||
client_secret?: string;
|
||||
refresh_token?: string;
|
||||
quota_project_id?: string;
|
||||
}
|
||||
export interface CredentialBody {
|
||||
client_email?: string;
|
||||
private_key?: string;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=credentials.js.map
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
import { GaxiosOptions, GaxiosPromise, GaxiosResponse } from 'gaxios';
|
||||
import { BodyResponseCallback } from '../transporters';
|
||||
import { Credentials } from './credentials';
|
||||
import { AuthClient } from './authclient';
|
||||
import { GetAccessTokenResponse, Headers, RefreshOptions } from './oauth2client';
|
||||
/**
|
||||
* The maximum number of access boundary rules a Credential Access Boundary
|
||||
* can contain.
|
||||
*/
|
||||
export declare const MAX_ACCESS_BOUNDARY_RULES_COUNT = 10;
|
||||
/**
|
||||
* Offset to take into account network delays and server clock skews.
|
||||
*/
|
||||
export declare const EXPIRATION_TIME_OFFSET: number;
|
||||
/**
|
||||
* Internal interface for tracking the access token expiration time.
|
||||
*/
|
||||
interface CredentialsWithResponse extends Credentials {
|
||||
res?: GaxiosResponse | null;
|
||||
}
|
||||
/**
|
||||
* Internal interface for tracking and returning the Downscoped access token
|
||||
* expiration time in epoch time (seconds).
|
||||
*/
|
||||
interface DownscopedAccessTokenResponse extends GetAccessTokenResponse {
|
||||
expirationTime?: number | null;
|
||||
}
|
||||
/**
|
||||
* Defines an upper bound of permissions available for a GCP credential.
|
||||
*/
|
||||
export interface CredentialAccessBoundary {
|
||||
accessBoundary: {
|
||||
accessBoundaryRules: AccessBoundaryRule[];
|
||||
};
|
||||
}
|
||||
/** Defines an upper bound of permissions on a particular resource. */
|
||||
interface AccessBoundaryRule {
|
||||
availablePermissions: string[];
|
||||
availableResource: string;
|
||||
availabilityCondition?: AvailabilityCondition;
|
||||
}
|
||||
/**
|
||||
* An optional condition that can be used as part of a
|
||||
* CredentialAccessBoundary to further restrict permissions.
|
||||
*/
|
||||
interface AvailabilityCondition {
|
||||
expression: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
/**
|
||||
* Defines a set of Google credentials that are downscoped from an existing set
|
||||
* of Google OAuth2 credentials. This is useful to restrict the Identity and
|
||||
* Access Management (IAM) permissions that a short-lived credential can use.
|
||||
* The common pattern of usage is to have a token broker with elevated access
|
||||
* generate these downscoped credentials from higher access source credentials
|
||||
* and pass the downscoped short-lived access tokens to a token consumer via
|
||||
* some secure authenticated channel for limited access to Google Cloud Storage
|
||||
* resources.
|
||||
*/
|
||||
export declare class DownscopedClient extends AuthClient {
|
||||
private readonly authClient;
|
||||
private readonly credentialAccessBoundary;
|
||||
private cachedDownscopedAccessToken;
|
||||
private readonly stsCredential;
|
||||
readonly eagerRefreshThresholdMillis: number;
|
||||
readonly forceRefreshOnFailure: boolean;
|
||||
/**
|
||||
* Instantiates a downscoped client object using the provided source
|
||||
* AuthClient and credential access boundary rules.
|
||||
* To downscope permissions of a source AuthClient, a Credential Access
|
||||
* Boundary that specifies which resources the new credential can access, as
|
||||
* well as an upper bound on the permissions that are available on each
|
||||
* resource, has to be defined. A downscoped client can then be instantiated
|
||||
* using the source AuthClient and the Credential Access Boundary.
|
||||
* @param authClient The source AuthClient to be downscoped based on the
|
||||
* provided Credential Access Boundary rules.
|
||||
* @param credentialAccessBoundary The Credential Access Boundary which
|
||||
* contains a list of access boundary rules. Each rule contains information
|
||||
* on the resource that the rule applies to, the upper bound of the
|
||||
* 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.
|
||||
* @param quotaProjectId Optional quota project id for setting up in the
|
||||
* x-goog-user-project header.
|
||||
*/
|
||||
constructor(authClient: AuthClient, credentialAccessBoundary: CredentialAccessBoundary, additionalOptions?: RefreshOptions, quotaProjectId?: string);
|
||||
/**
|
||||
* Provides a mechanism to inject Downscoped access tokens directly.
|
||||
* The expiry_date field is required to facilitate determination of the token
|
||||
* expiration which would make it easier for the token consumer to handle.
|
||||
* @param credentials The Credentials object to set on the current client.
|
||||
*/
|
||||
setCredentials(credentials: Credentials): void;
|
||||
getAccessToken(): Promise<DownscopedAccessTokenResponse>;
|
||||
/**
|
||||
* The main authentication interface. It takes an optional url which when
|
||||
* present is the endpoint being accessed, and returns a Promise which
|
||||
* resolves with authorization header fields.
|
||||
*
|
||||
* The result has the form:
|
||||
* { Authorization: 'Bearer <access_token_value>' }
|
||||
*/
|
||||
getRequestHeaders(): Promise<Headers>;
|
||||
/**
|
||||
* Provides a request implementation with OAuth 2.0 flow. In cases of
|
||||
* HTTP 401 and 403 responses, it automatically asks for a new access token
|
||||
* and replays the unsuccessful request.
|
||||
* @param opts Request options.
|
||||
* @param callback callback.
|
||||
* @return A promise that resolves with the HTTP response when no callback
|
||||
* is provided.
|
||||
*/
|
||||
request<T>(opts: GaxiosOptions): GaxiosPromise<T>;
|
||||
request<T>(opts: GaxiosOptions, callback: BodyResponseCallback<T>): void;
|
||||
/**
|
||||
* Authenticates the provided HTTP request, processes it and resolves with the
|
||||
* returned response.
|
||||
* @param opts The HTTP request options.
|
||||
* @param retry Whether the current attempt is a retry after a failed attempt.
|
||||
* @return A promise that resolves with the successful response.
|
||||
*/
|
||||
protected requestAsync<T>(opts: GaxiosOptions, retry?: boolean): Promise<GaxiosResponse<T>>;
|
||||
/**
|
||||
* Forces token refresh, even if unexpired tokens are currently cached.
|
||||
* GCP access tokens are retrieved from authclient object/source credential.
|
||||
* Then GCP access tokens are exchanged for downscoped access tokens via the
|
||||
* token exchange endpoint.
|
||||
* @return A promise that resolves with the fresh downscoped access token.
|
||||
*/
|
||||
protected refreshAccessTokenAsync(): Promise<CredentialsWithResponse>;
|
||||
/**
|
||||
* Returns whether the provided credentials are expired or not.
|
||||
* If there is no expiry time, assumes the token is not expired or expiring.
|
||||
* @param downscopedAccessToken The credentials to check for expiration.
|
||||
* @return Whether the credentials are expired or not.
|
||||
*/
|
||||
private isExpired;
|
||||
}
|
||||
export {};
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
"use strict";
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DownscopedClient = exports.EXPIRATION_TIME_OFFSET = exports.MAX_ACCESS_BOUNDARY_RULES_COUNT = void 0;
|
||||
const stream = require("stream");
|
||||
const authclient_1 = require("./authclient");
|
||||
const sts = require("./stscredentials");
|
||||
/**
|
||||
* The required token exchange grant_type: rfc8693#section-2.1
|
||||
*/
|
||||
const STS_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:token-exchange';
|
||||
/**
|
||||
* The requested token exchange requested_token_type: rfc8693#section-2.1
|
||||
*/
|
||||
const STS_REQUEST_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:access_token';
|
||||
/**
|
||||
* The requested token exchange subject_token_type: rfc8693#section-2.1
|
||||
*/
|
||||
const STS_SUBJECT_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:access_token';
|
||||
/** The STS access token exchange end point. */
|
||||
const STS_ACCESS_TOKEN_URL = 'https://sts.googleapis.com/v1/token';
|
||||
/**
|
||||
* The maximum number of access boundary rules a Credential Access Boundary
|
||||
* can contain.
|
||||
*/
|
||||
exports.MAX_ACCESS_BOUNDARY_RULES_COUNT = 10;
|
||||
/**
|
||||
* Offset to take into account network delays and server clock skews.
|
||||
*/
|
||||
exports.EXPIRATION_TIME_OFFSET = 5 * 60 * 1000;
|
||||
/**
|
||||
* Defines a set of Google credentials that are downscoped from an existing set
|
||||
* of Google OAuth2 credentials. This is useful to restrict the Identity and
|
||||
* Access Management (IAM) permissions that a short-lived credential can use.
|
||||
* The common pattern of usage is to have a token broker with elevated access
|
||||
* generate these downscoped credentials from higher access source credentials
|
||||
* and pass the downscoped short-lived access tokens to a token consumer via
|
||||
* some secure authenticated channel for limited access to Google Cloud Storage
|
||||
* resources.
|
||||
*/
|
||||
class DownscopedClient extends authclient_1.AuthClient {
|
||||
/**
|
||||
* Instantiates a downscoped client object using the provided source
|
||||
* AuthClient and credential access boundary rules.
|
||||
* To downscope permissions of a source AuthClient, a Credential Access
|
||||
* Boundary that specifies which resources the new credential can access, as
|
||||
* well as an upper bound on the permissions that are available on each
|
||||
* resource, has to be defined. A downscoped client can then be instantiated
|
||||
* using the source AuthClient and the Credential Access Boundary.
|
||||
* @param authClient The source AuthClient to be downscoped based on the
|
||||
* provided Credential Access Boundary rules.
|
||||
* @param credentialAccessBoundary The Credential Access Boundary which
|
||||
* contains a list of access boundary rules. Each rule contains information
|
||||
* on the resource that the rule applies to, the upper bound of the
|
||||
* 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.
|
||||
* @param quotaProjectId Optional quota project id for setting up in the
|
||||
* x-goog-user-project header.
|
||||
*/
|
||||
constructor(authClient, credentialAccessBoundary, additionalOptions, quotaProjectId) {
|
||||
super();
|
||||
this.authClient = authClient;
|
||||
this.credentialAccessBoundary = credentialAccessBoundary;
|
||||
// Check 1-10 Access Boundary Rules are defined within Credential Access
|
||||
// Boundary.
|
||||
if (credentialAccessBoundary.accessBoundary.accessBoundaryRules.length === 0) {
|
||||
throw new Error('At least one access boundary rule needs to be defined.');
|
||||
}
|
||||
else if (credentialAccessBoundary.accessBoundary.accessBoundaryRules.length >
|
||||
exports.MAX_ACCESS_BOUNDARY_RULES_COUNT) {
|
||||
throw new Error('The provided access boundary has more than ' +
|
||||
`${exports.MAX_ACCESS_BOUNDARY_RULES_COUNT} access boundary rules.`);
|
||||
}
|
||||
// Check at least one permission should be defined in each Access Boundary
|
||||
// Rule.
|
||||
for (const rule of credentialAccessBoundary.accessBoundary
|
||||
.accessBoundaryRules) {
|
||||
if (rule.availablePermissions.length === 0) {
|
||||
throw new Error('At least one permission should be defined in access boundary rules.');
|
||||
}
|
||||
}
|
||||
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 === null || additionalOptions === void 0 ? void 0 : additionalOptions.eagerRefreshThresholdMillis) !== 'number') {
|
||||
this.eagerRefreshThresholdMillis = exports.EXPIRATION_TIME_OFFSET;
|
||||
}
|
||||
else {
|
||||
this.eagerRefreshThresholdMillis = additionalOptions
|
||||
.eagerRefreshThresholdMillis;
|
||||
}
|
||||
this.forceRefreshOnFailure = !!(additionalOptions === null || additionalOptions === void 0 ? void 0 : additionalOptions.forceRefreshOnFailure);
|
||||
this.quotaProjectId = quotaProjectId;
|
||||
}
|
||||
/**
|
||||
* Provides a mechanism to inject Downscoped access tokens directly.
|
||||
* The expiry_date field is required to facilitate determination of the token
|
||||
* expiration which would make it easier for the token consumer to handle.
|
||||
* @param credentials The Credentials object to set on the current client.
|
||||
*/
|
||||
setCredentials(credentials) {
|
||||
if (!credentials.expiry_date) {
|
||||
throw new Error('The access token expiry_date field is missing in the provided ' +
|
||||
'credentials.');
|
||||
}
|
||||
super.setCredentials(credentials);
|
||||
this.cachedDownscopedAccessToken = credentials;
|
||||
}
|
||||
async getAccessToken() {
|
||||
// If the cached access token is unavailable or expired, force refresh.
|
||||
// The Downscoped access token will be returned in
|
||||
// DownscopedAccessTokenResponse format.
|
||||
if (!this.cachedDownscopedAccessToken ||
|
||||
this.isExpired(this.cachedDownscopedAccessToken)) {
|
||||
await this.refreshAccessTokenAsync();
|
||||
}
|
||||
// Return Downscoped access token in DownscopedAccessTokenResponse format.
|
||||
return {
|
||||
token: this.cachedDownscopedAccessToken.access_token,
|
||||
expirationTime: this.cachedDownscopedAccessToken.expiry_date,
|
||||
res: this.cachedDownscopedAccessToken.res,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* The main authentication interface. It takes an optional url which when
|
||||
* present is the endpoint being accessed, and returns a Promise which
|
||||
* resolves with authorization header fields.
|
||||
*
|
||||
* The result has the form:
|
||||
* { Authorization: 'Bearer <access_token_value>' }
|
||||
*/
|
||||
async getRequestHeaders() {
|
||||
const accessTokenResponse = await this.getAccessToken();
|
||||
const headers = {
|
||||
Authorization: `Bearer ${accessTokenResponse.token}`,
|
||||
};
|
||||
return this.addSharedMetadataHeaders(headers);
|
||||
}
|
||||
request(opts, callback) {
|
||||
if (callback) {
|
||||
this.requestAsync(opts).then(r => callback(null, r), e => {
|
||||
return callback(e, e.response);
|
||||
});
|
||||
}
|
||||
else {
|
||||
return this.requestAsync(opts);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Authenticates the provided HTTP request, processes it and resolves with the
|
||||
* returned response.
|
||||
* @param opts The HTTP request options.
|
||||
* @param retry Whether the current attempt is a retry after a failed attempt.
|
||||
* @return A promise that resolves with the successful response.
|
||||
*/
|
||||
async requestAsync(opts, retry = false) {
|
||||
let response;
|
||||
try {
|
||||
const requestHeaders = await this.getRequestHeaders();
|
||||
opts.headers = opts.headers || {};
|
||||
if (requestHeaders && requestHeaders['x-goog-user-project']) {
|
||||
opts.headers['x-goog-user-project'] =
|
||||
requestHeaders['x-goog-user-project'];
|
||||
}
|
||||
if (requestHeaders && requestHeaders.Authorization) {
|
||||
opts.headers.Authorization = requestHeaders.Authorization;
|
||||
}
|
||||
response = await this.transporter.request(opts);
|
||||
}
|
||||
catch (e) {
|
||||
const res = e.response;
|
||||
if (res) {
|
||||
const statusCode = res.status;
|
||||
// Retry the request for metadata if the following criteria are true:
|
||||
// - We haven't already retried. It only makes sense to retry once.
|
||||
// - The response was a 401 or a 403
|
||||
// - The request didn't send a readableStream
|
||||
// - forceRefreshOnFailure is true
|
||||
const isReadableStream = res.config.data instanceof stream.Readable;
|
||||
const isAuthErr = statusCode === 401 || statusCode === 403;
|
||||
if (!retry &&
|
||||
isAuthErr &&
|
||||
!isReadableStream &&
|
||||
this.forceRefreshOnFailure) {
|
||||
await this.refreshAccessTokenAsync();
|
||||
return await this.requestAsync(opts, true);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
/**
|
||||
* Forces token refresh, even if unexpired tokens are currently cached.
|
||||
* GCP access tokens are retrieved from authclient object/source credential.
|
||||
* Then GCP access tokens are exchanged for downscoped access tokens via the
|
||||
* token exchange endpoint.
|
||||
* @return A promise that resolves with the fresh downscoped access token.
|
||||
*/
|
||||
async refreshAccessTokenAsync() {
|
||||
var _a;
|
||||
// Retrieve GCP access token from source credential.
|
||||
const subjectToken = (await this.authClient.getAccessToken()).token;
|
||||
// Construct the STS credentials options.
|
||||
const stsCredentialsOptions = {
|
||||
grantType: STS_GRANT_TYPE,
|
||||
requestedTokenType: STS_REQUEST_TOKEN_TYPE,
|
||||
subjectToken: subjectToken,
|
||||
subjectTokenType: STS_SUBJECT_TOKEN_TYPE,
|
||||
};
|
||||
// Exchange the source AuthClient access token for a Downscoped access
|
||||
// token.
|
||||
const stsResponse = await this.stsCredential.exchangeToken(stsCredentialsOptions, undefined, this.credentialAccessBoundary);
|
||||
/**
|
||||
* The STS endpoint will only return the expiration time for the downscoped
|
||||
* access token if the original access token represents a service account.
|
||||
* The downscoped token's expiration time will always match the source
|
||||
* credential expiration. When no expires_in is returned, we can copy the
|
||||
* source credential's expiration time.
|
||||
*/
|
||||
const sourceCredExpireDate = ((_a = this.authClient.credentials) === null || _a === void 0 ? void 0 : _a.expiry_date) || null;
|
||||
const expiryDate = stsResponse.expires_in
|
||||
? new Date().getTime() + stsResponse.expires_in * 1000
|
||||
: sourceCredExpireDate;
|
||||
// Save response in cached access token.
|
||||
this.cachedDownscopedAccessToken = {
|
||||
access_token: stsResponse.access_token,
|
||||
expiry_date: expiryDate,
|
||||
res: stsResponse.res,
|
||||
};
|
||||
// Save credentials.
|
||||
this.credentials = {};
|
||||
Object.assign(this.credentials, this.cachedDownscopedAccessToken);
|
||||
delete this.credentials.res;
|
||||
// Trigger tokens event to notify external listeners.
|
||||
this.emit('tokens', {
|
||||
refresh_token: null,
|
||||
expiry_date: this.cachedDownscopedAccessToken.expiry_date,
|
||||
access_token: this.cachedDownscopedAccessToken.access_token,
|
||||
token_type: 'Bearer',
|
||||
id_token: null,
|
||||
});
|
||||
// Return the cached access token.
|
||||
return this.cachedDownscopedAccessToken;
|
||||
}
|
||||
/**
|
||||
* Returns whether the provided credentials are expired or not.
|
||||
* If there is no expiry time, assumes the token is not expired or expiring.
|
||||
* @param downscopedAccessToken The credentials to check for expiration.
|
||||
* @return Whether the credentials are expired or not.
|
||||
*/
|
||||
isExpired(downscopedAccessToken) {
|
||||
const now = new Date().getTime();
|
||||
return downscopedAccessToken.expiry_date
|
||||
? now >=
|
||||
downscopedAccessToken.expiry_date - this.eagerRefreshThresholdMillis
|
||||
: false;
|
||||
}
|
||||
}
|
||||
exports.DownscopedClient = DownscopedClient;
|
||||
//# sourceMappingURL=downscopedclient.js.map
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
export declare enum GCPEnv {
|
||||
APP_ENGINE = "APP_ENGINE",
|
||||
KUBERNETES_ENGINE = "KUBERNETES_ENGINE",
|
||||
CLOUD_FUNCTIONS = "CLOUD_FUNCTIONS",
|
||||
COMPUTE_ENGINE = "COMPUTE_ENGINE",
|
||||
CLOUD_RUN = "CLOUD_RUN",
|
||||
NONE = "NONE"
|
||||
}
|
||||
export declare function clear(): void;
|
||||
export declare function getEnv(): Promise<GCPEnv>;
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
"use strict";
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getEnv = exports.clear = exports.GCPEnv = void 0;
|
||||
const gcpMetadata = require("gcp-metadata");
|
||||
var GCPEnv;
|
||||
(function (GCPEnv) {
|
||||
GCPEnv["APP_ENGINE"] = "APP_ENGINE";
|
||||
GCPEnv["KUBERNETES_ENGINE"] = "KUBERNETES_ENGINE";
|
||||
GCPEnv["CLOUD_FUNCTIONS"] = "CLOUD_FUNCTIONS";
|
||||
GCPEnv["COMPUTE_ENGINE"] = "COMPUTE_ENGINE";
|
||||
GCPEnv["CLOUD_RUN"] = "CLOUD_RUN";
|
||||
GCPEnv["NONE"] = "NONE";
|
||||
})(GCPEnv = exports.GCPEnv || (exports.GCPEnv = {}));
|
||||
let envPromise;
|
||||
function clear() {
|
||||
envPromise = undefined;
|
||||
}
|
||||
exports.clear = clear;
|
||||
async function getEnv() {
|
||||
if (envPromise) {
|
||||
return envPromise;
|
||||
}
|
||||
envPromise = getEnvMemoized();
|
||||
return envPromise;
|
||||
}
|
||||
exports.getEnv = getEnv;
|
||||
async function getEnvMemoized() {
|
||||
let env = GCPEnv.NONE;
|
||||
if (isAppEngine()) {
|
||||
env = GCPEnv.APP_ENGINE;
|
||||
}
|
||||
else if (isCloudFunction()) {
|
||||
env = GCPEnv.CLOUD_FUNCTIONS;
|
||||
}
|
||||
else if (await isComputeEngine()) {
|
||||
if (await isKubernetesEngine()) {
|
||||
env = GCPEnv.KUBERNETES_ENGINE;
|
||||
}
|
||||
else if (isCloudRun()) {
|
||||
env = GCPEnv.CLOUD_RUN;
|
||||
}
|
||||
else {
|
||||
env = GCPEnv.COMPUTE_ENGINE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
env = GCPEnv.NONE;
|
||||
}
|
||||
return env;
|
||||
}
|
||||
function isAppEngine() {
|
||||
return !!(process.env.GAE_SERVICE || process.env.GAE_MODULE_NAME);
|
||||
}
|
||||
function isCloudFunction() {
|
||||
return !!(process.env.FUNCTION_NAME || process.env.FUNCTION_TARGET);
|
||||
}
|
||||
/**
|
||||
* This check only verifies that the environment is running knative.
|
||||
* This must be run *after* checking for Kubernetes, otherwise it will
|
||||
* return a false positive.
|
||||
*/
|
||||
function isCloudRun() {
|
||||
return !!process.env.K_CONFIGURATION;
|
||||
}
|
||||
async function isKubernetesEngine() {
|
||||
try {
|
||||
await gcpMetadata.instance('attributes/cluster-name');
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async function isComputeEngine() {
|
||||
return gcpMetadata.isAvailable();
|
||||
}
|
||||
//# sourceMappingURL=envDetect.js.map
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { RefreshOptions } from './oauth2client';
|
||||
import { BaseExternalAccountClient } from './baseexternalclient';
|
||||
import { IdentityPoolClientOptions } from './identitypoolclient';
|
||||
import { AwsClientOptions } from './awsclient';
|
||||
export declare type ExternalAccountClientOptions = IdentityPoolClientOptions | AwsClientOptions;
|
||||
/**
|
||||
* Dummy class with no constructor. Developers are expected to use fromJSON.
|
||||
*/
|
||||
export declare class ExternalAccountClient {
|
||||
constructor();
|
||||
/**
|
||||
* This static method will instantiate the
|
||||
* corresponding type of external account credential depending on the
|
||||
* underlying credential source.
|
||||
* @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.
|
||||
* @return A BaseExternalAccountClient instance or null if the options
|
||||
* provided do not correspond to an external account credential.
|
||||
*/
|
||||
static fromJSON(options: ExternalAccountClientOptions, additionalOptions?: RefreshOptions): BaseExternalAccountClient | null;
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
"use strict";
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ExternalAccountClient = void 0;
|
||||
const baseexternalclient_1 = require("./baseexternalclient");
|
||||
const identitypoolclient_1 = require("./identitypoolclient");
|
||||
const awsclient_1 = require("./awsclient");
|
||||
/**
|
||||
* Dummy class with no constructor. Developers are expected to use fromJSON.
|
||||
*/
|
||||
class ExternalAccountClient {
|
||||
constructor() {
|
||||
throw new Error('ExternalAccountClients should be initialized via: ' +
|
||||
'ExternalAccountClient.fromJSON(), ' +
|
||||
'directly via explicit constructors, eg. ' +
|
||||
'new AwsClient(options), new IdentityPoolClient(options) or via ' +
|
||||
'new GoogleAuth(options).getClient()');
|
||||
}
|
||||
/**
|
||||
* This static method will instantiate the
|
||||
* corresponding type of external account credential depending on the
|
||||
* underlying credential source.
|
||||
* @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.
|
||||
* @return A BaseExternalAccountClient instance or null if the options
|
||||
* provided do not correspond to an external account credential.
|
||||
*/
|
||||
static fromJSON(options, additionalOptions) {
|
||||
var _a;
|
||||
if (options && options.type === baseexternalclient_1.EXTERNAL_ACCOUNT_TYPE) {
|
||||
if ((_a = options.credential_source) === null || _a === void 0 ? void 0 : _a.environment_id) {
|
||||
return new awsclient_1.AwsClient(options, additionalOptions);
|
||||
}
|
||||
else {
|
||||
return new identitypoolclient_1.IdentityPoolClient(options, additionalOptions);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.ExternalAccountClient = ExternalAccountClient;
|
||||
//# sourceMappingURL=externalclient.js.map
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
/// <reference types="node" />
|
||||
import { GaxiosOptions, GaxiosResponse } from 'gaxios';
|
||||
import * as stream from 'stream';
|
||||
import { DefaultTransporter, Transporter } from '../transporters';
|
||||
import { Compute } from './computeclient';
|
||||
import { CredentialBody, JWTInput } from './credentials';
|
||||
import { IdTokenClient } from './idtokenclient';
|
||||
import { GCPEnv } from './envDetect';
|
||||
import { JWT, JWTOptions } from './jwtclient';
|
||||
import { Headers, OAuth2ClientOptions, RefreshOptions } from './oauth2client';
|
||||
import { UserRefreshClient, UserRefreshClientOptions } from './refreshclient';
|
||||
import { Impersonated, ImpersonatedOptions } from './impersonated';
|
||||
import { ExternalAccountClientOptions } from './externalclient';
|
||||
import { BaseExternalAccountClient } from './baseexternalclient';
|
||||
import { AuthClient } from './authclient';
|
||||
/**
|
||||
* Defines all types of explicit clients that are determined via ADC JSON
|
||||
* config file.
|
||||
*/
|
||||
export declare type JSONClient = JWT | UserRefreshClient | BaseExternalAccountClient | Impersonated;
|
||||
export interface ProjectIdCallback {
|
||||
(err?: Error | null, projectId?: string | null): void;
|
||||
}
|
||||
export interface CredentialCallback {
|
||||
(err: Error | null, result?: JSONClient): void;
|
||||
}
|
||||
interface DeprecatedGetClientOptions {
|
||||
}
|
||||
export interface ADCCallback {
|
||||
(err: Error | null, credential?: AuthClient, projectId?: string | null): void;
|
||||
}
|
||||
export interface ADCResponse {
|
||||
credential: AuthClient;
|
||||
projectId: string | null;
|
||||
}
|
||||
export interface GoogleAuthOptions {
|
||||
/**
|
||||
* Path to a .json, .pem, or .p12 key file
|
||||
*/
|
||||
keyFilename?: string;
|
||||
/**
|
||||
* Path to a .json, .pem, or .p12 key file
|
||||
*/
|
||||
keyFile?: string;
|
||||
/**
|
||||
* Object containing client_email and private_key properties, or the
|
||||
* external account client options.
|
||||
*/
|
||||
credentials?: CredentialBody | ExternalAccountClientOptions;
|
||||
/**
|
||||
* Options object passed to the constructor of the client
|
||||
*/
|
||||
clientOptions?: JWTOptions | OAuth2ClientOptions | UserRefreshClientOptions | ImpersonatedOptions;
|
||||
/**
|
||||
* Required scopes for the desired API request
|
||||
*/
|
||||
scopes?: string | string[];
|
||||
/**
|
||||
* Your project ID.
|
||||
*/
|
||||
projectId?: string;
|
||||
}
|
||||
export declare const CLOUD_SDK_CLIENT_ID = "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com";
|
||||
export declare class GoogleAuth {
|
||||
transporter?: Transporter;
|
||||
/**
|
||||
* Caches a value indicating whether the auth layer is running on Google
|
||||
* Compute Engine.
|
||||
* @private
|
||||
*/
|
||||
private checkIsGCE?;
|
||||
useJWTAccessWithScope?: boolean;
|
||||
defaultServicePath?: string;
|
||||
get isGCE(): boolean | undefined;
|
||||
private _getDefaultProjectIdPromise?;
|
||||
private _cachedProjectId?;
|
||||
jsonContent: JWTInput | ExternalAccountClientOptions | null;
|
||||
cachedCredential: JSONClient | Impersonated | Compute | null;
|
||||
/**
|
||||
* Scopes populated by the client library by default. We differentiate between
|
||||
* these and user defined scopes when deciding whether to use a self-signed JWT.
|
||||
*/
|
||||
defaultScopes?: string | string[];
|
||||
private keyFilename?;
|
||||
private scopes?;
|
||||
private clientOptions?;
|
||||
/**
|
||||
* Export DefaultTransporter as a static property of the class.
|
||||
*/
|
||||
static DefaultTransporter: typeof DefaultTransporter;
|
||||
constructor(opts?: GoogleAuthOptions);
|
||||
setGapicJWTValues(client: JWT): void;
|
||||
/**
|
||||
* Obtains the default project ID for the application.
|
||||
* @param callback Optional callback
|
||||
* @returns Promise that resolves with project Id (if used without callback)
|
||||
*/
|
||||
getProjectId(): Promise<string>;
|
||||
getProjectId(callback: ProjectIdCallback): void;
|
||||
private getProjectIdAsync;
|
||||
/**
|
||||
* @returns Any scopes (user-specified or default scopes specified by the
|
||||
* client library) that need to be set on the current Auth client.
|
||||
*/
|
||||
private getAnyScopes;
|
||||
/**
|
||||
* Obtains the default service-level credentials for the application.
|
||||
* @param callback Optional callback.
|
||||
* @returns Promise that resolves with the ADCResponse (if no callback was
|
||||
* passed).
|
||||
*/
|
||||
getApplicationDefault(): Promise<ADCResponse>;
|
||||
getApplicationDefault(callback: ADCCallback): void;
|
||||
getApplicationDefault(options: RefreshOptions): Promise<ADCResponse>;
|
||||
getApplicationDefault(options: RefreshOptions, callback: ADCCallback): void;
|
||||
private getApplicationDefaultAsync;
|
||||
/**
|
||||
* Determines whether the auth layer is running on Google Compute Engine.
|
||||
* @returns A promise that resolves with the boolean.
|
||||
* @api private
|
||||
*/
|
||||
_checkIsGCE(): Promise<boolean>;
|
||||
/**
|
||||
* Attempts to load default credentials from the environment variable path..
|
||||
* @returns Promise that resolves with the OAuth2Client or null.
|
||||
* @api private
|
||||
*/
|
||||
_tryGetApplicationCredentialsFromEnvironmentVariable(options?: RefreshOptions): Promise<JSONClient | null>;
|
||||
/**
|
||||
* Attempts to load default credentials from a well-known file location
|
||||
* @return Promise that resolves with the OAuth2Client or null.
|
||||
* @api private
|
||||
*/
|
||||
_tryGetApplicationCredentialsFromWellKnownFile(options?: RefreshOptions): Promise<JSONClient | null>;
|
||||
/**
|
||||
* Attempts to load default credentials from a file at the given path..
|
||||
* @param filePath The path to the file to read.
|
||||
* @returns Promise that resolves with the OAuth2Client
|
||||
* @api private
|
||||
*/
|
||||
_getApplicationCredentialsFromFilePath(filePath: string, options?: RefreshOptions): Promise<JSONClient>;
|
||||
/**
|
||||
* Create a credentials instance using the given input options.
|
||||
* @param json The input object.
|
||||
* @param options The JWT or UserRefresh options for the client
|
||||
* @returns JWT or UserRefresh Client with data
|
||||
*/
|
||||
fromJSON(json: JWTInput, options?: RefreshOptions): JSONClient;
|
||||
/**
|
||||
* Return a JWT or UserRefreshClient from JavaScript object, caching both the
|
||||
* object used to instantiate and the client.
|
||||
* @param json The input object.
|
||||
* @param options The JWT or UserRefresh options for the client
|
||||
* @returns JWT or UserRefresh Client with data
|
||||
*/
|
||||
private _cacheClientFromJSON;
|
||||
/**
|
||||
* Create a credentials instance using the given input stream.
|
||||
* @param inputStream The input stream.
|
||||
* @param callback Optional callback.
|
||||
*/
|
||||
fromStream(inputStream: stream.Readable): Promise<JSONClient>;
|
||||
fromStream(inputStream: stream.Readable, callback: CredentialCallback): void;
|
||||
fromStream(inputStream: stream.Readable, options: RefreshOptions): Promise<JSONClient>;
|
||||
fromStream(inputStream: stream.Readable, options: RefreshOptions, callback: CredentialCallback): void;
|
||||
private fromStreamAsync;
|
||||
/**
|
||||
* Create a credentials instance using the given API key string.
|
||||
* @param apiKey The API key string
|
||||
* @param options An optional options object.
|
||||
* @returns A JWT loaded from the key
|
||||
*/
|
||||
fromAPIKey(apiKey: string, options?: RefreshOptions): JWT;
|
||||
/**
|
||||
* Determines whether the current operating system is Windows.
|
||||
* @api private
|
||||
*/
|
||||
private _isWindows;
|
||||
/**
|
||||
* Run the Google Cloud SDK command that prints the default project ID
|
||||
*/
|
||||
private getDefaultServiceProjectId;
|
||||
/**
|
||||
* Loads the project id from environment variables.
|
||||
* @api private
|
||||
*/
|
||||
private getProductionProjectId;
|
||||
/**
|
||||
* Loads the project id from the GOOGLE_APPLICATION_CREDENTIALS json file.
|
||||
* @api private
|
||||
*/
|
||||
private getFileProjectId;
|
||||
/**
|
||||
* Gets the project ID from external account client if available.
|
||||
*/
|
||||
private getExternalAccountClientProjectId;
|
||||
/**
|
||||
* Gets the Compute Engine project ID if it can be inferred.
|
||||
*/
|
||||
private getGCEProjectId;
|
||||
/**
|
||||
* The callback function handles a credential object that contains the
|
||||
* client_email and private_key (if exists).
|
||||
* getCredentials checks for these values from the user JSON at first.
|
||||
* If it doesn't exist, and the environment is on GCE, it gets the
|
||||
* client_email from the cloud metadata server.
|
||||
* @param callback Callback that handles the credential object that contains
|
||||
* a client_email and optional private key, or the error.
|
||||
* returned
|
||||
*/
|
||||
getCredentials(): Promise<CredentialBody>;
|
||||
getCredentials(callback: (err: Error | null, credentials?: CredentialBody) => void): void;
|
||||
private getCredentialsAsync;
|
||||
/**
|
||||
* Automatically obtain a client based on the provided configuration. If no
|
||||
* options were passed, use Application Default Credentials.
|
||||
*/
|
||||
getClient(options?: DeprecatedGetClientOptions): Promise<Compute | JWT | UserRefreshClient | Impersonated | BaseExternalAccountClient>;
|
||||
/**
|
||||
* Creates a client which will fetch an ID token for authorization.
|
||||
* @param targetAudience the audience for the fetched ID token.
|
||||
* @returns IdTokenClient for making HTTP calls authenticated with ID tokens.
|
||||
*/
|
||||
getIdTokenClient(targetAudience: string): Promise<IdTokenClient>;
|
||||
/**
|
||||
* Automatically obtain application default credentials, and return
|
||||
* an access token for making requests.
|
||||
*/
|
||||
getAccessToken(): Promise<string | null | undefined>;
|
||||
/**
|
||||
* Obtain the HTTP headers that will provide authorization for a given
|
||||
* request.
|
||||
*/
|
||||
getRequestHeaders(url?: string): Promise<Headers>;
|
||||
/**
|
||||
* Obtain credentials for a request, then attach the appropriate headers to
|
||||
* the request options.
|
||||
* @param opts Axios or Request options on which to attach the headers
|
||||
*/
|
||||
authorizeRequest(opts: {
|
||||
url?: string;
|
||||
uri?: string;
|
||||
headers?: Headers;
|
||||
}): Promise<{
|
||||
url?: string | undefined;
|
||||
uri?: string | undefined;
|
||||
headers?: Headers | undefined;
|
||||
}>;
|
||||
/**
|
||||
* Automatically obtain application default credentials, and make an
|
||||
* HTTP request using the given options.
|
||||
* @param opts Axios request options for the HTTP request.
|
||||
*/
|
||||
request<T = any>(opts: GaxiosOptions): Promise<GaxiosResponse<T>>;
|
||||
/**
|
||||
* Determine the compute environment in which the code is running.
|
||||
*/
|
||||
getEnv(): Promise<GCPEnv>;
|
||||
/**
|
||||
* Sign the given data with the current private key, or go out
|
||||
* to the IAM API to sign it.
|
||||
* @param data The data to be signed.
|
||||
*/
|
||||
sign(data: string): Promise<string>;
|
||||
private signBlob;
|
||||
}
|
||||
export interface SignBlobResponse {
|
||||
keyId: string;
|
||||
signedBlob: string;
|
||||
}
|
||||
export {};
|
||||
+670
@@ -0,0 +1,670 @@
|
||||
"use strict";
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GoogleAuth = exports.CLOUD_SDK_CLIENT_ID = void 0;
|
||||
const child_process_1 = require("child_process");
|
||||
const fs = require("fs");
|
||||
const gcpMetadata = require("gcp-metadata");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const crypto_1 = require("../crypto/crypto");
|
||||
const transporters_1 = require("../transporters");
|
||||
const computeclient_1 = require("./computeclient");
|
||||
const idtokenclient_1 = require("./idtokenclient");
|
||||
const envDetect_1 = require("./envDetect");
|
||||
const jwtclient_1 = require("./jwtclient");
|
||||
const refreshclient_1 = require("./refreshclient");
|
||||
const externalclient_1 = require("./externalclient");
|
||||
const baseexternalclient_1 = require("./baseexternalclient");
|
||||
exports.CLOUD_SDK_CLIENT_ID = '764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com';
|
||||
class GoogleAuth {
|
||||
constructor(opts) {
|
||||
/**
|
||||
* Caches a value indicating whether the auth layer is running on Google
|
||||
* Compute Engine.
|
||||
* @private
|
||||
*/
|
||||
this.checkIsGCE = undefined;
|
||||
// To save the contents of the JSON credential file
|
||||
this.jsonContent = null;
|
||||
this.cachedCredential = null;
|
||||
opts = opts || {};
|
||||
this._cachedProjectId = opts.projectId || null;
|
||||
this.keyFilename = opts.keyFilename || opts.keyFile;
|
||||
this.scopes = opts.scopes;
|
||||
this.jsonContent = opts.credentials || null;
|
||||
this.clientOptions = opts.clientOptions;
|
||||
}
|
||||
// Note: this properly is only public to satisify unit tests.
|
||||
// https://github.com/Microsoft/TypeScript/issues/5228
|
||||
get isGCE() {
|
||||
return this.checkIsGCE;
|
||||
}
|
||||
// GAPIC client libraries should always use self-signed JWTs. The following
|
||||
// variables are set on the JWT client in order to indicate the type of library,
|
||||
// and sign the JWT with the correct audience and scopes (if not supplied).
|
||||
setGapicJWTValues(client) {
|
||||
client.defaultServicePath = this.defaultServicePath;
|
||||
client.useJWTAccessWithScope = this.useJWTAccessWithScope;
|
||||
client.defaultScopes = this.defaultScopes;
|
||||
}
|
||||
getProjectId(callback) {
|
||||
if (callback) {
|
||||
this.getProjectIdAsync().then(r => callback(null, r), callback);
|
||||
}
|
||||
else {
|
||||
return this.getProjectIdAsync();
|
||||
}
|
||||
}
|
||||
getProjectIdAsync() {
|
||||
if (this._cachedProjectId) {
|
||||
return Promise.resolve(this._cachedProjectId);
|
||||
}
|
||||
// In implicit case, supports three environments. In order of precedence,
|
||||
// the implicit environments are:
|
||||
// - GCLOUD_PROJECT or GOOGLE_CLOUD_PROJECT environment variable
|
||||
// - GOOGLE_APPLICATION_CREDENTIALS JSON file
|
||||
// - Cloud SDK: `gcloud config config-helper --format json`
|
||||
// - GCE project ID from metadata server)
|
||||
if (!this._getDefaultProjectIdPromise) {
|
||||
// TODO: refactor the below code so that it doesn't mix and match
|
||||
// promises and async/await.
|
||||
this._getDefaultProjectIdPromise = new Promise(
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
async (resolve, reject) => {
|
||||
try {
|
||||
const projectId = this.getProductionProjectId() ||
|
||||
(await this.getFileProjectId()) ||
|
||||
(await this.getDefaultServiceProjectId()) ||
|
||||
(await this.getGCEProjectId()) ||
|
||||
(await this.getExternalAccountClientProjectId());
|
||||
this._cachedProjectId = projectId;
|
||||
if (!projectId) {
|
||||
throw new Error('Unable to detect a Project Id in the current environment. \n' +
|
||||
'To learn more about authentication and Google APIs, visit: \n' +
|
||||
'https://cloud.google.com/docs/authentication/getting-started');
|
||||
}
|
||||
resolve(projectId);
|
||||
}
|
||||
catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._getDefaultProjectIdPromise;
|
||||
}
|
||||
/**
|
||||
* @returns Any scopes (user-specified or default scopes specified by the
|
||||
* client library) that need to be set on the current Auth client.
|
||||
*/
|
||||
getAnyScopes() {
|
||||
return this.scopes || this.defaultScopes;
|
||||
}
|
||||
getApplicationDefault(optionsOrCallback = {}, callback) {
|
||||
let options;
|
||||
if (typeof optionsOrCallback === 'function') {
|
||||
callback = optionsOrCallback;
|
||||
}
|
||||
else {
|
||||
options = optionsOrCallback;
|
||||
}
|
||||
if (callback) {
|
||||
this.getApplicationDefaultAsync(options).then(r => callback(null, r.credential, r.projectId), callback);
|
||||
}
|
||||
else {
|
||||
return this.getApplicationDefaultAsync(options);
|
||||
}
|
||||
}
|
||||
async getApplicationDefaultAsync(options = {}) {
|
||||
// If we've already got a cached credential, just return it.
|
||||
if (this.cachedCredential) {
|
||||
return {
|
||||
credential: this.cachedCredential,
|
||||
projectId: await this.getProjectIdAsync(),
|
||||
};
|
||||
}
|
||||
let credential;
|
||||
let projectId;
|
||||
// Check for the existence of a local environment variable pointing to the
|
||||
// location of the credential file. This is typically used in local
|
||||
// developer scenarios.
|
||||
credential =
|
||||
await this._tryGetApplicationCredentialsFromEnvironmentVariable(options);
|
||||
if (credential) {
|
||||
if (credential instanceof jwtclient_1.JWT) {
|
||||
credential.scopes = this.scopes;
|
||||
}
|
||||
else if (credential instanceof baseexternalclient_1.BaseExternalAccountClient) {
|
||||
credential.scopes = this.getAnyScopes();
|
||||
}
|
||||
this.cachedCredential = credential;
|
||||
projectId = await this.getProjectId();
|
||||
return { credential, projectId };
|
||||
}
|
||||
// Look in the well-known credential file location.
|
||||
credential = await this._tryGetApplicationCredentialsFromWellKnownFile(options);
|
||||
if (credential) {
|
||||
if (credential instanceof jwtclient_1.JWT) {
|
||||
credential.scopes = this.scopes;
|
||||
}
|
||||
else if (credential instanceof baseexternalclient_1.BaseExternalAccountClient) {
|
||||
credential.scopes = this.getAnyScopes();
|
||||
}
|
||||
this.cachedCredential = credential;
|
||||
projectId = await this.getProjectId();
|
||||
return { credential, projectId };
|
||||
}
|
||||
// Determine if we're running on GCE.
|
||||
let isGCE;
|
||||
try {
|
||||
isGCE = await this._checkIsGCE();
|
||||
}
|
||||
catch (e) {
|
||||
e.message = `Unexpected error determining execution environment: ${e.message}`;
|
||||
throw e;
|
||||
}
|
||||
if (!isGCE) {
|
||||
// We failed to find the default credentials. Bail out with an error.
|
||||
throw new Error('Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.');
|
||||
}
|
||||
// For GCE, just return a default ComputeClient. It will take care of
|
||||
// the rest.
|
||||
options.scopes = this.getAnyScopes();
|
||||
this.cachedCredential = new computeclient_1.Compute(options);
|
||||
projectId = await this.getProjectId();
|
||||
return { projectId, credential: this.cachedCredential };
|
||||
}
|
||||
/**
|
||||
* Determines whether the auth layer is running on Google Compute Engine.
|
||||
* @returns A promise that resolves with the boolean.
|
||||
* @api private
|
||||
*/
|
||||
async _checkIsGCE() {
|
||||
if (this.checkIsGCE === undefined) {
|
||||
this.checkIsGCE = await gcpMetadata.isAvailable();
|
||||
}
|
||||
return this.checkIsGCE;
|
||||
}
|
||||
/**
|
||||
* Attempts to load default credentials from the environment variable path..
|
||||
* @returns Promise that resolves with the OAuth2Client or null.
|
||||
* @api private
|
||||
*/
|
||||
async _tryGetApplicationCredentialsFromEnvironmentVariable(options) {
|
||||
const credentialsPath = process.env['GOOGLE_APPLICATION_CREDENTIALS'] ||
|
||||
process.env['google_application_credentials'];
|
||||
if (!credentialsPath || credentialsPath.length === 0) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return this._getApplicationCredentialsFromFilePath(credentialsPath, options);
|
||||
}
|
||||
catch (e) {
|
||||
e.message = `Unable to read the credential file specified by the GOOGLE_APPLICATION_CREDENTIALS environment variable: ${e.message}`;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Attempts to load default credentials from a well-known file location
|
||||
* @return Promise that resolves with the OAuth2Client or null.
|
||||
* @api private
|
||||
*/
|
||||
async _tryGetApplicationCredentialsFromWellKnownFile(options) {
|
||||
// First, figure out the location of the file, depending upon the OS type.
|
||||
let location = null;
|
||||
if (this._isWindows()) {
|
||||
// Windows
|
||||
location = process.env['APPDATA'];
|
||||
}
|
||||
else {
|
||||
// Linux or Mac
|
||||
const home = process.env['HOME'];
|
||||
if (home) {
|
||||
location = path.join(home, '.config');
|
||||
}
|
||||
}
|
||||
// If we found the root path, expand it.
|
||||
if (location) {
|
||||
location = path.join(location, 'gcloud', 'application_default_credentials.json');
|
||||
if (!fs.existsSync(location)) {
|
||||
location = null;
|
||||
}
|
||||
}
|
||||
// The file does not exist.
|
||||
if (!location) {
|
||||
return null;
|
||||
}
|
||||
// The file seems to exist. Try to use it.
|
||||
const client = await this._getApplicationCredentialsFromFilePath(location, options);
|
||||
return client;
|
||||
}
|
||||
/**
|
||||
* Attempts to load default credentials from a file at the given path..
|
||||
* @param filePath The path to the file to read.
|
||||
* @returns Promise that resolves with the OAuth2Client
|
||||
* @api private
|
||||
*/
|
||||
async _getApplicationCredentialsFromFilePath(filePath, options = {}) {
|
||||
// Make sure the path looks like a string.
|
||||
if (!filePath || filePath.length === 0) {
|
||||
throw new Error('The file path is invalid.');
|
||||
}
|
||||
// Make sure there is a file at the path. lstatSync will throw if there is
|
||||
// nothing there.
|
||||
try {
|
||||
// Resolve path to actual file in case of symlink. Expect a thrown error
|
||||
// if not resolvable.
|
||||
filePath = fs.realpathSync(filePath);
|
||||
if (!fs.lstatSync(filePath).isFile()) {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
err.message = `The file at ${filePath} does not exist, or it is not a file. ${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
// Now open a read stream on the file, and parse it.
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
return this.fromStream(readStream, options);
|
||||
}
|
||||
/**
|
||||
* Create a credentials instance using the given input options.
|
||||
* @param json The input object.
|
||||
* @param options The JWT or UserRefresh options for the client
|
||||
* @returns JWT or UserRefresh Client with data
|
||||
*/
|
||||
fromJSON(json, options) {
|
||||
let client;
|
||||
if (!json) {
|
||||
throw new Error('Must pass in a JSON object containing the Google auth settings.');
|
||||
}
|
||||
options = options || {};
|
||||
if (json.type === 'authorized_user') {
|
||||
client = new refreshclient_1.UserRefreshClient(options);
|
||||
client.fromJSON(json);
|
||||
}
|
||||
else if (json.type === baseexternalclient_1.EXTERNAL_ACCOUNT_TYPE) {
|
||||
client = externalclient_1.ExternalAccountClient.fromJSON(json, options);
|
||||
client.scopes = this.getAnyScopes();
|
||||
}
|
||||
else {
|
||||
options.scopes = this.scopes;
|
||||
client = new jwtclient_1.JWT(options);
|
||||
this.setGapicJWTValues(client);
|
||||
client.fromJSON(json);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
/**
|
||||
* Return a JWT or UserRefreshClient from JavaScript object, caching both the
|
||||
* object used to instantiate and the client.
|
||||
* @param json The input object.
|
||||
* @param options The JWT or UserRefresh options for the client
|
||||
* @returns JWT or UserRefresh Client with data
|
||||
*/
|
||||
_cacheClientFromJSON(json, options) {
|
||||
let client;
|
||||
// create either a UserRefreshClient or JWT client.
|
||||
options = options || {};
|
||||
if (json.type === 'authorized_user') {
|
||||
client = new refreshclient_1.UserRefreshClient(options);
|
||||
client.fromJSON(json);
|
||||
}
|
||||
else if (json.type === baseexternalclient_1.EXTERNAL_ACCOUNT_TYPE) {
|
||||
client = externalclient_1.ExternalAccountClient.fromJSON(json, options);
|
||||
client.scopes = this.getAnyScopes();
|
||||
}
|
||||
else {
|
||||
options.scopes = this.scopes;
|
||||
client = new jwtclient_1.JWT(options);
|
||||
this.setGapicJWTValues(client);
|
||||
client.fromJSON(json);
|
||||
}
|
||||
// cache both raw data used to instantiate client and client itself.
|
||||
this.jsonContent = json;
|
||||
this.cachedCredential = client;
|
||||
return this.cachedCredential;
|
||||
}
|
||||
fromStream(inputStream, optionsOrCallback = {}, callback) {
|
||||
let options = {};
|
||||
if (typeof optionsOrCallback === 'function') {
|
||||
callback = optionsOrCallback;
|
||||
}
|
||||
else {
|
||||
options = optionsOrCallback;
|
||||
}
|
||||
if (callback) {
|
||||
this.fromStreamAsync(inputStream, options).then(r => callback(null, r), callback);
|
||||
}
|
||||
else {
|
||||
return this.fromStreamAsync(inputStream, options);
|
||||
}
|
||||
}
|
||||
fromStreamAsync(inputStream, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!inputStream) {
|
||||
throw new Error('Must pass in a stream containing the Google auth settings.');
|
||||
}
|
||||
let s = '';
|
||||
inputStream
|
||||
.setEncoding('utf8')
|
||||
.on('error', reject)
|
||||
.on('data', chunk => (s += chunk))
|
||||
.on('end', () => {
|
||||
try {
|
||||
try {
|
||||
const data = JSON.parse(s);
|
||||
const r = this._cacheClientFromJSON(data, options);
|
||||
return resolve(r);
|
||||
}
|
||||
catch (err) {
|
||||
// If we failed parsing this.keyFileName, assume that it
|
||||
// is a PEM or p12 certificate:
|
||||
if (!this.keyFilename)
|
||||
throw err;
|
||||
const client = new jwtclient_1.JWT({
|
||||
...this.clientOptions,
|
||||
keyFile: this.keyFilename,
|
||||
});
|
||||
this.cachedCredential = client;
|
||||
this.setGapicJWTValues(client);
|
||||
return resolve(client);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Create a credentials instance using the given API key string.
|
||||
* @param apiKey The API key string
|
||||
* @param options An optional options object.
|
||||
* @returns A JWT loaded from the key
|
||||
*/
|
||||
fromAPIKey(apiKey, options) {
|
||||
options = options || {};
|
||||
const client = new jwtclient_1.JWT(options);
|
||||
client.fromAPIKey(apiKey);
|
||||
return client;
|
||||
}
|
||||
/**
|
||||
* Determines whether the current operating system is Windows.
|
||||
* @api private
|
||||
*/
|
||||
_isWindows() {
|
||||
const sys = os.platform();
|
||||
if (sys && sys.length >= 3) {
|
||||
if (sys.substring(0, 3).toLowerCase() === 'win') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Run the Google Cloud SDK command that prints the default project ID
|
||||
*/
|
||||
async getDefaultServiceProjectId() {
|
||||
return new Promise(resolve => {
|
||||
child_process_1.exec('gcloud config config-helper --format json', (err, stdout) => {
|
||||
if (!err && stdout) {
|
||||
try {
|
||||
const projectId = JSON.parse(stdout).configuration.properties.core.project;
|
||||
resolve(projectId);
|
||||
return;
|
||||
}
|
||||
catch (e) {
|
||||
// ignore errors
|
||||
}
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Loads the project id from environment variables.
|
||||
* @api private
|
||||
*/
|
||||
getProductionProjectId() {
|
||||
return (process.env['GCLOUD_PROJECT'] ||
|
||||
process.env['GOOGLE_CLOUD_PROJECT'] ||
|
||||
process.env['gcloud_project'] ||
|
||||
process.env['google_cloud_project']);
|
||||
}
|
||||
/**
|
||||
* Loads the project id from the GOOGLE_APPLICATION_CREDENTIALS json file.
|
||||
* @api private
|
||||
*/
|
||||
async getFileProjectId() {
|
||||
if (this.cachedCredential) {
|
||||
// Try to read the project ID from the cached credentials file
|
||||
return this.cachedCredential.projectId;
|
||||
}
|
||||
// Ensure the projectId is loaded from the keyFile if available.
|
||||
if (this.keyFilename) {
|
||||
const creds = await this.getClient();
|
||||
if (creds && creds.projectId) {
|
||||
return creds.projectId;
|
||||
}
|
||||
}
|
||||
// Try to load a credentials file and read its project ID
|
||||
const r = await this._tryGetApplicationCredentialsFromEnvironmentVariable();
|
||||
if (r) {
|
||||
return r.projectId;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Gets the project ID from external account client if available.
|
||||
*/
|
||||
async getExternalAccountClientProjectId() {
|
||||
if (!this.jsonContent || this.jsonContent.type !== baseexternalclient_1.EXTERNAL_ACCOUNT_TYPE) {
|
||||
return null;
|
||||
}
|
||||
const creds = await this.getClient();
|
||||
// Do not suppress the underlying error, as the error could contain helpful
|
||||
// information for debugging and fixing. This is especially true for
|
||||
// external account creds as in order to get the project ID, the following
|
||||
// operations have to succeed:
|
||||
// 1. Valid credentials file should be supplied.
|
||||
// 2. Ability to retrieve access tokens from STS token exchange API.
|
||||
// 3. Ability to exchange for service account impersonated credentials (if
|
||||
// enabled).
|
||||
// 4. Ability to get project info using the access token from step 2 or 3.
|
||||
// Without surfacing the error, it is harder for developers to determine
|
||||
// which step went wrong.
|
||||
return await creds.getProjectId();
|
||||
}
|
||||
/**
|
||||
* Gets the Compute Engine project ID if it can be inferred.
|
||||
*/
|
||||
async getGCEProjectId() {
|
||||
try {
|
||||
const r = await gcpMetadata.project('project-id');
|
||||
return r;
|
||||
}
|
||||
catch (e) {
|
||||
// Ignore any errors
|
||||
return null;
|
||||
}
|
||||
}
|
||||
getCredentials(callback) {
|
||||
if (callback) {
|
||||
this.getCredentialsAsync().then(r => callback(null, r), callback);
|
||||
}
|
||||
else {
|
||||
return this.getCredentialsAsync();
|
||||
}
|
||||
}
|
||||
async getCredentialsAsync() {
|
||||
await this.getClient();
|
||||
if (this.jsonContent) {
|
||||
const credential = {
|
||||
client_email: this.jsonContent.client_email,
|
||||
private_key: this.jsonContent.private_key,
|
||||
};
|
||||
return credential;
|
||||
}
|
||||
const isGCE = await this._checkIsGCE();
|
||||
if (!isGCE) {
|
||||
throw new Error('Unknown error.');
|
||||
}
|
||||
// For GCE, return the service account details from the metadata server
|
||||
// NOTE: The trailing '/' at the end of service-accounts/ is very important!
|
||||
// The GCF metadata server doesn't respect querystring params if this / is
|
||||
// not included.
|
||||
const data = await gcpMetadata.instance({
|
||||
property: 'service-accounts/',
|
||||
params: { recursive: 'true' },
|
||||
});
|
||||
if (!data || !data.default || !data.default.email) {
|
||||
throw new Error('Failure from metadata server.');
|
||||
}
|
||||
return { client_email: data.default.email };
|
||||
}
|
||||
/**
|
||||
* Automatically obtain a client based on the provided configuration. If no
|
||||
* options were passed, use Application Default Credentials.
|
||||
*/
|
||||
async getClient(options) {
|
||||
if (options) {
|
||||
throw new Error('Passing options to getClient is forbidden in v5.0.0. Use new GoogleAuth(opts) instead.');
|
||||
}
|
||||
if (!this.cachedCredential) {
|
||||
if (this.jsonContent) {
|
||||
this._cacheClientFromJSON(this.jsonContent, this.clientOptions);
|
||||
}
|
||||
else if (this.keyFilename) {
|
||||
const filePath = path.resolve(this.keyFilename);
|
||||
const stream = fs.createReadStream(filePath);
|
||||
await this.fromStreamAsync(stream, this.clientOptions);
|
||||
}
|
||||
else {
|
||||
await this.getApplicationDefaultAsync(this.clientOptions);
|
||||
}
|
||||
}
|
||||
return this.cachedCredential;
|
||||
}
|
||||
/**
|
||||
* Creates a client which will fetch an ID token for authorization.
|
||||
* @param targetAudience the audience for the fetched ID token.
|
||||
* @returns IdTokenClient for making HTTP calls authenticated with ID tokens.
|
||||
*/
|
||||
async getIdTokenClient(targetAudience) {
|
||||
const client = await this.getClient();
|
||||
if (!('fetchIdToken' in client)) {
|
||||
throw new Error('Cannot fetch ID token in this environment, use GCE or set the GOOGLE_APPLICATION_CREDENTIALS environment variable to a service account credentials JSON file.');
|
||||
}
|
||||
return new idtokenclient_1.IdTokenClient({ targetAudience, idTokenProvider: client });
|
||||
}
|
||||
/**
|
||||
* Automatically obtain application default credentials, and return
|
||||
* an access token for making requests.
|
||||
*/
|
||||
async getAccessToken() {
|
||||
const client = await this.getClient();
|
||||
return (await client.getAccessToken()).token;
|
||||
}
|
||||
/**
|
||||
* Obtain the HTTP headers that will provide authorization for a given
|
||||
* request.
|
||||
*/
|
||||
async getRequestHeaders(url) {
|
||||
const client = await this.getClient();
|
||||
return client.getRequestHeaders(url);
|
||||
}
|
||||
/**
|
||||
* Obtain credentials for a request, then attach the appropriate headers to
|
||||
* the request options.
|
||||
* @param opts Axios or Request options on which to attach the headers
|
||||
*/
|
||||
async authorizeRequest(opts) {
|
||||
opts = opts || {};
|
||||
const url = opts.url || opts.uri;
|
||||
const client = await this.getClient();
|
||||
const headers = await client.getRequestHeaders(url);
|
||||
opts.headers = Object.assign(opts.headers || {}, headers);
|
||||
return opts;
|
||||
}
|
||||
/**
|
||||
* Automatically obtain application default credentials, and make an
|
||||
* HTTP request using the given options.
|
||||
* @param opts Axios request options for the HTTP request.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async request(opts) {
|
||||
const client = await this.getClient();
|
||||
return client.request(opts);
|
||||
}
|
||||
/**
|
||||
* Determine the compute environment in which the code is running.
|
||||
*/
|
||||
getEnv() {
|
||||
return envDetect_1.getEnv();
|
||||
}
|
||||
/**
|
||||
* Sign the given data with the current private key, or go out
|
||||
* to the IAM API to sign it.
|
||||
* @param data The data to be signed.
|
||||
*/
|
||||
async sign(data) {
|
||||
const client = await this.getClient();
|
||||
const crypto = crypto_1.createCrypto();
|
||||
if (client instanceof jwtclient_1.JWT && client.key) {
|
||||
const sign = await crypto.sign(client.key, data);
|
||||
return sign;
|
||||
}
|
||||
// signBlob requires a service account email and the underlying
|
||||
// access token to have iam.serviceAccounts.signBlob permission
|
||||
// on the specified resource name.
|
||||
// The "Service Account Token Creator" role should cover this.
|
||||
// As a result external account credentials can support this
|
||||
// operation when service account impersonation is enabled.
|
||||
if (client instanceof baseexternalclient_1.BaseExternalAccountClient &&
|
||||
client.getServiceAccountEmail()) {
|
||||
return this.signBlob(crypto, client.getServiceAccountEmail(), data);
|
||||
}
|
||||
const projectId = await this.getProjectId();
|
||||
if (!projectId) {
|
||||
throw new Error('Cannot sign data without a project ID.');
|
||||
}
|
||||
const creds = await this.getCredentials();
|
||||
if (!creds.client_email) {
|
||||
throw new Error('Cannot sign data without `client_email`.');
|
||||
}
|
||||
return this.signBlob(crypto, creds.client_email, data);
|
||||
}
|
||||
async signBlob(crypto, emailOrUniqueId, data) {
|
||||
const url = 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/' +
|
||||
`${emailOrUniqueId}:signBlob`;
|
||||
const res = await this.request({
|
||||
method: 'POST',
|
||||
url,
|
||||
data: {
|
||||
payload: crypto.encodeBase64StringUtf8(data),
|
||||
},
|
||||
});
|
||||
return res.data.signedBlob;
|
||||
}
|
||||
}
|
||||
exports.GoogleAuth = GoogleAuth;
|
||||
/**
|
||||
* Export DefaultTransporter as a static property of the class.
|
||||
*/
|
||||
GoogleAuth.DefaultTransporter = transporters_1.DefaultTransporter;
|
||||
//# sourceMappingURL=googleauth.js.map
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
export interface RequestMetadata {
|
||||
'x-goog-iam-authority-selector': string;
|
||||
'x-goog-iam-authorization-token': string;
|
||||
}
|
||||
export declare class IAMAuth {
|
||||
selector: string;
|
||||
token: string;
|
||||
/**
|
||||
* IAM credentials.
|
||||
*
|
||||
* @param selector the iam authority selector
|
||||
* @param token the token
|
||||
* @constructor
|
||||
*/
|
||||
constructor(selector: string, token: string);
|
||||
/**
|
||||
* Acquire the HTTP headers required to make an authenticated request.
|
||||
*/
|
||||
getRequestHeaders(): {
|
||||
'x-goog-iam-authority-selector': string;
|
||||
'x-goog-iam-authorization-token': string;
|
||||
};
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
"use strict";
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.IAMAuth = void 0;
|
||||
class IAMAuth {
|
||||
/**
|
||||
* IAM credentials.
|
||||
*
|
||||
* @param selector the iam authority selector
|
||||
* @param token the token
|
||||
* @constructor
|
||||
*/
|
||||
constructor(selector, token) {
|
||||
this.selector = selector;
|
||||
this.token = token;
|
||||
this.selector = selector;
|
||||
this.token = token;
|
||||
}
|
||||
/**
|
||||
* Acquire the HTTP headers required to make an authenticated request.
|
||||
*/
|
||||
getRequestHeaders() {
|
||||
return {
|
||||
'x-goog-iam-authority-selector': this.selector,
|
||||
'x-goog-iam-authorization-token': this.token,
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.IAMAuth = IAMAuth;
|
||||
//# sourceMappingURL=iam.js.map
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import { BaseExternalAccountClient, BaseExternalAccountClientOptions } from './baseexternalclient';
|
||||
import { RefreshOptions } from './oauth2client';
|
||||
declare type SubjectTokenFormatType = 'json' | 'text';
|
||||
/**
|
||||
* Url-sourced/file-sourced credentials json interface.
|
||||
* This is used for K8s and Azure workloads.
|
||||
*/
|
||||
export interface IdentityPoolClientOptions extends BaseExternalAccountClientOptions {
|
||||
credential_source: {
|
||||
file?: string;
|
||||
url?: string;
|
||||
headers?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
format?: {
|
||||
type: SubjectTokenFormatType;
|
||||
subject_token_field_name?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Defines the Url-sourced and file-sourced external account clients mainly
|
||||
* used for K8s and Azure workloads.
|
||||
*/
|
||||
export declare class IdentityPoolClient extends BaseExternalAccountClient {
|
||||
private readonly file?;
|
||||
private readonly url?;
|
||||
private readonly headers?;
|
||||
private readonly formatType;
|
||||
private readonly formatSubjectTokenFieldName?;
|
||||
/**
|
||||
* Instantiate an IdentityPoolClient instance using the provided JSON
|
||||
* object loaded from an external account credentials file.
|
||||
* An error is thrown if the credential is not a valid file-sourced or
|
||||
* url-sourced credential or a workforce pool user project is provided
|
||||
* with a non workforce audience.
|
||||
* @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.
|
||||
*/
|
||||
constructor(options: IdentityPoolClientOptions, additionalOptions?: RefreshOptions);
|
||||
/**
|
||||
* Triggered when a external subject token is needed to be exchanged for a GCP
|
||||
* access token via GCP STS endpoint.
|
||||
* This uses the `options.credential_source` object to figure out how
|
||||
* to retrieve the token using the current environment. In this case,
|
||||
* this either retrieves the local credential from a file location (k8s
|
||||
* workload) or by sending a GET request to a local metadata server (Azure
|
||||
* workloads).
|
||||
* @return A promise that resolves with the external subject token.
|
||||
*/
|
||||
retrieveSubjectToken(): Promise<string>;
|
||||
/**
|
||||
* Looks up the external subject token in the file path provided and
|
||||
* resolves with that token.
|
||||
* @param file The file path where the external credential is located.
|
||||
* @param formatType The token file or URL response type (JSON or text).
|
||||
* @param formatSubjectTokenFieldName For JSON response types, this is the
|
||||
* subject_token field name. For Azure, this is access_token. For text
|
||||
* response types, this is ignored.
|
||||
* @return A promise that resolves with the external subject token.
|
||||
*/
|
||||
private getTokenFromFile;
|
||||
/**
|
||||
* Sends a GET request to the URL provided and resolves with the returned
|
||||
* external subject token.
|
||||
* @param url The URL to call to retrieve the subject token. This is typically
|
||||
* a local metadata server.
|
||||
* @param formatType The token file or URL response type (JSON or text).
|
||||
* @param formatSubjectTokenFieldName For JSON response types, this is the
|
||||
* subject_token field name. For Azure, this is access_token. For text
|
||||
* response types, this is ignored.
|
||||
* @param headers The optional additional headers to send with the request to
|
||||
* the metadata server url.
|
||||
* @return A promise that resolves with the external subject token.
|
||||
*/
|
||||
private getTokenFromUrl;
|
||||
}
|
||||
export {};
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
"use strict";
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
var _a, _b, _c;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.IdentityPoolClient = void 0;
|
||||
const fs = require("fs");
|
||||
const util_1 = require("util");
|
||||
const baseexternalclient_1 = require("./baseexternalclient");
|
||||
// fs.readfile is undefined in browser karma tests causing
|
||||
// `npm run browser-test` to fail as test.oauth2.ts imports this file via
|
||||
// src/index.ts.
|
||||
// Fallback to void function to avoid promisify throwing a TypeError.
|
||||
const readFile = util_1.promisify((_a = fs.readFile) !== null && _a !== void 0 ? _a : (() => { }));
|
||||
const realpath = util_1.promisify((_b = fs.realpath) !== null && _b !== void 0 ? _b : (() => { }));
|
||||
const lstat = util_1.promisify((_c = fs.lstat) !== null && _c !== void 0 ? _c : (() => { }));
|
||||
/**
|
||||
* Defines the Url-sourced and file-sourced external account clients mainly
|
||||
* used for K8s and Azure workloads.
|
||||
*/
|
||||
class IdentityPoolClient extends baseexternalclient_1.BaseExternalAccountClient {
|
||||
/**
|
||||
* Instantiate an IdentityPoolClient instance using the provided JSON
|
||||
* object loaded from an external account credentials file.
|
||||
* An error is thrown if the credential is not a valid file-sourced or
|
||||
* url-sourced credential or a workforce pool user project is provided
|
||||
* with a non workforce audience.
|
||||
* @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.
|
||||
*/
|
||||
constructor(options, additionalOptions) {
|
||||
var _a, _b;
|
||||
super(options, additionalOptions);
|
||||
this.file = options.credential_source.file;
|
||||
this.url = options.credential_source.url;
|
||||
this.headers = options.credential_source.headers;
|
||||
if (!this.file && !this.url) {
|
||||
throw new Error('No valid Identity Pool "credential_source" provided');
|
||||
}
|
||||
// Text is the default format type.
|
||||
this.formatType = ((_a = options.credential_source.format) === null || _a === void 0 ? void 0 : _a.type) || 'text';
|
||||
this.formatSubjectTokenFieldName = (_b = options.credential_source.format) === null || _b === void 0 ? void 0 : _b.subject_token_field_name;
|
||||
if (this.formatType !== 'json' && this.formatType !== 'text') {
|
||||
throw new Error(`Invalid credential_source format "${this.formatType}"`);
|
||||
}
|
||||
if (this.formatType === 'json' && !this.formatSubjectTokenFieldName) {
|
||||
throw new Error('Missing subject_token_field_name for JSON credential_source format');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Triggered when a external subject token is needed to be exchanged for a GCP
|
||||
* access token via GCP STS endpoint.
|
||||
* This uses the `options.credential_source` object to figure out how
|
||||
* to retrieve the token using the current environment. In this case,
|
||||
* this either retrieves the local credential from a file location (k8s
|
||||
* workload) or by sending a GET request to a local metadata server (Azure
|
||||
* workloads).
|
||||
* @return A promise that resolves with the external subject token.
|
||||
*/
|
||||
async retrieveSubjectToken() {
|
||||
if (this.file) {
|
||||
return await this.getTokenFromFile(this.file, this.formatType, this.formatSubjectTokenFieldName);
|
||||
}
|
||||
return await this.getTokenFromUrl(this.url, this.formatType, this.formatSubjectTokenFieldName, this.headers);
|
||||
}
|
||||
/**
|
||||
* Looks up the external subject token in the file path provided and
|
||||
* resolves with that token.
|
||||
* @param file The file path where the external credential is located.
|
||||
* @param formatType The token file or URL response type (JSON or text).
|
||||
* @param formatSubjectTokenFieldName For JSON response types, this is the
|
||||
* subject_token field name. For Azure, this is access_token. For text
|
||||
* response types, this is ignored.
|
||||
* @return A promise that resolves with the external subject token.
|
||||
*/
|
||||
async getTokenFromFile(filePath, formatType, formatSubjectTokenFieldName) {
|
||||
// Make sure there is a file at the path. lstatSync will throw if there is
|
||||
// nothing there.
|
||||
try {
|
||||
// Resolve path to actual file in case of symlink. Expect a thrown error
|
||||
// if not resolvable.
|
||||
filePath = await realpath(filePath);
|
||||
if (!(await lstat(filePath)).isFile()) {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
err.message = `The file at ${filePath} does not exist, or it is not a file. ${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
let subjectToken;
|
||||
const rawText = await readFile(filePath, { encoding: 'utf8' });
|
||||
if (formatType === 'text') {
|
||||
subjectToken = rawText;
|
||||
}
|
||||
else if (formatType === 'json' && formatSubjectTokenFieldName) {
|
||||
const json = JSON.parse(rawText);
|
||||
subjectToken = json[formatSubjectTokenFieldName];
|
||||
}
|
||||
if (!subjectToken) {
|
||||
throw new Error('Unable to parse the subject_token from the credential_source file');
|
||||
}
|
||||
return subjectToken;
|
||||
}
|
||||
/**
|
||||
* Sends a GET request to the URL provided and resolves with the returned
|
||||
* external subject token.
|
||||
* @param url The URL to call to retrieve the subject token. This is typically
|
||||
* a local metadata server.
|
||||
* @param formatType The token file or URL response type (JSON or text).
|
||||
* @param formatSubjectTokenFieldName For JSON response types, this is the
|
||||
* subject_token field name. For Azure, this is access_token. For text
|
||||
* response types, this is ignored.
|
||||
* @param headers The optional additional headers to send with the request to
|
||||
* the metadata server url.
|
||||
* @return A promise that resolves with the external subject token.
|
||||
*/
|
||||
async getTokenFromUrl(url, formatType, formatSubjectTokenFieldName, headers) {
|
||||
const opts = {
|
||||
url,
|
||||
method: 'GET',
|
||||
headers,
|
||||
responseType: formatType,
|
||||
};
|
||||
let subjectToken;
|
||||
if (formatType === 'text') {
|
||||
const response = await this.transporter.request(opts);
|
||||
subjectToken = response.data;
|
||||
}
|
||||
else if (formatType === 'json' && formatSubjectTokenFieldName) {
|
||||
const response = await this.transporter.request(opts);
|
||||
subjectToken = response.data[formatSubjectTokenFieldName];
|
||||
}
|
||||
if (!subjectToken) {
|
||||
throw new Error('Unable to parse the subject_token from the credential_source URL');
|
||||
}
|
||||
return subjectToken;
|
||||
}
|
||||
}
|
||||
exports.IdentityPoolClient = IdentityPoolClient;
|
||||
//# sourceMappingURL=identitypoolclient.js.map
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { OAuth2Client, RequestMetadataResponse } from './oauth2client';
|
||||
export interface IdTokenOptions {
|
||||
/**
|
||||
* The client to make the request to fetch an ID token.
|
||||
*/
|
||||
idTokenProvider: IdTokenProvider;
|
||||
/**
|
||||
* The audience to use when requesting an ID token.
|
||||
*/
|
||||
targetAudience: string;
|
||||
}
|
||||
export interface IdTokenProvider {
|
||||
fetchIdToken: (targetAudience: string) => Promise<string>;
|
||||
}
|
||||
export declare class IdTokenClient extends OAuth2Client {
|
||||
targetAudience: string;
|
||||
idTokenProvider: IdTokenProvider;
|
||||
/**
|
||||
* Google ID Token client
|
||||
*
|
||||
* Retrieve access token from the metadata server.
|
||||
* See: https://developers.google.com/compute/docs/authentication
|
||||
*/
|
||||
constructor(options: IdTokenOptions);
|
||||
protected getRequestMetadataAsync(url?: string | null): Promise<RequestMetadataResponse>;
|
||||
private getIdTokenExpiryDate;
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
"use strict";
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.IdTokenClient = void 0;
|
||||
const oauth2client_1 = require("./oauth2client");
|
||||
class IdTokenClient extends oauth2client_1.OAuth2Client {
|
||||
/**
|
||||
* Google ID Token client
|
||||
*
|
||||
* Retrieve access token from the metadata server.
|
||||
* See: https://developers.google.com/compute/docs/authentication
|
||||
*/
|
||||
constructor(options) {
|
||||
super();
|
||||
this.targetAudience = options.targetAudience;
|
||||
this.idTokenProvider = options.idTokenProvider;
|
||||
}
|
||||
async getRequestMetadataAsync(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
url) {
|
||||
if (!this.credentials.id_token ||
|
||||
(this.credentials.expiry_date || 0) < Date.now()) {
|
||||
const idToken = await this.idTokenProvider.fetchIdToken(this.targetAudience);
|
||||
this.credentials = {
|
||||
id_token: idToken,
|
||||
expiry_date: this.getIdTokenExpiryDate(idToken),
|
||||
};
|
||||
}
|
||||
const headers = {
|
||||
Authorization: 'Bearer ' + this.credentials.id_token,
|
||||
};
|
||||
return { headers };
|
||||
}
|
||||
getIdTokenExpiryDate(idToken) {
|
||||
const payloadB64 = idToken.split('.')[1];
|
||||
if (payloadB64) {
|
||||
const payload = JSON.parse(Buffer.from(payloadB64, 'base64').toString('ascii'));
|
||||
return payload.exp * 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.IdTokenClient = IdTokenClient;
|
||||
//# sourceMappingURL=idtokenclient.js.map
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { GetTokenResponse, OAuth2Client, RefreshOptions } from './oauth2client';
|
||||
import { AuthClient } from './authclient';
|
||||
export interface ImpersonatedOptions extends RefreshOptions {
|
||||
/**
|
||||
* Client used to perform exchange for impersonated client.
|
||||
*/
|
||||
sourceClient?: AuthClient;
|
||||
/**
|
||||
* The service account to impersonate.
|
||||
*/
|
||||
targetPrincipal?: string;
|
||||
/**
|
||||
* Scopes to request during the authorization grant.
|
||||
*/
|
||||
targetScopes?: string[];
|
||||
/**
|
||||
* The chained list of delegates required to grant the final access_token.
|
||||
*/
|
||||
delegates?: string[];
|
||||
/**
|
||||
* Number of seconds the delegated credential should be valid.
|
||||
*/
|
||||
lifetime?: number | 3600;
|
||||
/**
|
||||
* API endpoint to fetch token from.
|
||||
*/
|
||||
endpoint?: string;
|
||||
}
|
||||
export interface TokenResponse {
|
||||
accessToken: string;
|
||||
expireTime: string;
|
||||
}
|
||||
export declare class Impersonated extends OAuth2Client {
|
||||
private sourceClient;
|
||||
private targetPrincipal;
|
||||
private targetScopes;
|
||||
private delegates;
|
||||
private lifetime;
|
||||
private endpoint;
|
||||
/**
|
||||
* Impersonated service account credentials.
|
||||
*
|
||||
* Create a new access token by impersonating another service account.
|
||||
*
|
||||
* Impersonated Credentials allowing credentials issued to a user or
|
||||
* service account to impersonate another. The source project using
|
||||
* Impersonated Credentials must enable the "IAMCredentials" API.
|
||||
* Also, the target service account must grant the orginating principal
|
||||
* the "Service Account Token Creator" IAM role.
|
||||
*
|
||||
* @param {object} options - The configuration object.
|
||||
* @param {object} [options.sourceClient] the source credential used as to
|
||||
* acquire the impersonated credentials.
|
||||
* @param {string} [options.targetPrincipal] the service account to
|
||||
* impersonate.
|
||||
* @param {string[]} [options.delegates] the chained list of delegates
|
||||
* required to grant the final access_token. If set, the sequence of
|
||||
* identities must have "Service Account Token Creator" capability granted to
|
||||
* the preceding identity. For example, if set to [serviceAccountB,
|
||||
* serviceAccountC], the sourceCredential must have the Token Creator role on
|
||||
* serviceAccountB. serviceAccountB must have the Token Creator on
|
||||
* serviceAccountC. Finally, C must have Token Creator on target_principal.
|
||||
* If left unset, sourceCredential must have that role on targetPrincipal.
|
||||
* @param {string[]} [options.targetScopes] scopes to request during the
|
||||
* authorization grant.
|
||||
* @param {number} [options.lifetime] number of seconds the delegated
|
||||
* credential should be valid for up to 3600 seconds by default, or 43,200
|
||||
* seconds by extending the token's lifetime, see:
|
||||
* https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oauth
|
||||
* @param {string} [options.endpoint] api endpoint override.
|
||||
*/
|
||||
constructor(options?: ImpersonatedOptions);
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
* @param refreshToken Unused parameter
|
||||
*/
|
||||
protected refreshToken(refreshToken?: string | null): Promise<GetTokenResponse>;
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Impersonated = void 0;
|
||||
const oauth2client_1 = require("./oauth2client");
|
||||
class Impersonated extends oauth2client_1.OAuth2Client {
|
||||
/**
|
||||
* Impersonated service account credentials.
|
||||
*
|
||||
* Create a new access token by impersonating another service account.
|
||||
*
|
||||
* Impersonated Credentials allowing credentials issued to a user or
|
||||
* service account to impersonate another. The source project using
|
||||
* Impersonated Credentials must enable the "IAMCredentials" API.
|
||||
* Also, the target service account must grant the orginating principal
|
||||
* the "Service Account Token Creator" IAM role.
|
||||
*
|
||||
* @param {object} options - The configuration object.
|
||||
* @param {object} [options.sourceClient] the source credential used as to
|
||||
* acquire the impersonated credentials.
|
||||
* @param {string} [options.targetPrincipal] the service account to
|
||||
* impersonate.
|
||||
* @param {string[]} [options.delegates] the chained list of delegates
|
||||
* required to grant the final access_token. If set, the sequence of
|
||||
* identities must have "Service Account Token Creator" capability granted to
|
||||
* the preceding identity. For example, if set to [serviceAccountB,
|
||||
* serviceAccountC], the sourceCredential must have the Token Creator role on
|
||||
* serviceAccountB. serviceAccountB must have the Token Creator on
|
||||
* serviceAccountC. Finally, C must have Token Creator on target_principal.
|
||||
* If left unset, sourceCredential must have that role on targetPrincipal.
|
||||
* @param {string[]} [options.targetScopes] scopes to request during the
|
||||
* authorization grant.
|
||||
* @param {number} [options.lifetime] number of seconds the delegated
|
||||
* credential should be valid for up to 3600 seconds by default, or 43,200
|
||||
* seconds by extending the token's lifetime, see:
|
||||
* https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oauth
|
||||
* @param {string} [options.endpoint] api endpoint override.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
var _a, _b, _c, _d, _e, _f;
|
||||
super(options);
|
||||
this.credentials = {
|
||||
expiry_date: 1,
|
||||
refresh_token: 'impersonated-placeholder',
|
||||
};
|
||||
this.sourceClient = (_a = options.sourceClient) !== null && _a !== void 0 ? _a : new oauth2client_1.OAuth2Client();
|
||||
this.targetPrincipal = (_b = options.targetPrincipal) !== null && _b !== void 0 ? _b : '';
|
||||
this.delegates = (_c = options.delegates) !== null && _c !== void 0 ? _c : [];
|
||||
this.targetScopes = (_d = options.targetScopes) !== null && _d !== void 0 ? _d : [];
|
||||
this.lifetime = (_e = options.lifetime) !== null && _e !== void 0 ? _e : 3600;
|
||||
this.endpoint = (_f = options.endpoint) !== null && _f !== void 0 ? _f : 'https://iamcredentials.googleapis.com';
|
||||
}
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
* @param refreshToken Unused parameter
|
||||
*/
|
||||
async refreshToken(refreshToken) {
|
||||
var _a, _b, _c, _d, _e, _f;
|
||||
try {
|
||||
await this.sourceClient.getAccessToken();
|
||||
const name = 'projects/-/serviceAccounts/' + this.targetPrincipal;
|
||||
const u = `${this.endpoint}/v1/${name}:generateAccessToken`;
|
||||
const body = {
|
||||
delegates: this.delegates,
|
||||
scope: this.targetScopes,
|
||||
lifetime: this.lifetime + 's',
|
||||
};
|
||||
const res = await this.sourceClient.request({
|
||||
url: u,
|
||||
data: body,
|
||||
method: 'POST',
|
||||
});
|
||||
const tokenResponse = res.data;
|
||||
this.credentials.access_token = tokenResponse.accessToken;
|
||||
this.credentials.expiry_date = Date.parse(tokenResponse.expireTime);
|
||||
return {
|
||||
tokens: this.credentials,
|
||||
res,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
const status = (_c = (_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.status;
|
||||
const message = (_f = (_e = (_d = error === null || error === void 0 ? void 0 : error.response) === null || _d === void 0 ? void 0 : _d.data) === null || _e === void 0 ? void 0 : _e.error) === null || _f === void 0 ? void 0 : _f.message;
|
||||
if (status && message) {
|
||||
error.message = `${status}: unable to impersonate: ${message}`;
|
||||
throw error;
|
||||
}
|
||||
else {
|
||||
error.message = `unable to impersonate: ${error}`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.Impersonated = Impersonated;
|
||||
//# sourceMappingURL=impersonated.js.map
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/// <reference types="node" />
|
||||
import * as stream from 'stream';
|
||||
import { JWTInput } from './credentials';
|
||||
import { Headers } from './oauth2client';
|
||||
export interface Claims {
|
||||
[index: string]: string;
|
||||
}
|
||||
export declare class JWTAccess {
|
||||
email?: string | null;
|
||||
key?: string | null;
|
||||
keyId?: string | null;
|
||||
projectId?: string;
|
||||
eagerRefreshThresholdMillis: number;
|
||||
private cache;
|
||||
/**
|
||||
* JWTAccess service account credentials.
|
||||
*
|
||||
* Create a new access token by using the credential to create a new JWT token
|
||||
* that's recognized as the access token.
|
||||
*
|
||||
* @param email the service account email address.
|
||||
* @param key the private key that will be used to sign the token.
|
||||
* @param keyId the ID of the private key used to sign the token.
|
||||
*/
|
||||
constructor(email?: string | null, key?: string | null, keyId?: string | null, eagerRefreshThresholdMillis?: number);
|
||||
/**
|
||||
* Ensures that we're caching a key appropriately, giving precedence to scopes vs. url
|
||||
*
|
||||
* @param url The URI being authorized.
|
||||
* @param scopes The scope or scopes being authorized
|
||||
* @returns A string that returns the cached key.
|
||||
*/
|
||||
getCachedKey(url?: string, scopes?: string | string[]): string;
|
||||
/**
|
||||
* Get a non-expired access token, after refreshing if necessary.
|
||||
*
|
||||
* @param url The URI being authorized.
|
||||
* @param additionalClaims An object with a set of additional claims to
|
||||
* include in the payload.
|
||||
* @returns An object that includes the authorization header.
|
||||
*/
|
||||
getRequestHeaders(url?: string, additionalClaims?: Claims, scopes?: string | string[]): Headers;
|
||||
/**
|
||||
* Returns an expiration time for the JWT token.
|
||||
*
|
||||
* @param iat The issued at time for the JWT.
|
||||
* @returns An expiration time for the JWT.
|
||||
*/
|
||||
private static getExpirationTime;
|
||||
/**
|
||||
* Create a JWTAccess credentials instance using the given input options.
|
||||
* @param json The input object.
|
||||
*/
|
||||
fromJSON(json: JWTInput): void;
|
||||
/**
|
||||
* Create a JWTAccess credentials instance using the given input stream.
|
||||
* @param inputStream The input stream.
|
||||
* @param callback Optional callback.
|
||||
*/
|
||||
fromStream(inputStream: stream.Readable): Promise<void>;
|
||||
fromStream(inputStream: stream.Readable, callback: (err?: Error) => void): void;
|
||||
private fromStreamAsync;
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
"use strict";
|
||||
// Copyright 2015 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.JWTAccess = void 0;
|
||||
const jws = require("jws");
|
||||
const LRU = require("lru-cache");
|
||||
const DEFAULT_HEADER = {
|
||||
alg: 'RS256',
|
||||
typ: 'JWT',
|
||||
};
|
||||
class JWTAccess {
|
||||
/**
|
||||
* JWTAccess service account credentials.
|
||||
*
|
||||
* Create a new access token by using the credential to create a new JWT token
|
||||
* that's recognized as the access token.
|
||||
*
|
||||
* @param email the service account email address.
|
||||
* @param key the private key that will be used to sign the token.
|
||||
* @param keyId the ID of the private key used to sign the token.
|
||||
*/
|
||||
constructor(email, key, keyId, eagerRefreshThresholdMillis) {
|
||||
this.cache = new LRU({
|
||||
max: 500,
|
||||
maxAge: 60 * 60 * 1000,
|
||||
});
|
||||
this.email = email;
|
||||
this.key = key;
|
||||
this.keyId = keyId;
|
||||
this.eagerRefreshThresholdMillis = eagerRefreshThresholdMillis !== null && eagerRefreshThresholdMillis !== void 0 ? eagerRefreshThresholdMillis : 5 * 60 * 1000;
|
||||
}
|
||||
/**
|
||||
* Ensures that we're caching a key appropriately, giving precedence to scopes vs. url
|
||||
*
|
||||
* @param url The URI being authorized.
|
||||
* @param scopes The scope or scopes being authorized
|
||||
* @returns A string that returns the cached key.
|
||||
*/
|
||||
getCachedKey(url, scopes) {
|
||||
let cacheKey = url;
|
||||
if (scopes && Array.isArray(scopes) && scopes.length) {
|
||||
cacheKey = url ? `${url}_${scopes.join('_')}` : `${scopes.join('_')}`;
|
||||
}
|
||||
else if (typeof scopes === 'string') {
|
||||
cacheKey = url ? `${url}_${scopes}` : scopes;
|
||||
}
|
||||
if (!cacheKey) {
|
||||
throw Error('Scopes or url must be provided');
|
||||
}
|
||||
return cacheKey;
|
||||
}
|
||||
/**
|
||||
* Get a non-expired access token, after refreshing if necessary.
|
||||
*
|
||||
* @param url The URI being authorized.
|
||||
* @param additionalClaims An object with a set of additional claims to
|
||||
* include in the payload.
|
||||
* @returns An object that includes the authorization header.
|
||||
*/
|
||||
getRequestHeaders(url, additionalClaims, scopes) {
|
||||
// Return cached authorization headers, unless we are within
|
||||
// eagerRefreshThresholdMillis ms of them expiring:
|
||||
const key = this.getCachedKey(url, scopes);
|
||||
const cachedToken = this.cache.get(key);
|
||||
const now = Date.now();
|
||||
if (cachedToken &&
|
||||
cachedToken.expiration - now > this.eagerRefreshThresholdMillis) {
|
||||
return cachedToken.headers;
|
||||
}
|
||||
const iat = Math.floor(Date.now() / 1000);
|
||||
const exp = JWTAccess.getExpirationTime(iat);
|
||||
let defaultClaims;
|
||||
// Turn scopes into space-separated string
|
||||
if (Array.isArray(scopes)) {
|
||||
scopes = scopes.join(' ');
|
||||
}
|
||||
// If scopes are specified, sign with scopes
|
||||
if (scopes) {
|
||||
defaultClaims = {
|
||||
iss: this.email,
|
||||
sub: this.email,
|
||||
scope: scopes,
|
||||
exp,
|
||||
iat,
|
||||
};
|
||||
}
|
||||
else {
|
||||
defaultClaims = {
|
||||
iss: this.email,
|
||||
sub: this.email,
|
||||
aud: url,
|
||||
exp,
|
||||
iat,
|
||||
};
|
||||
}
|
||||
// if additionalClaims are provided, ensure they do not collide with
|
||||
// other required claims.
|
||||
if (additionalClaims) {
|
||||
for (const claim in defaultClaims) {
|
||||
if (additionalClaims[claim]) {
|
||||
throw new Error(`The '${claim}' property is not allowed when passing additionalClaims. This claim is included in the JWT by default.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const header = this.keyId
|
||||
? { ...DEFAULT_HEADER, kid: this.keyId }
|
||||
: DEFAULT_HEADER;
|
||||
const payload = Object.assign(defaultClaims, additionalClaims);
|
||||
// Sign the jwt and add it to the cache
|
||||
const signedJWT = jws.sign({ header, payload, secret: this.key });
|
||||
const headers = { Authorization: `Bearer ${signedJWT}` };
|
||||
this.cache.set(key, {
|
||||
expiration: exp * 1000,
|
||||
headers,
|
||||
});
|
||||
return headers;
|
||||
}
|
||||
/**
|
||||
* Returns an expiration time for the JWT token.
|
||||
*
|
||||
* @param iat The issued at time for the JWT.
|
||||
* @returns An expiration time for the JWT.
|
||||
*/
|
||||
static getExpirationTime(iat) {
|
||||
const exp = iat + 3600; // 3600 seconds = 1 hour
|
||||
return exp;
|
||||
}
|
||||
/**
|
||||
* Create a JWTAccess credentials instance using the given input options.
|
||||
* @param json The input object.
|
||||
*/
|
||||
fromJSON(json) {
|
||||
if (!json) {
|
||||
throw new Error('Must pass in a JSON object containing the service account auth settings.');
|
||||
}
|
||||
if (!json.client_email) {
|
||||
throw new Error('The incoming JSON object does not contain a client_email field');
|
||||
}
|
||||
if (!json.private_key) {
|
||||
throw new Error('The incoming JSON object does not contain a private_key field');
|
||||
}
|
||||
// Extract the relevant information from the json key file.
|
||||
this.email = json.client_email;
|
||||
this.key = json.private_key;
|
||||
this.keyId = json.private_key_id;
|
||||
this.projectId = json.project_id;
|
||||
}
|
||||
fromStream(inputStream, callback) {
|
||||
if (callback) {
|
||||
this.fromStreamAsync(inputStream).then(() => callback(), callback);
|
||||
}
|
||||
else {
|
||||
return this.fromStreamAsync(inputStream);
|
||||
}
|
||||
}
|
||||
fromStreamAsync(inputStream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!inputStream) {
|
||||
reject(new Error('Must pass in a stream containing the service account auth settings.'));
|
||||
}
|
||||
let s = '';
|
||||
inputStream
|
||||
.setEncoding('utf8')
|
||||
.on('data', chunk => (s += chunk))
|
||||
.on('error', reject)
|
||||
.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(s);
|
||||
this.fromJSON(data);
|
||||
resolve();
|
||||
}
|
||||
catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.JWTAccess = JWTAccess;
|
||||
//# sourceMappingURL=jwtaccess.js.map
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/// <reference types="node" />
|
||||
import { GoogleToken } from 'gtoken';
|
||||
import * as stream from 'stream';
|
||||
import { CredentialBody, Credentials, JWTInput } from './credentials';
|
||||
import { IdTokenProvider } from './idtokenclient';
|
||||
import { GetTokenResponse, OAuth2Client, RefreshOptions, RequestMetadataResponse } from './oauth2client';
|
||||
export interface JWTOptions extends RefreshOptions {
|
||||
email?: string;
|
||||
keyFile?: string;
|
||||
key?: string;
|
||||
keyId?: string;
|
||||
scopes?: string | string[];
|
||||
subject?: string;
|
||||
additionalClaims?: {};
|
||||
}
|
||||
export declare class JWT extends OAuth2Client implements IdTokenProvider {
|
||||
email?: string;
|
||||
keyFile?: string;
|
||||
key?: string;
|
||||
keyId?: string;
|
||||
defaultScopes?: string | string[];
|
||||
scopes?: string | string[];
|
||||
scope?: string;
|
||||
subject?: string;
|
||||
gtoken?: GoogleToken;
|
||||
additionalClaims?: {};
|
||||
useJWTAccessWithScope?: boolean;
|
||||
defaultServicePath?: string;
|
||||
private access?;
|
||||
/**
|
||||
* JWT service account credentials.
|
||||
*
|
||||
* Retrieve access token using gtoken.
|
||||
*
|
||||
* @param email service account email address.
|
||||
* @param keyFile path to private key file.
|
||||
* @param key value of key
|
||||
* @param scopes list of requested scopes or a single scope.
|
||||
* @param subject impersonated account's email address.
|
||||
* @param key_id the ID of the key
|
||||
*/
|
||||
constructor(options: JWTOptions);
|
||||
constructor(email?: string, keyFile?: string, key?: string, scopes?: string | string[], subject?: string, keyId?: string);
|
||||
/**
|
||||
* Creates a copy of the credential with the specified scopes.
|
||||
* @param scopes List of requested scopes or a single scope.
|
||||
* @return The cloned instance.
|
||||
*/
|
||||
createScoped(scopes?: string | string[]): JWT;
|
||||
/**
|
||||
* Obtains the metadata to be sent with the request.
|
||||
*
|
||||
* @param url the URI being authorized.
|
||||
*/
|
||||
protected getRequestMetadataAsync(url?: string | null): Promise<RequestMetadataResponse>;
|
||||
/**
|
||||
* Fetches an ID token.
|
||||
* @param targetAudience the audience for the fetched ID token.
|
||||
*/
|
||||
fetchIdToken(targetAudience: string): Promise<string>;
|
||||
/**
|
||||
* Determine if there are currently scopes available.
|
||||
*/
|
||||
private hasUserScopes;
|
||||
/**
|
||||
* Are there any default or user scopes defined.
|
||||
*/
|
||||
private hasAnyScopes;
|
||||
/**
|
||||
* Get the initial access token using gToken.
|
||||
* @param callback Optional callback.
|
||||
* @returns Promise that resolves with credentials
|
||||
*/
|
||||
authorize(): Promise<Credentials>;
|
||||
authorize(callback: (err: Error | null, result?: Credentials) => void): void;
|
||||
private authorizeAsync;
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
* @param refreshToken ignored
|
||||
* @private
|
||||
*/
|
||||
protected refreshTokenNoCache(refreshToken?: string | null): Promise<GetTokenResponse>;
|
||||
/**
|
||||
* Create a gToken if it doesn't already exist.
|
||||
*/
|
||||
private createGToken;
|
||||
/**
|
||||
* Create a JWT credentials instance using the given input options.
|
||||
* @param json The input object.
|
||||
*/
|
||||
fromJSON(json: JWTInput): void;
|
||||
/**
|
||||
* Create a JWT credentials instance using the given input stream.
|
||||
* @param inputStream The input stream.
|
||||
* @param callback Optional callback.
|
||||
*/
|
||||
fromStream(inputStream: stream.Readable): Promise<void>;
|
||||
fromStream(inputStream: stream.Readable, callback: (err?: Error | null) => void): void;
|
||||
private fromStreamAsync;
|
||||
/**
|
||||
* Creates a JWT credentials instance using an API Key for authentication.
|
||||
* @param apiKey The API Key in string form.
|
||||
*/
|
||||
fromAPIKey(apiKey: string): void;
|
||||
/**
|
||||
* Using the key or keyFile on the JWT client, obtain an object that contains
|
||||
* the key and the client email.
|
||||
*/
|
||||
getCredentials(): Promise<CredentialBody>;
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
"use strict";
|
||||
// Copyright 2013 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.JWT = void 0;
|
||||
const gtoken_1 = require("gtoken");
|
||||
const jwtaccess_1 = require("./jwtaccess");
|
||||
const oauth2client_1 = require("./oauth2client");
|
||||
class JWT extends oauth2client_1.OAuth2Client {
|
||||
constructor(optionsOrEmail, keyFile, key, scopes, subject, keyId) {
|
||||
const opts = optionsOrEmail && typeof optionsOrEmail === 'object'
|
||||
? optionsOrEmail
|
||||
: { email: optionsOrEmail, keyFile, key, keyId, scopes, subject };
|
||||
super({
|
||||
eagerRefreshThresholdMillis: opts.eagerRefreshThresholdMillis,
|
||||
forceRefreshOnFailure: opts.forceRefreshOnFailure,
|
||||
});
|
||||
this.email = opts.email;
|
||||
this.keyFile = opts.keyFile;
|
||||
this.key = opts.key;
|
||||
this.keyId = opts.keyId;
|
||||
this.scopes = opts.scopes;
|
||||
this.subject = opts.subject;
|
||||
this.additionalClaims = opts.additionalClaims;
|
||||
this.credentials = { refresh_token: 'jwt-placeholder', expiry_date: 1 };
|
||||
}
|
||||
/**
|
||||
* Creates a copy of the credential with the specified scopes.
|
||||
* @param scopes List of requested scopes or a single scope.
|
||||
* @return The cloned instance.
|
||||
*/
|
||||
createScoped(scopes) {
|
||||
return new JWT({
|
||||
email: this.email,
|
||||
keyFile: this.keyFile,
|
||||
key: this.key,
|
||||
keyId: this.keyId,
|
||||
scopes,
|
||||
subject: this.subject,
|
||||
additionalClaims: this.additionalClaims,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Obtains the metadata to be sent with the request.
|
||||
*
|
||||
* @param url the URI being authorized.
|
||||
*/
|
||||
async getRequestMetadataAsync(url) {
|
||||
url = this.defaultServicePath ? `https://${this.defaultServicePath}/` : url;
|
||||
const useSelfSignedJWT = (!this.hasUserScopes() && url) ||
|
||||
(this.useJWTAccessWithScope && this.hasAnyScopes());
|
||||
if (!this.apiKey && useSelfSignedJWT) {
|
||||
if (this.additionalClaims &&
|
||||
this.additionalClaims.target_audience) {
|
||||
const { tokens } = await this.refreshToken();
|
||||
return {
|
||||
headers: this.addSharedMetadataHeaders({
|
||||
Authorization: `Bearer ${tokens.id_token}`,
|
||||
}),
|
||||
};
|
||||
}
|
||||
else {
|
||||
// no scopes have been set, but a uri has been provided. Use JWTAccess
|
||||
// credentials.
|
||||
if (!this.access) {
|
||||
this.access = new jwtaccess_1.JWTAccess(this.email, this.key, this.keyId, this.eagerRefreshThresholdMillis);
|
||||
}
|
||||
let scopes;
|
||||
if (this.hasUserScopes()) {
|
||||
scopes = this.scopes;
|
||||
}
|
||||
else if (!url) {
|
||||
scopes = this.defaultScopes;
|
||||
}
|
||||
const headers = await this.access.getRequestHeaders(url !== null && url !== void 0 ? url : undefined, this.additionalClaims,
|
||||
// Scopes take precedent over audience for signing,
|
||||
// so we only provide them if useJWTAccessWithScope is on
|
||||
this.useJWTAccessWithScope ? scopes : undefined);
|
||||
return { headers: this.addSharedMetadataHeaders(headers) };
|
||||
}
|
||||
}
|
||||
else if (this.hasAnyScopes() || this.apiKey) {
|
||||
return super.getRequestMetadataAsync(url);
|
||||
}
|
||||
else {
|
||||
// If no audience, apiKey, or scopes are provided, we should not attempt
|
||||
// to populate any headers:
|
||||
return { headers: {} };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Fetches an ID token.
|
||||
* @param targetAudience the audience for the fetched ID token.
|
||||
*/
|
||||
async fetchIdToken(targetAudience) {
|
||||
// Create a new gToken for fetching an ID token
|
||||
const gtoken = new gtoken_1.GoogleToken({
|
||||
iss: this.email,
|
||||
sub: this.subject,
|
||||
scope: this.scopes || this.defaultScopes,
|
||||
keyFile: this.keyFile,
|
||||
key: this.key,
|
||||
additionalClaims: { target_audience: targetAudience },
|
||||
});
|
||||
await gtoken.getToken({
|
||||
forceRefresh: true,
|
||||
});
|
||||
if (!gtoken.idToken) {
|
||||
throw new Error('Unknown error: Failed to fetch ID token');
|
||||
}
|
||||
return gtoken.idToken;
|
||||
}
|
||||
/**
|
||||
* Determine if there are currently scopes available.
|
||||
*/
|
||||
hasUserScopes() {
|
||||
if (!this.scopes) {
|
||||
return false;
|
||||
}
|
||||
return this.scopes.length > 0;
|
||||
}
|
||||
/**
|
||||
* Are there any default or user scopes defined.
|
||||
*/
|
||||
hasAnyScopes() {
|
||||
if (this.scopes && this.scopes.length > 0)
|
||||
return true;
|
||||
if (this.defaultScopes && this.defaultScopes.length > 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
authorize(callback) {
|
||||
if (callback) {
|
||||
this.authorizeAsync().then(r => callback(null, r), callback);
|
||||
}
|
||||
else {
|
||||
return this.authorizeAsync();
|
||||
}
|
||||
}
|
||||
async authorizeAsync() {
|
||||
const result = await this.refreshToken();
|
||||
if (!result) {
|
||||
throw new Error('No result returned');
|
||||
}
|
||||
this.credentials = result.tokens;
|
||||
this.credentials.refresh_token = 'jwt-placeholder';
|
||||
this.key = this.gtoken.key;
|
||||
this.email = this.gtoken.iss;
|
||||
return result.tokens;
|
||||
}
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
* @param refreshToken ignored
|
||||
* @private
|
||||
*/
|
||||
async refreshTokenNoCache(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
refreshToken) {
|
||||
const gtoken = this.createGToken();
|
||||
const token = await gtoken.getToken({
|
||||
forceRefresh: this.isTokenExpiring(),
|
||||
});
|
||||
const tokens = {
|
||||
access_token: token.access_token,
|
||||
token_type: 'Bearer',
|
||||
expiry_date: gtoken.expiresAt,
|
||||
id_token: gtoken.idToken,
|
||||
};
|
||||
this.emit('tokens', tokens);
|
||||
return { res: null, tokens };
|
||||
}
|
||||
/**
|
||||
* Create a gToken if it doesn't already exist.
|
||||
*/
|
||||
createGToken() {
|
||||
if (!this.gtoken) {
|
||||
this.gtoken = new gtoken_1.GoogleToken({
|
||||
iss: this.email,
|
||||
sub: this.subject,
|
||||
scope: this.scopes || this.defaultScopes,
|
||||
keyFile: this.keyFile,
|
||||
key: this.key,
|
||||
additionalClaims: this.additionalClaims,
|
||||
});
|
||||
}
|
||||
return this.gtoken;
|
||||
}
|
||||
/**
|
||||
* Create a JWT credentials instance using the given input options.
|
||||
* @param json The input object.
|
||||
*/
|
||||
fromJSON(json) {
|
||||
if (!json) {
|
||||
throw new Error('Must pass in a JSON object containing the service account auth settings.');
|
||||
}
|
||||
if (!json.client_email) {
|
||||
throw new Error('The incoming JSON object does not contain a client_email field');
|
||||
}
|
||||
if (!json.private_key) {
|
||||
throw new Error('The incoming JSON object does not contain a private_key field');
|
||||
}
|
||||
// Extract the relevant information from the json key file.
|
||||
this.email = json.client_email;
|
||||
this.key = json.private_key;
|
||||
this.keyId = json.private_key_id;
|
||||
this.projectId = json.project_id;
|
||||
this.quotaProjectId = json.quota_project_id;
|
||||
}
|
||||
fromStream(inputStream, callback) {
|
||||
if (callback) {
|
||||
this.fromStreamAsync(inputStream).then(() => callback(), callback);
|
||||
}
|
||||
else {
|
||||
return this.fromStreamAsync(inputStream);
|
||||
}
|
||||
}
|
||||
fromStreamAsync(inputStream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!inputStream) {
|
||||
throw new Error('Must pass in a stream containing the service account auth settings.');
|
||||
}
|
||||
let s = '';
|
||||
inputStream
|
||||
.setEncoding('utf8')
|
||||
.on('error', reject)
|
||||
.on('data', chunk => (s += chunk))
|
||||
.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(s);
|
||||
this.fromJSON(data);
|
||||
resolve();
|
||||
}
|
||||
catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Creates a JWT credentials instance using an API Key for authentication.
|
||||
* @param apiKey The API Key in string form.
|
||||
*/
|
||||
fromAPIKey(apiKey) {
|
||||
if (typeof apiKey !== 'string') {
|
||||
throw new Error('Must provide an API Key string.');
|
||||
}
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
/**
|
||||
* Using the key or keyFile on the JWT client, obtain an object that contains
|
||||
* the key and the client email.
|
||||
*/
|
||||
async getCredentials() {
|
||||
if (this.key) {
|
||||
return { private_key: this.key, client_email: this.email };
|
||||
}
|
||||
else if (this.keyFile) {
|
||||
const gtoken = this.createGToken();
|
||||
const creds = await gtoken.getCredentials(this.keyFile);
|
||||
return { private_key: creds.privateKey, client_email: creds.clientEmail };
|
||||
}
|
||||
throw new Error('A key or a keyFile must be provided to getCredentials.');
|
||||
}
|
||||
}
|
||||
exports.JWT = JWT;
|
||||
//# sourceMappingURL=jwtclient.js.map
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
export declare class LoginTicket {
|
||||
private envelope?;
|
||||
private payload?;
|
||||
/**
|
||||
* Create a simple class to extract user ID from an ID Token
|
||||
*
|
||||
* @param {string} env Envelope of the jwt
|
||||
* @param {TokenPayload} pay Payload of the jwt
|
||||
* @constructor
|
||||
*/
|
||||
constructor(env?: string, pay?: TokenPayload);
|
||||
getEnvelope(): string | undefined;
|
||||
getPayload(): TokenPayload | undefined;
|
||||
/**
|
||||
* Create a simple class to extract user ID from an ID Token
|
||||
*
|
||||
* @return The user ID
|
||||
*/
|
||||
getUserId(): string | null;
|
||||
/**
|
||||
* Returns attributes from the login ticket. This can contain
|
||||
* various information about the user session.
|
||||
*
|
||||
* @return The envelope and payload
|
||||
*/
|
||||
getAttributes(): {
|
||||
envelope: string | undefined;
|
||||
payload: TokenPayload | undefined;
|
||||
};
|
||||
}
|
||||
export interface TokenPayload {
|
||||
/**
|
||||
* The Issuer Identifier for the Issuer of the response. Always
|
||||
* https://accounts.google.com or accounts.google.com for Google ID tokens.
|
||||
*/
|
||||
iss: string;
|
||||
/**
|
||||
* Access token hash. Provides validation that the access token is tied to the
|
||||
* identity token. If the ID token is issued with an access token in the
|
||||
* server flow, this is always included. This can be used as an alternate
|
||||
* mechanism to protect against cross-site request forgery attacks, but if you
|
||||
* follow Step 1 and Step 3 it is not necessary to verify the access token.
|
||||
*/
|
||||
at_hash?: string;
|
||||
/**
|
||||
* True if the user's e-mail address has been verified; otherwise false.
|
||||
*/
|
||||
email_verified?: boolean;
|
||||
/**
|
||||
* An identifier for the user, unique among all Google accounts and never
|
||||
* reused. A Google account can have multiple emails at different points in
|
||||
* time, but the sub value is never changed. Use sub within your application
|
||||
* as the unique-identifier key for the user.
|
||||
*/
|
||||
sub: string;
|
||||
/**
|
||||
* The client_id of the authorized presenter. This claim is only needed when
|
||||
* the party requesting the ID token is not the same as the audience of the ID
|
||||
* token. This may be the case at Google for hybrid apps where a web
|
||||
* application and Android app have a different client_id but share the same
|
||||
* project.
|
||||
*/
|
||||
azp?: string;
|
||||
/**
|
||||
* The user's email address. This may not be unique and is not suitable for
|
||||
* use as a primary key. Provided only if your scope included the string
|
||||
* "email".
|
||||
*/
|
||||
email?: string;
|
||||
/**
|
||||
* The URL of the user's profile page. Might be provided when:
|
||||
* - The request scope included the string "profile"
|
||||
* - The ID token is returned from a token refresh
|
||||
* - When profile claims are present, you can use them to update your app's
|
||||
* user records. Note that this claim is never guaranteed to be present.
|
||||
*/
|
||||
profile?: string;
|
||||
/**
|
||||
* The URL of the user's profile picture. Might be provided when:
|
||||
* - The request scope included the string "profile"
|
||||
* - The ID token is returned from a token refresh
|
||||
* - When picture claims are present, you can use them to update your app's
|
||||
* user records. Note that this claim is never guaranteed to be present.
|
||||
*/
|
||||
picture?: string;
|
||||
/**
|
||||
* The user's full name, in a displayable form. Might be provided when:
|
||||
* - The request scope included the string "profile"
|
||||
* - The ID token is returned from a token refresh
|
||||
* - When name claims are present, you can use them to update your app's user
|
||||
* records. Note that this claim is never guaranteed to be present.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* The user's given name, in a displayable form. Might be provided when:
|
||||
* - The request scope included the string "profile"
|
||||
* - The ID token is returned from a token refresh
|
||||
* - When name claims are present, you can use them to update your app's user
|
||||
* records. Note that this claim is never guaranteed to be present.
|
||||
*/
|
||||
given_name?: string;
|
||||
/**
|
||||
* The user's family name, in a displayable form. Might be provided when:
|
||||
* - The request scope included the string "profile"
|
||||
* - The ID token is returned from a token refresh
|
||||
* - When name claims are present, you can use them to update your app's user
|
||||
* records. Note that this claim is never guaranteed to be present.
|
||||
*/
|
||||
family_name?: string;
|
||||
/**
|
||||
* Identifies the audience that this ID token is intended for. It must be one
|
||||
* of the OAuth 2.0 client IDs of your application.
|
||||
*/
|
||||
aud: string;
|
||||
/**
|
||||
* The time the ID token was issued, represented in Unix time (integer
|
||||
* seconds).
|
||||
*/
|
||||
iat: number;
|
||||
/**
|
||||
* The time the ID token expires, represented in Unix time (integer seconds).
|
||||
*/
|
||||
exp: number;
|
||||
/**
|
||||
* The value of the nonce supplied by your app in the authentication request.
|
||||
* You should enforce protection against replay attacks by ensuring it is
|
||||
* presented only once.
|
||||
*/
|
||||
nonce?: string;
|
||||
/**
|
||||
* The hosted G Suite domain of the user. Provided only if the user belongs to
|
||||
* a hosted domain.
|
||||
*/
|
||||
hd?: string;
|
||||
/**
|
||||
* The user's locale, represented by a BCP 47 language tag.
|
||||
* Might be provided when a name claim is present.
|
||||
*/
|
||||
locale?: string;
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
"use strict";
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.LoginTicket = void 0;
|
||||
class LoginTicket {
|
||||
/**
|
||||
* Create a simple class to extract user ID from an ID Token
|
||||
*
|
||||
* @param {string} env Envelope of the jwt
|
||||
* @param {TokenPayload} pay Payload of the jwt
|
||||
* @constructor
|
||||
*/
|
||||
constructor(env, pay) {
|
||||
this.envelope = env;
|
||||
this.payload = pay;
|
||||
}
|
||||
getEnvelope() {
|
||||
return this.envelope;
|
||||
}
|
||||
getPayload() {
|
||||
return this.payload;
|
||||
}
|
||||
/**
|
||||
* Create a simple class to extract user ID from an ID Token
|
||||
*
|
||||
* @return The user ID
|
||||
*/
|
||||
getUserId() {
|
||||
const payload = this.getPayload();
|
||||
if (payload && payload.sub) {
|
||||
return payload.sub;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Returns attributes from the login ticket. This can contain
|
||||
* various information about the user session.
|
||||
*
|
||||
* @return The envelope and payload
|
||||
*/
|
||||
getAttributes() {
|
||||
return { envelope: this.getEnvelope(), payload: this.getPayload() };
|
||||
}
|
||||
}
|
||||
exports.LoginTicket = LoginTicket;
|
||||
//# sourceMappingURL=loginticket.js.map
|
||||
+516
@@ -0,0 +1,516 @@
|
||||
import { GaxiosError, GaxiosOptions, GaxiosPromise, GaxiosResponse } from 'gaxios';
|
||||
import { JwkCertificate } from '../crypto/crypto';
|
||||
import { BodyResponseCallback } from '../transporters';
|
||||
import { AuthClient } from './authclient';
|
||||
import { Credentials } from './credentials';
|
||||
import { LoginTicket } from './loginticket';
|
||||
/**
|
||||
* The results from the `generateCodeVerifierAsync` method. To learn more,
|
||||
* See the sample:
|
||||
* https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/oauth2-codeVerifier.js
|
||||
*/
|
||||
export interface CodeVerifierResults {
|
||||
/**
|
||||
* The code verifier that will be used when calling `getToken` to obtain a new
|
||||
* access token.
|
||||
*/
|
||||
codeVerifier: string;
|
||||
/**
|
||||
* The code_challenge that should be sent with the `generateAuthUrl` call
|
||||
* to obtain a verifiable authentication url.
|
||||
*/
|
||||
codeChallenge?: string;
|
||||
}
|
||||
export interface Certificates {
|
||||
[index: string]: string | JwkCertificate;
|
||||
}
|
||||
export interface PublicKeys {
|
||||
[index: string]: string;
|
||||
}
|
||||
export interface Headers {
|
||||
[index: string]: string;
|
||||
}
|
||||
export declare enum CodeChallengeMethod {
|
||||
Plain = "plain",
|
||||
S256 = "S256"
|
||||
}
|
||||
export declare enum CertificateFormat {
|
||||
PEM = "PEM",
|
||||
JWK = "JWK"
|
||||
}
|
||||
export interface GetTokenOptions {
|
||||
code: string;
|
||||
codeVerifier?: string;
|
||||
/**
|
||||
* The client ID for your application. The value passed into the constructor
|
||||
* will be used if not provided. Must match any client_id option passed to
|
||||
* a corresponding call to generateAuthUrl.
|
||||
*/
|
||||
client_id?: string;
|
||||
/**
|
||||
* Determines where the API server redirects the user after the user
|
||||
* completes the authorization flow. The value passed into the constructor
|
||||
* will be used if not provided. Must match any redirect_uri option passed to
|
||||
* a corresponding call to generateAuthUrl.
|
||||
*/
|
||||
redirect_uri?: string;
|
||||
}
|
||||
export interface TokenInfo {
|
||||
/**
|
||||
* The application that is the intended user of the access token.
|
||||
*/
|
||||
aud: string;
|
||||
/**
|
||||
* This value lets you correlate profile information from multiple Google
|
||||
* APIs. It is only present in the response if you included the profile scope
|
||||
* in your request in step 1. The field value is an immutable identifier for
|
||||
* the logged-in user that can be used to create and manage user sessions in
|
||||
* your application. The identifier is the same regardless of which client ID
|
||||
* is used to retrieve it. This enables multiple applications in the same
|
||||
* organization to correlate profile information.
|
||||
*/
|
||||
user_id?: string;
|
||||
/**
|
||||
* An array of scopes that the user granted access to.
|
||||
*/
|
||||
scopes: string[];
|
||||
/**
|
||||
* The datetime when the token becomes invalid.
|
||||
*/
|
||||
expiry_date: number;
|
||||
/**
|
||||
* An identifier for the user, unique among all Google accounts and never
|
||||
* reused. A Google account can have multiple emails at different points in
|
||||
* time, but the sub value is never changed. Use sub within your application
|
||||
* as the unique-identifier key for the user.
|
||||
*/
|
||||
sub?: string;
|
||||
/**
|
||||
* The client_id of the authorized presenter. This claim is only needed when
|
||||
* the party requesting the ID token is not the same as the audience of the ID
|
||||
* token. This may be the case at Google for hybrid apps where a web
|
||||
* application and Android app have a different client_id but share the same
|
||||
* project.
|
||||
*/
|
||||
azp?: string;
|
||||
/**
|
||||
* Indicates whether your application can refresh access tokens
|
||||
* when the user is not present at the browser. Valid parameter values are
|
||||
* 'online', which is the default value, and 'offline'. Set the value to
|
||||
* 'offline' if your application needs to refresh access tokens when the user
|
||||
* is not present at the browser. This value instructs the Google
|
||||
* authorization server to return a refresh token and an access token the
|
||||
* first time that your application exchanges an authorization code for
|
||||
* tokens.
|
||||
*/
|
||||
access_type?: string;
|
||||
/**
|
||||
* The user's email address. This value may not be unique to this user and
|
||||
* is not suitable for use as a primary key. Provided only if your scope
|
||||
* included the email scope value.
|
||||
*/
|
||||
email?: string;
|
||||
/**
|
||||
* True if the user's e-mail address has been verified; otherwise false.
|
||||
*/
|
||||
email_verified?: boolean;
|
||||
}
|
||||
export interface GenerateAuthUrlOpts {
|
||||
/**
|
||||
* Recommended. Indicates whether your application can refresh access tokens
|
||||
* when the user is not present at the browser. Valid parameter values are
|
||||
* 'online', which is the default value, and 'offline'. Set the value to
|
||||
* 'offline' if your application needs to refresh access tokens when the user
|
||||
* is not present at the browser. This value instructs the Google
|
||||
* authorization server to return a refresh token and an access token the
|
||||
* first time that your application exchanges an authorization code for
|
||||
* tokens.
|
||||
*/
|
||||
access_type?: string;
|
||||
/**
|
||||
* The hd (hosted domain) parameter streamlines the login process for G Suite
|
||||
* hosted accounts. By including the domain of the G Suite user (for example,
|
||||
* mycollege.edu), you can indicate that the account selection UI should be
|
||||
* optimized for accounts at that domain. To optimize for G Suite accounts
|
||||
* generally instead of just one domain, use an asterisk: hd=*.
|
||||
* Don't rely on this UI optimization to control who can access your app,
|
||||
* as client-side requests can be modified. Be sure to validate that the
|
||||
* returned ID token has an hd claim value that matches what you expect
|
||||
* (e.g. mycolledge.edu). Unlike the request parameter, the ID token claim is
|
||||
* contained within a security token from Google, so the value can be trusted.
|
||||
*/
|
||||
hd?: string;
|
||||
/**
|
||||
* The 'response_type' will always be set to 'CODE'.
|
||||
*/
|
||||
response_type?: string;
|
||||
/**
|
||||
* The client ID for your application. The value passed into the constructor
|
||||
* will be used if not provided. You can find this value in the API Console.
|
||||
*/
|
||||
client_id?: string;
|
||||
/**
|
||||
* Determines where the API server redirects the user after the user
|
||||
* completes the authorization flow. The value must exactly match one of the
|
||||
* 'redirect_uri' values listed for your project in the API Console. Note that
|
||||
* the http or https scheme, case, and trailing slash ('/') must all match.
|
||||
* The value passed into the constructor will be used if not provided.
|
||||
*/
|
||||
redirect_uri?: string;
|
||||
/**
|
||||
* Required. A space-delimited list of scopes that identify the resources that
|
||||
* your application could access on the user's behalf. These values inform the
|
||||
* consent screen that Google displays to the user. Scopes enable your
|
||||
* application to only request access to the resources that it needs while
|
||||
* also enabling users to control the amount of access that they grant to your
|
||||
* application. Thus, there is an inverse relationship between the number of
|
||||
* scopes requested and the likelihood of obtaining user consent. The
|
||||
* OAuth 2.0 API Scopes document provides a full list of scopes that you might
|
||||
* use to access Google APIs. We recommend that your application request
|
||||
* access to authorization scopes in context whenever possible. By requesting
|
||||
* access to user data in context, via incremental authorization, you help
|
||||
* users to more easily understand why your application needs the access it is
|
||||
* requesting.
|
||||
*/
|
||||
scope?: string[] | string;
|
||||
/**
|
||||
* Recommended. Specifies any string value that your application uses to
|
||||
* maintain state between your authorization request and the authorization
|
||||
* server's response. The server returns the exact value that you send as a
|
||||
* name=value pair in the hash (#) fragment of the 'redirect_uri' after the
|
||||
* user consents to or denies your application's access request. You can use
|
||||
* this parameter for several purposes, such as directing the user to the
|
||||
* correct resource in your application, sending nonces, and mitigating
|
||||
* cross-site request forgery. Since your redirect_uri can be guessed, using a
|
||||
* state value can increase your assurance that an incoming connection is the
|
||||
* result of an authentication request. If you generate a random string or
|
||||
* encode the hash of a cookie or another value that captures the client's
|
||||
* state, you can validate the response to additionally ensure that the
|
||||
* request and response originated in the same browser, providing protection
|
||||
* against attacks such as cross-site request forgery. See the OpenID Connect
|
||||
* documentation for an example of how to create and confirm a state token.
|
||||
*/
|
||||
state?: string;
|
||||
/**
|
||||
* Optional. Enables applications to use incremental authorization to request
|
||||
* access to additional scopes in context. If you set this parameter's value
|
||||
* to true and the authorization request is granted, then the new access token
|
||||
* will also cover any scopes to which the user previously granted the
|
||||
* application access. See the incremental authorization section for examples.
|
||||
*/
|
||||
include_granted_scopes?: boolean;
|
||||
/**
|
||||
* Optional. If your application knows which user is trying to authenticate,
|
||||
* it can use this parameter to provide a hint to the Google Authentication
|
||||
* Server. The server uses the hint to simplify the login flow either by
|
||||
* prefilling the email field in the sign-in form or by selecting the
|
||||
* appropriate multi-login session. Set the parameter value to an email
|
||||
* address or sub identifier, which is equivalent to the user's Google ID.
|
||||
*/
|
||||
login_hint?: string;
|
||||
/**
|
||||
* Optional. A space-delimited, case-sensitive list of prompts to present the
|
||||
* user. If you don't specify this parameter, the user will be prompted only
|
||||
* the first time your app requests access. Possible values are:
|
||||
*
|
||||
* 'none' - Donot display any authentication or consent screens. Must not be
|
||||
* specified with other values.
|
||||
* 'consent' - Prompt the user for consent.
|
||||
* 'select_account' - Prompt the user to select an account.
|
||||
*/
|
||||
prompt?: string;
|
||||
/**
|
||||
* Recommended. Specifies what method was used to encode a 'code_verifier'
|
||||
* that will be used during authorization code exchange. This parameter must
|
||||
* be used with the 'code_challenge' parameter. The value of the
|
||||
* 'code_challenge_method' defaults to "plain" if not present in the request
|
||||
* that includes a 'code_challenge'. The only supported values for this
|
||||
* parameter are "S256" or "plain".
|
||||
*/
|
||||
code_challenge_method?: CodeChallengeMethod;
|
||||
/**
|
||||
* Recommended. Specifies an encoded 'code_verifier' that will be used as a
|
||||
* server-side challenge during authorization code exchange. This parameter
|
||||
* must be used with the 'code_challenge' parameter described above.
|
||||
*/
|
||||
code_challenge?: string;
|
||||
}
|
||||
export interface AccessTokenResponse {
|
||||
access_token: string;
|
||||
expiry_date: number;
|
||||
}
|
||||
export interface GetRefreshHandlerCallback {
|
||||
(): Promise<AccessTokenResponse>;
|
||||
}
|
||||
export interface GetTokenCallback {
|
||||
(err: GaxiosError | null, token?: Credentials | null, res?: GaxiosResponse | null): void;
|
||||
}
|
||||
export interface GetTokenResponse {
|
||||
tokens: Credentials;
|
||||
res: GaxiosResponse | null;
|
||||
}
|
||||
export interface GetAccessTokenCallback {
|
||||
(err: GaxiosError | null, token?: string | null, res?: GaxiosResponse | null): void;
|
||||
}
|
||||
export interface GetAccessTokenResponse {
|
||||
token?: string | null;
|
||||
res?: GaxiosResponse | null;
|
||||
}
|
||||
export interface RefreshAccessTokenCallback {
|
||||
(err: GaxiosError | null, credentials?: Credentials | null, res?: GaxiosResponse | null): void;
|
||||
}
|
||||
export interface RefreshAccessTokenResponse {
|
||||
credentials: Credentials;
|
||||
res: GaxiosResponse | null;
|
||||
}
|
||||
export interface RequestMetadataResponse {
|
||||
headers: Headers;
|
||||
res?: GaxiosResponse<void> | null;
|
||||
}
|
||||
export interface RequestMetadataCallback {
|
||||
(err: GaxiosError | null, headers?: Headers, res?: GaxiosResponse<void> | null): void;
|
||||
}
|
||||
export interface GetFederatedSignonCertsCallback {
|
||||
(err: GaxiosError | null, certs?: Certificates, response?: GaxiosResponse<void> | null): void;
|
||||
}
|
||||
export interface FederatedSignonCertsResponse {
|
||||
certs: Certificates;
|
||||
format: CertificateFormat;
|
||||
res?: GaxiosResponse<void> | null;
|
||||
}
|
||||
export interface GetIapPublicKeysCallback {
|
||||
(err: GaxiosError | null, pubkeys?: PublicKeys, response?: GaxiosResponse<void> | null): void;
|
||||
}
|
||||
export interface IapPublicKeysResponse {
|
||||
pubkeys: PublicKeys;
|
||||
res?: GaxiosResponse<void> | null;
|
||||
}
|
||||
export interface RevokeCredentialsResult {
|
||||
success: boolean;
|
||||
}
|
||||
export interface VerifyIdTokenOptions {
|
||||
idToken: string;
|
||||
audience?: string | string[];
|
||||
maxExpiry?: number;
|
||||
}
|
||||
export interface OAuth2ClientOptions extends RefreshOptions {
|
||||
clientId?: string;
|
||||
clientSecret?: string;
|
||||
redirectUri?: string;
|
||||
}
|
||||
export interface RefreshOptions {
|
||||
eagerRefreshThresholdMillis?: number;
|
||||
forceRefreshOnFailure?: boolean;
|
||||
}
|
||||
export declare class OAuth2Client extends AuthClient {
|
||||
private redirectUri?;
|
||||
private certificateCache;
|
||||
private certificateExpiry;
|
||||
private certificateCacheFormat;
|
||||
protected refreshTokenPromises: Map<string, Promise<GetTokenResponse>>;
|
||||
_clientId?: string;
|
||||
_clientSecret?: string;
|
||||
apiKey?: string;
|
||||
projectId?: string;
|
||||
eagerRefreshThresholdMillis: number;
|
||||
forceRefreshOnFailure: boolean;
|
||||
refreshHandler?: GetRefreshHandlerCallback;
|
||||
/**
|
||||
* Handles OAuth2 flow for Google APIs.
|
||||
*
|
||||
* @param clientId The authentication client ID.
|
||||
* @param clientSecret The authentication client secret.
|
||||
* @param redirectUri The URI to redirect to after completing the auth
|
||||
* request.
|
||||
* @param opts optional options for overriding the given parameters.
|
||||
* @constructor
|
||||
*/
|
||||
constructor(options?: OAuth2ClientOptions);
|
||||
constructor(clientId?: string, clientSecret?: string, redirectUri?: string);
|
||||
protected static readonly GOOGLE_TOKEN_INFO_URL = "https://oauth2.googleapis.com/tokeninfo";
|
||||
/**
|
||||
* The base URL for auth endpoints.
|
||||
*/
|
||||
private static readonly GOOGLE_OAUTH2_AUTH_BASE_URL_;
|
||||
/**
|
||||
* The base endpoint for token retrieval.
|
||||
*/
|
||||
private static readonly GOOGLE_OAUTH2_TOKEN_URL_;
|
||||
/**
|
||||
* The base endpoint to revoke tokens.
|
||||
*/
|
||||
private static readonly GOOGLE_OAUTH2_REVOKE_URL_;
|
||||
/**
|
||||
* Google Sign on certificates in PEM format.
|
||||
*/
|
||||
private static readonly GOOGLE_OAUTH2_FEDERATED_SIGNON_PEM_CERTS_URL_;
|
||||
/**
|
||||
* Google Sign on certificates in JWK format.
|
||||
*/
|
||||
private static readonly GOOGLE_OAUTH2_FEDERATED_SIGNON_JWK_CERTS_URL_;
|
||||
/**
|
||||
* Google Sign on certificates in JWK format.
|
||||
*/
|
||||
private static readonly GOOGLE_OAUTH2_IAP_PUBLIC_KEY_URL_;
|
||||
/**
|
||||
* Clock skew - five minutes in seconds
|
||||
*/
|
||||
private static readonly CLOCK_SKEW_SECS_;
|
||||
/**
|
||||
* Max Token Lifetime is one day in seconds
|
||||
*/
|
||||
private static readonly MAX_TOKEN_LIFETIME_SECS_;
|
||||
/**
|
||||
* The allowed oauth token issuers.
|
||||
*/
|
||||
private static readonly ISSUERS_;
|
||||
/**
|
||||
* Generates URL for consent page landing.
|
||||
* @param opts Options.
|
||||
* @return URL to consent page.
|
||||
*/
|
||||
generateAuthUrl(opts?: GenerateAuthUrlOpts): string;
|
||||
generateCodeVerifier(): void;
|
||||
/**
|
||||
* Convenience method to automatically generate a code_verifier, and its
|
||||
* resulting SHA256. If used, this must be paired with a S256
|
||||
* code_challenge_method.
|
||||
*
|
||||
* For a full example see:
|
||||
* https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/oauth2-codeVerifier.js
|
||||
*/
|
||||
generateCodeVerifierAsync(): Promise<CodeVerifierResults>;
|
||||
/**
|
||||
* Gets the access token for the given code.
|
||||
* @param code The authorization code.
|
||||
* @param callback Optional callback fn.
|
||||
*/
|
||||
getToken(code: string): Promise<GetTokenResponse>;
|
||||
getToken(options: GetTokenOptions): Promise<GetTokenResponse>;
|
||||
getToken(code: string, callback: GetTokenCallback): void;
|
||||
getToken(options: GetTokenOptions, callback: GetTokenCallback): void;
|
||||
private getTokenAsync;
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
* @param refresh_token Existing refresh token.
|
||||
* @private
|
||||
*/
|
||||
protected refreshToken(refreshToken?: string | null): Promise<GetTokenResponse>;
|
||||
protected refreshTokenNoCache(refreshToken?: string | null): Promise<GetTokenResponse>;
|
||||
/**
|
||||
* Retrieves the access token using refresh token
|
||||
*
|
||||
* @deprecated use getRequestHeaders instead.
|
||||
* @param callback callback
|
||||
*/
|
||||
refreshAccessToken(): Promise<RefreshAccessTokenResponse>;
|
||||
refreshAccessToken(callback: RefreshAccessTokenCallback): void;
|
||||
private refreshAccessTokenAsync;
|
||||
/**
|
||||
* Get a non-expired access token, after refreshing if necessary
|
||||
*
|
||||
* @param callback Callback to call with the access token
|
||||
*/
|
||||
getAccessToken(): Promise<GetAccessTokenResponse>;
|
||||
getAccessToken(callback: GetAccessTokenCallback): void;
|
||||
private getAccessTokenAsync;
|
||||
/**
|
||||
* The main authentication interface. It takes an optional url which when
|
||||
* present is the endpoint being accessed, and returns a Promise which
|
||||
* resolves with authorization header fields.
|
||||
*
|
||||
* In OAuth2Client, the result has the form:
|
||||
* { Authorization: 'Bearer <access_token_value>' }
|
||||
* @param url The optional url being authorized
|
||||
*/
|
||||
getRequestHeaders(url?: string): Promise<Headers>;
|
||||
protected getRequestMetadataAsync(url?: string | null): Promise<RequestMetadataResponse>;
|
||||
/**
|
||||
* Generates an URL to revoke the given token.
|
||||
* @param token The existing token to be revoked.
|
||||
*/
|
||||
static getRevokeTokenUrl(token: string): string;
|
||||
/**
|
||||
* Revokes the access given to token.
|
||||
* @param token The existing token to be revoked.
|
||||
* @param callback Optional callback fn.
|
||||
*/
|
||||
revokeToken(token: string): GaxiosPromise<RevokeCredentialsResult>;
|
||||
revokeToken(token: string, callback: BodyResponseCallback<RevokeCredentialsResult>): void;
|
||||
/**
|
||||
* Revokes access token and clears the credentials object
|
||||
* @param callback callback
|
||||
*/
|
||||
revokeCredentials(): GaxiosPromise<RevokeCredentialsResult>;
|
||||
revokeCredentials(callback: BodyResponseCallback<RevokeCredentialsResult>): void;
|
||||
private revokeCredentialsAsync;
|
||||
/**
|
||||
* Provides a request implementation with OAuth 2.0 flow. If credentials have
|
||||
* a refresh_token, in cases of HTTP 401 and 403 responses, it automatically
|
||||
* asks for a new access token and replays the unsuccessful request.
|
||||
* @param opts Request options.
|
||||
* @param callback callback.
|
||||
* @return Request object
|
||||
*/
|
||||
request<T>(opts: GaxiosOptions): GaxiosPromise<T>;
|
||||
request<T>(opts: GaxiosOptions, callback: BodyResponseCallback<T>): void;
|
||||
protected requestAsync<T>(opts: GaxiosOptions, retry?: boolean): Promise<GaxiosResponse<T>>;
|
||||
/**
|
||||
* Verify id token is token by checking the certs and audience
|
||||
* @param options that contains all options.
|
||||
* @param callback Callback supplying GoogleLogin if successful
|
||||
*/
|
||||
verifyIdToken(options: VerifyIdTokenOptions): Promise<LoginTicket>;
|
||||
verifyIdToken(options: VerifyIdTokenOptions, callback: (err: Error | null, login?: LoginTicket) => void): void;
|
||||
private verifyIdTokenAsync;
|
||||
/**
|
||||
* Obtains information about the provisioned access token. Especially useful
|
||||
* if you want to check the scopes that were provisioned to a given token.
|
||||
*
|
||||
* @param accessToken Required. The Access Token for which you want to get
|
||||
* user info.
|
||||
*/
|
||||
getTokenInfo(accessToken: string): Promise<TokenInfo>;
|
||||
/**
|
||||
* Gets federated sign-on certificates to use for verifying identity tokens.
|
||||
* Returns certs as array structure, where keys are key ids, and values
|
||||
* are certificates in either PEM or JWK format.
|
||||
* @param callback Callback supplying the certificates
|
||||
*/
|
||||
getFederatedSignonCerts(): Promise<FederatedSignonCertsResponse>;
|
||||
getFederatedSignonCerts(callback: GetFederatedSignonCertsCallback): void;
|
||||
getFederatedSignonCertsAsync(): Promise<FederatedSignonCertsResponse>;
|
||||
/**
|
||||
* Gets federated sign-on certificates to use for verifying identity tokens.
|
||||
* Returns certs as array structure, where keys are key ids, and values
|
||||
* are certificates in either PEM or JWK format.
|
||||
* @param callback Callback supplying the certificates
|
||||
*/
|
||||
getIapPublicKeys(): Promise<IapPublicKeysResponse>;
|
||||
getIapPublicKeys(callback: GetIapPublicKeysCallback): void;
|
||||
getIapPublicKeysAsync(): Promise<IapPublicKeysResponse>;
|
||||
verifySignedJwtWithCerts(): void;
|
||||
/**
|
||||
* Verify the id token is signed with the correct certificate
|
||||
* and is from the correct audience.
|
||||
* @param jwt The jwt to verify (The ID Token in this case).
|
||||
* @param certs The array of certs to test the jwt against.
|
||||
* @param requiredAudience The audience to test the jwt against.
|
||||
* @param issuers The allowed issuers of the jwt (Optional).
|
||||
* @param maxExpiry The max expiry the certificate can be (Optional).
|
||||
* @return Returns a promise resolving to LoginTicket on verification.
|
||||
*/
|
||||
verifySignedJwtWithCertsAsync(jwt: string, certs: Certificates | PublicKeys, requiredAudience?: string | string[], issuers?: string[], maxExpiry?: number): Promise<LoginTicket>;
|
||||
/**
|
||||
* Returns a promise that resolves with AccessTokenResponse type if
|
||||
* refreshHandler is defined.
|
||||
* If not, nothing is returned.
|
||||
*/
|
||||
private processAndValidateRefreshHandler;
|
||||
/**
|
||||
* Returns true if a token is expired or will expire within
|
||||
* eagerRefreshThresholdMillismilliseconds.
|
||||
* If there is no expiry time, assumes the token is not expired or expiring.
|
||||
*/
|
||||
protected isTokenExpiring(): boolean;
|
||||
}
|
||||
+744
@@ -0,0 +1,744 @@
|
||||
"use strict";
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.OAuth2Client = exports.CertificateFormat = exports.CodeChallengeMethod = void 0;
|
||||
const querystring = require("querystring");
|
||||
const stream = require("stream");
|
||||
const formatEcdsa = require("ecdsa-sig-formatter");
|
||||
const crypto_1 = require("../crypto/crypto");
|
||||
const authclient_1 = require("./authclient");
|
||||
const loginticket_1 = require("./loginticket");
|
||||
var CodeChallengeMethod;
|
||||
(function (CodeChallengeMethod) {
|
||||
CodeChallengeMethod["Plain"] = "plain";
|
||||
CodeChallengeMethod["S256"] = "S256";
|
||||
})(CodeChallengeMethod = exports.CodeChallengeMethod || (exports.CodeChallengeMethod = {}));
|
||||
var CertificateFormat;
|
||||
(function (CertificateFormat) {
|
||||
CertificateFormat["PEM"] = "PEM";
|
||||
CertificateFormat["JWK"] = "JWK";
|
||||
})(CertificateFormat = exports.CertificateFormat || (exports.CertificateFormat = {}));
|
||||
class OAuth2Client extends authclient_1.AuthClient {
|
||||
constructor(optionsOrClientId, clientSecret, redirectUri) {
|
||||
super();
|
||||
this.certificateCache = {};
|
||||
this.certificateExpiry = null;
|
||||
this.certificateCacheFormat = CertificateFormat.PEM;
|
||||
this.refreshTokenPromises = new Map();
|
||||
const opts = optionsOrClientId && typeof optionsOrClientId === 'object'
|
||||
? optionsOrClientId
|
||||
: { clientId: optionsOrClientId, clientSecret, redirectUri };
|
||||
this._clientId = opts.clientId;
|
||||
this._clientSecret = opts.clientSecret;
|
||||
this.redirectUri = opts.redirectUri;
|
||||
this.eagerRefreshThresholdMillis =
|
||||
opts.eagerRefreshThresholdMillis || 5 * 60 * 1000;
|
||||
this.forceRefreshOnFailure = !!opts.forceRefreshOnFailure;
|
||||
}
|
||||
/**
|
||||
* Generates URL for consent page landing.
|
||||
* @param opts Options.
|
||||
* @return URL to consent page.
|
||||
*/
|
||||
generateAuthUrl(opts = {}) {
|
||||
if (opts.code_challenge_method && !opts.code_challenge) {
|
||||
throw new Error('If a code_challenge_method is provided, code_challenge must be included.');
|
||||
}
|
||||
opts.response_type = opts.response_type || 'code';
|
||||
opts.client_id = opts.client_id || this._clientId;
|
||||
opts.redirect_uri = opts.redirect_uri || this.redirectUri;
|
||||
// Allow scopes to be passed either as array or a string
|
||||
if (opts.scope instanceof Array) {
|
||||
opts.scope = opts.scope.join(' ');
|
||||
}
|
||||
const rootUrl = OAuth2Client.GOOGLE_OAUTH2_AUTH_BASE_URL_;
|
||||
return (rootUrl +
|
||||
'?' +
|
||||
querystring.stringify(opts));
|
||||
}
|
||||
generateCodeVerifier() {
|
||||
// To make the code compatible with browser SubtleCrypto we need to make
|
||||
// this method async.
|
||||
throw new Error('generateCodeVerifier is removed, please use generateCodeVerifierAsync instead.');
|
||||
}
|
||||
/**
|
||||
* Convenience method to automatically generate a code_verifier, and its
|
||||
* resulting SHA256. If used, this must be paired with a S256
|
||||
* code_challenge_method.
|
||||
*
|
||||
* For a full example see:
|
||||
* https://github.com/googleapis/google-auth-library-nodejs/blob/main/samples/oauth2-codeVerifier.js
|
||||
*/
|
||||
async generateCodeVerifierAsync() {
|
||||
// base64 encoding uses 6 bits per character, and we want to generate128
|
||||
// characters. 6*128/8 = 96.
|
||||
const crypto = crypto_1.createCrypto();
|
||||
const randomString = crypto.randomBytesBase64(96);
|
||||
// The valid characters in the code_verifier are [A-Z]/[a-z]/[0-9]/
|
||||
// "-"/"."/"_"/"~". Base64 encoded strings are pretty close, so we're just
|
||||
// swapping out a few chars.
|
||||
const codeVerifier = randomString
|
||||
.replace(/\+/g, '~')
|
||||
.replace(/=/g, '_')
|
||||
.replace(/\//g, '-');
|
||||
// Generate the base64 encoded SHA256
|
||||
const unencodedCodeChallenge = await crypto.sha256DigestBase64(codeVerifier);
|
||||
// We need to use base64UrlEncoding instead of standard base64
|
||||
const codeChallenge = unencodedCodeChallenge
|
||||
.split('=')[0]
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_');
|
||||
return { codeVerifier, codeChallenge };
|
||||
}
|
||||
getToken(codeOrOptions, callback) {
|
||||
const options = typeof codeOrOptions === 'string' ? { code: codeOrOptions } : codeOrOptions;
|
||||
if (callback) {
|
||||
this.getTokenAsync(options).then(r => callback(null, r.tokens, r.res), e => callback(e, null, e.response));
|
||||
}
|
||||
else {
|
||||
return this.getTokenAsync(options);
|
||||
}
|
||||
}
|
||||
async getTokenAsync(options) {
|
||||
const url = OAuth2Client.GOOGLE_OAUTH2_TOKEN_URL_;
|
||||
const values = {
|
||||
code: options.code,
|
||||
client_id: options.client_id || this._clientId,
|
||||
client_secret: this._clientSecret,
|
||||
redirect_uri: options.redirect_uri || this.redirectUri,
|
||||
grant_type: 'authorization_code',
|
||||
code_verifier: options.codeVerifier,
|
||||
};
|
||||
const res = await this.transporter.request({
|
||||
method: 'POST',
|
||||
url,
|
||||
data: querystring.stringify(values),
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
});
|
||||
const tokens = res.data;
|
||||
if (res.data && res.data.expires_in) {
|
||||
tokens.expiry_date = new Date().getTime() + res.data.expires_in * 1000;
|
||||
delete tokens.expires_in;
|
||||
}
|
||||
this.emit('tokens', tokens);
|
||||
return { tokens, res };
|
||||
}
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
* @param refresh_token Existing refresh token.
|
||||
* @private
|
||||
*/
|
||||
async refreshToken(refreshToken) {
|
||||
if (!refreshToken) {
|
||||
return this.refreshTokenNoCache(refreshToken);
|
||||
}
|
||||
// If a request to refresh using the same token has started,
|
||||
// return the same promise.
|
||||
if (this.refreshTokenPromises.has(refreshToken)) {
|
||||
return this.refreshTokenPromises.get(refreshToken);
|
||||
}
|
||||
const p = this.refreshTokenNoCache(refreshToken).then(r => {
|
||||
this.refreshTokenPromises.delete(refreshToken);
|
||||
return r;
|
||||
}, e => {
|
||||
this.refreshTokenPromises.delete(refreshToken);
|
||||
throw e;
|
||||
});
|
||||
this.refreshTokenPromises.set(refreshToken, p);
|
||||
return p;
|
||||
}
|
||||
async refreshTokenNoCache(refreshToken) {
|
||||
if (!refreshToken) {
|
||||
throw new Error('No refresh token is set.');
|
||||
}
|
||||
const url = OAuth2Client.GOOGLE_OAUTH2_TOKEN_URL_;
|
||||
const data = {
|
||||
refresh_token: refreshToken,
|
||||
client_id: this._clientId,
|
||||
client_secret: this._clientSecret,
|
||||
grant_type: 'refresh_token',
|
||||
};
|
||||
// request for new token
|
||||
const res = await this.transporter.request({
|
||||
method: 'POST',
|
||||
url,
|
||||
data: querystring.stringify(data),
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
});
|
||||
const tokens = res.data;
|
||||
// TODO: de-duplicate this code from a few spots
|
||||
if (res.data && res.data.expires_in) {
|
||||
tokens.expiry_date = new Date().getTime() + res.data.expires_in * 1000;
|
||||
delete tokens.expires_in;
|
||||
}
|
||||
this.emit('tokens', tokens);
|
||||
return { tokens, res };
|
||||
}
|
||||
refreshAccessToken(callback) {
|
||||
if (callback) {
|
||||
this.refreshAccessTokenAsync().then(r => callback(null, r.credentials, r.res), callback);
|
||||
}
|
||||
else {
|
||||
return this.refreshAccessTokenAsync();
|
||||
}
|
||||
}
|
||||
async refreshAccessTokenAsync() {
|
||||
const r = await this.refreshToken(this.credentials.refresh_token);
|
||||
const tokens = r.tokens;
|
||||
tokens.refresh_token = this.credentials.refresh_token;
|
||||
this.credentials = tokens;
|
||||
return { credentials: this.credentials, res: r.res };
|
||||
}
|
||||
getAccessToken(callback) {
|
||||
if (callback) {
|
||||
this.getAccessTokenAsync().then(r => callback(null, r.token, r.res), callback);
|
||||
}
|
||||
else {
|
||||
return this.getAccessTokenAsync();
|
||||
}
|
||||
}
|
||||
async getAccessTokenAsync() {
|
||||
const shouldRefresh = !this.credentials.access_token || this.isTokenExpiring();
|
||||
if (shouldRefresh) {
|
||||
if (!this.credentials.refresh_token) {
|
||||
if (this.refreshHandler) {
|
||||
const refreshedAccessToken = await this.processAndValidateRefreshHandler();
|
||||
if (refreshedAccessToken === null || refreshedAccessToken === void 0 ? void 0 : refreshedAccessToken.access_token) {
|
||||
this.setCredentials(refreshedAccessToken);
|
||||
return { token: this.credentials.access_token };
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('No refresh token or refresh handler callback is set.');
|
||||
}
|
||||
}
|
||||
const r = await this.refreshAccessTokenAsync();
|
||||
if (!r.credentials || (r.credentials && !r.credentials.access_token)) {
|
||||
throw new Error('Could not refresh access token.');
|
||||
}
|
||||
return { token: r.credentials.access_token, res: r.res };
|
||||
}
|
||||
else {
|
||||
return { token: this.credentials.access_token };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The main authentication interface. It takes an optional url which when
|
||||
* present is the endpoint being accessed, and returns a Promise which
|
||||
* resolves with authorization header fields.
|
||||
*
|
||||
* In OAuth2Client, the result has the form:
|
||||
* { Authorization: 'Bearer <access_token_value>' }
|
||||
* @param url The optional url being authorized
|
||||
*/
|
||||
async getRequestHeaders(url) {
|
||||
const headers = (await this.getRequestMetadataAsync(url)).headers;
|
||||
return headers;
|
||||
}
|
||||
async getRequestMetadataAsync(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
url) {
|
||||
const thisCreds = this.credentials;
|
||||
if (!thisCreds.access_token &&
|
||||
!thisCreds.refresh_token &&
|
||||
!this.apiKey &&
|
||||
!this.refreshHandler) {
|
||||
throw new Error('No access, refresh token, API key or refresh handler callback is set.');
|
||||
}
|
||||
if (thisCreds.access_token && !this.isTokenExpiring()) {
|
||||
thisCreds.token_type = thisCreds.token_type || 'Bearer';
|
||||
const headers = {
|
||||
Authorization: thisCreds.token_type + ' ' + thisCreds.access_token,
|
||||
};
|
||||
return { headers: this.addSharedMetadataHeaders(headers) };
|
||||
}
|
||||
// If refreshHandler exists, call processAndValidateRefreshHandler().
|
||||
if (this.refreshHandler) {
|
||||
const refreshedAccessToken = await this.processAndValidateRefreshHandler();
|
||||
if (refreshedAccessToken === null || refreshedAccessToken === void 0 ? void 0 : refreshedAccessToken.access_token) {
|
||||
this.setCredentials(refreshedAccessToken);
|
||||
const headers = {
|
||||
Authorization: 'Bearer ' + this.credentials.access_token,
|
||||
};
|
||||
return { headers: this.addSharedMetadataHeaders(headers) };
|
||||
}
|
||||
}
|
||||
if (this.apiKey) {
|
||||
return { headers: { 'X-Goog-Api-Key': this.apiKey } };
|
||||
}
|
||||
let r = null;
|
||||
let tokens = null;
|
||||
try {
|
||||
r = await this.refreshToken(thisCreds.refresh_token);
|
||||
tokens = r.tokens;
|
||||
}
|
||||
catch (err) {
|
||||
const e = err;
|
||||
if (e.response &&
|
||||
(e.response.status === 403 || e.response.status === 404)) {
|
||||
e.message = `Could not refresh access token: ${e.message}`;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
const credentials = this.credentials;
|
||||
credentials.token_type = credentials.token_type || 'Bearer';
|
||||
tokens.refresh_token = credentials.refresh_token;
|
||||
this.credentials = tokens;
|
||||
const headers = {
|
||||
Authorization: credentials.token_type + ' ' + tokens.access_token,
|
||||
};
|
||||
return { headers: this.addSharedMetadataHeaders(headers), res: r.res };
|
||||
}
|
||||
/**
|
||||
* Generates an URL to revoke the given token.
|
||||
* @param token The existing token to be revoked.
|
||||
*/
|
||||
static getRevokeTokenUrl(token) {
|
||||
const parameters = querystring.stringify({ token });
|
||||
return `${OAuth2Client.GOOGLE_OAUTH2_REVOKE_URL_}?${parameters}`;
|
||||
}
|
||||
revokeToken(token, callback) {
|
||||
const opts = {
|
||||
url: OAuth2Client.getRevokeTokenUrl(token),
|
||||
method: 'POST',
|
||||
};
|
||||
if (callback) {
|
||||
this.transporter
|
||||
.request(opts)
|
||||
.then(r => callback(null, r), callback);
|
||||
}
|
||||
else {
|
||||
return this.transporter.request(opts);
|
||||
}
|
||||
}
|
||||
revokeCredentials(callback) {
|
||||
if (callback) {
|
||||
this.revokeCredentialsAsync().then(res => callback(null, res), callback);
|
||||
}
|
||||
else {
|
||||
return this.revokeCredentialsAsync();
|
||||
}
|
||||
}
|
||||
async revokeCredentialsAsync() {
|
||||
const token = this.credentials.access_token;
|
||||
this.credentials = {};
|
||||
if (token) {
|
||||
return this.revokeToken(token);
|
||||
}
|
||||
else {
|
||||
throw new Error('No access token to revoke.');
|
||||
}
|
||||
}
|
||||
request(opts, callback) {
|
||||
if (callback) {
|
||||
this.requestAsync(opts).then(r => callback(null, r), e => {
|
||||
return callback(e, e.response);
|
||||
});
|
||||
}
|
||||
else {
|
||||
return this.requestAsync(opts);
|
||||
}
|
||||
}
|
||||
async requestAsync(opts, retry = false) {
|
||||
let r2;
|
||||
try {
|
||||
const r = await this.getRequestMetadataAsync(opts.url);
|
||||
opts.headers = opts.headers || {};
|
||||
if (r.headers && r.headers['x-goog-user-project']) {
|
||||
opts.headers['x-goog-user-project'] = r.headers['x-goog-user-project'];
|
||||
}
|
||||
if (r.headers && r.headers.Authorization) {
|
||||
opts.headers.Authorization = r.headers.Authorization;
|
||||
}
|
||||
if (this.apiKey) {
|
||||
opts.headers['X-Goog-Api-Key'] = this.apiKey;
|
||||
}
|
||||
r2 = await this.transporter.request(opts);
|
||||
}
|
||||
catch (e) {
|
||||
const res = e.response;
|
||||
if (res) {
|
||||
const statusCode = res.status;
|
||||
// Retry the request for metadata if the following criteria are true:
|
||||
// - We haven't already retried. It only makes sense to retry once.
|
||||
// - The response was a 401 or a 403
|
||||
// - The request didn't send a readableStream
|
||||
// - An access_token and refresh_token were available, but either no
|
||||
// expiry_date was available or the forceRefreshOnFailure flag is set.
|
||||
// The absent expiry_date case can happen when developers stash the
|
||||
// access_token and refresh_token for later use, but the access_token
|
||||
// fails on the first try because it's expired. Some developers may
|
||||
// choose to enable forceRefreshOnFailure to mitigate time-related
|
||||
// errors.
|
||||
// Or the following criteria are true:
|
||||
// - We haven't already retried. It only makes sense to retry once.
|
||||
// - The response was a 401 or a 403
|
||||
// - The request didn't send a readableStream
|
||||
// - No refresh_token was available
|
||||
// - An access_token and a refreshHandler callback were available, but
|
||||
// either no expiry_date was available or the forceRefreshOnFailure
|
||||
// flag is set. The access_token fails on the first try because it's
|
||||
// expired. Some developers may choose to enable forceRefreshOnFailure
|
||||
// to mitigate time-related errors.
|
||||
const mayRequireRefresh = this.credentials &&
|
||||
this.credentials.access_token &&
|
||||
this.credentials.refresh_token &&
|
||||
(!this.credentials.expiry_date || this.forceRefreshOnFailure);
|
||||
const mayRequireRefreshWithNoRefreshToken = this.credentials &&
|
||||
this.credentials.access_token &&
|
||||
!this.credentials.refresh_token &&
|
||||
(!this.credentials.expiry_date || this.forceRefreshOnFailure) &&
|
||||
this.refreshHandler;
|
||||
const isReadableStream = res.config.data instanceof stream.Readable;
|
||||
const isAuthErr = statusCode === 401 || statusCode === 403;
|
||||
if (!retry && isAuthErr && !isReadableStream && mayRequireRefresh) {
|
||||
await this.refreshAccessTokenAsync();
|
||||
return this.requestAsync(opts, true);
|
||||
}
|
||||
else if (!retry &&
|
||||
isAuthErr &&
|
||||
!isReadableStream &&
|
||||
mayRequireRefreshWithNoRefreshToken) {
|
||||
const refreshedAccessToken = await this.processAndValidateRefreshHandler();
|
||||
if (refreshedAccessToken === null || refreshedAccessToken === void 0 ? void 0 : refreshedAccessToken.access_token) {
|
||||
this.setCredentials(refreshedAccessToken);
|
||||
}
|
||||
return this.requestAsync(opts, true);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return r2;
|
||||
}
|
||||
verifyIdToken(options, callback) {
|
||||
// This function used to accept two arguments instead of an options object.
|
||||
// Check the types to help users upgrade with less pain.
|
||||
// This check can be removed after a 2.0 release.
|
||||
if (callback && typeof callback !== 'function') {
|
||||
throw new Error('This method accepts an options object as the first parameter, which includes the idToken, audience, and maxExpiry.');
|
||||
}
|
||||
if (callback) {
|
||||
this.verifyIdTokenAsync(options).then(r => callback(null, r), callback);
|
||||
}
|
||||
else {
|
||||
return this.verifyIdTokenAsync(options);
|
||||
}
|
||||
}
|
||||
async verifyIdTokenAsync(options) {
|
||||
if (!options.idToken) {
|
||||
throw new Error('The verifyIdToken method requires an ID Token');
|
||||
}
|
||||
const response = await this.getFederatedSignonCertsAsync();
|
||||
const login = await this.verifySignedJwtWithCertsAsync(options.idToken, response.certs, options.audience, OAuth2Client.ISSUERS_, options.maxExpiry);
|
||||
return login;
|
||||
}
|
||||
/**
|
||||
* Obtains information about the provisioned access token. Especially useful
|
||||
* if you want to check the scopes that were provisioned to a given token.
|
||||
*
|
||||
* @param accessToken Required. The Access Token for which you want to get
|
||||
* user info.
|
||||
*/
|
||||
async getTokenInfo(accessToken) {
|
||||
const { data } = await this.transporter.request({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
url: OAuth2Client.GOOGLE_TOKEN_INFO_URL,
|
||||
});
|
||||
const info = Object.assign({
|
||||
expiry_date: new Date().getTime() + data.expires_in * 1000,
|
||||
scopes: data.scope.split(' '),
|
||||
}, data);
|
||||
delete info.expires_in;
|
||||
delete info.scope;
|
||||
return info;
|
||||
}
|
||||
getFederatedSignonCerts(callback) {
|
||||
if (callback) {
|
||||
this.getFederatedSignonCertsAsync().then(r => callback(null, r.certs, r.res), callback);
|
||||
}
|
||||
else {
|
||||
return this.getFederatedSignonCertsAsync();
|
||||
}
|
||||
}
|
||||
async getFederatedSignonCertsAsync() {
|
||||
const nowTime = new Date().getTime();
|
||||
const format = crypto_1.hasBrowserCrypto()
|
||||
? CertificateFormat.JWK
|
||||
: CertificateFormat.PEM;
|
||||
if (this.certificateExpiry &&
|
||||
nowTime < this.certificateExpiry.getTime() &&
|
||||
this.certificateCacheFormat === format) {
|
||||
return { certs: this.certificateCache, format };
|
||||
}
|
||||
let res;
|
||||
let url;
|
||||
switch (format) {
|
||||
case CertificateFormat.PEM:
|
||||
url = OAuth2Client.GOOGLE_OAUTH2_FEDERATED_SIGNON_PEM_CERTS_URL_;
|
||||
break;
|
||||
case CertificateFormat.JWK:
|
||||
url = OAuth2Client.GOOGLE_OAUTH2_FEDERATED_SIGNON_JWK_CERTS_URL_;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported certificate format ${format}`);
|
||||
}
|
||||
try {
|
||||
res = await this.transporter.request({ url });
|
||||
}
|
||||
catch (e) {
|
||||
e.message = `Failed to retrieve verification certificates: ${e.message}`;
|
||||
throw e;
|
||||
}
|
||||
const cacheControl = res ? res.headers['cache-control'] : undefined;
|
||||
let cacheAge = -1;
|
||||
if (cacheControl) {
|
||||
const pattern = new RegExp('max-age=([0-9]*)');
|
||||
const regexResult = pattern.exec(cacheControl);
|
||||
if (regexResult && regexResult.length === 2) {
|
||||
// Cache results with max-age (in seconds)
|
||||
cacheAge = Number(regexResult[1]) * 1000; // milliseconds
|
||||
}
|
||||
}
|
||||
let certificates = {};
|
||||
switch (format) {
|
||||
case CertificateFormat.PEM:
|
||||
certificates = res.data;
|
||||
break;
|
||||
case CertificateFormat.JWK:
|
||||
for (const key of res.data.keys) {
|
||||
certificates[key.kid] = key;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported certificate format ${format}`);
|
||||
}
|
||||
const now = new Date();
|
||||
this.certificateExpiry =
|
||||
cacheAge === -1 ? null : new Date(now.getTime() + cacheAge);
|
||||
this.certificateCache = certificates;
|
||||
this.certificateCacheFormat = format;
|
||||
return { certs: certificates, format, res };
|
||||
}
|
||||
getIapPublicKeys(callback) {
|
||||
if (callback) {
|
||||
this.getIapPublicKeysAsync().then(r => callback(null, r.pubkeys, r.res), callback);
|
||||
}
|
||||
else {
|
||||
return this.getIapPublicKeysAsync();
|
||||
}
|
||||
}
|
||||
async getIapPublicKeysAsync() {
|
||||
let res;
|
||||
const url = OAuth2Client.GOOGLE_OAUTH2_IAP_PUBLIC_KEY_URL_;
|
||||
try {
|
||||
res = await this.transporter.request({ url });
|
||||
}
|
||||
catch (e) {
|
||||
e.message = `Failed to retrieve verification certificates: ${e.message}`;
|
||||
throw e;
|
||||
}
|
||||
return { pubkeys: res.data, res };
|
||||
}
|
||||
verifySignedJwtWithCerts() {
|
||||
// To make the code compatible with browser SubtleCrypto we need to make
|
||||
// this method async.
|
||||
throw new Error('verifySignedJwtWithCerts is removed, please use verifySignedJwtWithCertsAsync instead.');
|
||||
}
|
||||
/**
|
||||
* Verify the id token is signed with the correct certificate
|
||||
* and is from the correct audience.
|
||||
* @param jwt The jwt to verify (The ID Token in this case).
|
||||
* @param certs The array of certs to test the jwt against.
|
||||
* @param requiredAudience The audience to test the jwt against.
|
||||
* @param issuers The allowed issuers of the jwt (Optional).
|
||||
* @param maxExpiry The max expiry the certificate can be (Optional).
|
||||
* @return Returns a promise resolving to LoginTicket on verification.
|
||||
*/
|
||||
async verifySignedJwtWithCertsAsync(jwt, certs, requiredAudience, issuers, maxExpiry) {
|
||||
const crypto = crypto_1.createCrypto();
|
||||
if (!maxExpiry) {
|
||||
maxExpiry = OAuth2Client.MAX_TOKEN_LIFETIME_SECS_;
|
||||
}
|
||||
const segments = jwt.split('.');
|
||||
if (segments.length !== 3) {
|
||||
throw new Error('Wrong number of segments in token: ' + jwt);
|
||||
}
|
||||
const signed = segments[0] + '.' + segments[1];
|
||||
let signature = segments[2];
|
||||
let envelope;
|
||||
let payload;
|
||||
try {
|
||||
envelope = JSON.parse(crypto.decodeBase64StringUtf8(segments[0]));
|
||||
}
|
||||
catch (err) {
|
||||
err.message = `Can't parse token envelope: ${segments[0]}': ${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
if (!envelope) {
|
||||
throw new Error("Can't parse token envelope: " + segments[0]);
|
||||
}
|
||||
try {
|
||||
payload = JSON.parse(crypto.decodeBase64StringUtf8(segments[1]));
|
||||
}
|
||||
catch (err) {
|
||||
err.message = `Can't parse token payload '${segments[0]}`;
|
||||
throw err;
|
||||
}
|
||||
if (!payload) {
|
||||
throw new Error("Can't parse token payload: " + segments[1]);
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(certs, envelope.kid)) {
|
||||
// If this is not present, then there's no reason to attempt verification
|
||||
throw new Error('No pem found for envelope: ' + JSON.stringify(envelope));
|
||||
}
|
||||
const cert = certs[envelope.kid];
|
||||
if (envelope.alg === 'ES256') {
|
||||
signature = formatEcdsa.joseToDer(signature, 'ES256').toString('base64');
|
||||
}
|
||||
const verified = await crypto.verify(cert, signed, signature);
|
||||
if (!verified) {
|
||||
throw new Error('Invalid token signature: ' + jwt);
|
||||
}
|
||||
if (!payload.iat) {
|
||||
throw new Error('No issue time in token: ' + JSON.stringify(payload));
|
||||
}
|
||||
if (!payload.exp) {
|
||||
throw new Error('No expiration time in token: ' + JSON.stringify(payload));
|
||||
}
|
||||
const iat = Number(payload.iat);
|
||||
if (isNaN(iat))
|
||||
throw new Error('iat field using invalid format');
|
||||
const exp = Number(payload.exp);
|
||||
if (isNaN(exp))
|
||||
throw new Error('exp field using invalid format');
|
||||
const now = new Date().getTime() / 1000;
|
||||
if (exp >= now + maxExpiry) {
|
||||
throw new Error('Expiration time too far in future: ' + JSON.stringify(payload));
|
||||
}
|
||||
const earliest = iat - OAuth2Client.CLOCK_SKEW_SECS_;
|
||||
const latest = exp + OAuth2Client.CLOCK_SKEW_SECS_;
|
||||
if (now < earliest) {
|
||||
throw new Error('Token used too early, ' +
|
||||
now +
|
||||
' < ' +
|
||||
earliest +
|
||||
': ' +
|
||||
JSON.stringify(payload));
|
||||
}
|
||||
if (now > latest) {
|
||||
throw new Error('Token used too late, ' +
|
||||
now +
|
||||
' > ' +
|
||||
latest +
|
||||
': ' +
|
||||
JSON.stringify(payload));
|
||||
}
|
||||
if (issuers && issuers.indexOf(payload.iss) < 0) {
|
||||
throw new Error('Invalid issuer, expected one of [' +
|
||||
issuers +
|
||||
'], but got ' +
|
||||
payload.iss);
|
||||
}
|
||||
// Check the audience matches if we have one
|
||||
if (typeof requiredAudience !== 'undefined' && requiredAudience !== null) {
|
||||
const aud = payload.aud;
|
||||
let audVerified = false;
|
||||
// If the requiredAudience is an array, check if it contains token
|
||||
// audience
|
||||
if (requiredAudience.constructor === Array) {
|
||||
audVerified = requiredAudience.indexOf(aud) > -1;
|
||||
}
|
||||
else {
|
||||
audVerified = aud === requiredAudience;
|
||||
}
|
||||
if (!audVerified) {
|
||||
throw new Error('Wrong recipient, payload audience != requiredAudience');
|
||||
}
|
||||
}
|
||||
return new loginticket_1.LoginTicket(envelope, payload);
|
||||
}
|
||||
/**
|
||||
* Returns a promise that resolves with AccessTokenResponse type if
|
||||
* refreshHandler is defined.
|
||||
* If not, nothing is returned.
|
||||
*/
|
||||
async processAndValidateRefreshHandler() {
|
||||
if (this.refreshHandler) {
|
||||
const accessTokenResponse = await this.refreshHandler();
|
||||
if (!accessTokenResponse.access_token) {
|
||||
throw new Error('No access token is returned by the refreshHandler callback.');
|
||||
}
|
||||
return accessTokenResponse;
|
||||
}
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Returns true if a token is expired or will expire within
|
||||
* eagerRefreshThresholdMillismilliseconds.
|
||||
* If there is no expiry time, assumes the token is not expired or expiring.
|
||||
*/
|
||||
isTokenExpiring() {
|
||||
const expiryDate = this.credentials.expiry_date;
|
||||
return expiryDate
|
||||
? expiryDate <= new Date().getTime() + this.eagerRefreshThresholdMillis
|
||||
: false;
|
||||
}
|
||||
}
|
||||
exports.OAuth2Client = OAuth2Client;
|
||||
OAuth2Client.GOOGLE_TOKEN_INFO_URL = 'https://oauth2.googleapis.com/tokeninfo';
|
||||
/**
|
||||
* The base URL for auth endpoints.
|
||||
*/
|
||||
OAuth2Client.GOOGLE_OAUTH2_AUTH_BASE_URL_ = 'https://accounts.google.com/o/oauth2/v2/auth';
|
||||
/**
|
||||
* The base endpoint for token retrieval.
|
||||
*/
|
||||
OAuth2Client.GOOGLE_OAUTH2_TOKEN_URL_ = 'https://oauth2.googleapis.com/token';
|
||||
/**
|
||||
* The base endpoint to revoke tokens.
|
||||
*/
|
||||
OAuth2Client.GOOGLE_OAUTH2_REVOKE_URL_ = 'https://oauth2.googleapis.com/revoke';
|
||||
/**
|
||||
* Google Sign on certificates in PEM format.
|
||||
*/
|
||||
OAuth2Client.GOOGLE_OAUTH2_FEDERATED_SIGNON_PEM_CERTS_URL_ = 'https://www.googleapis.com/oauth2/v1/certs';
|
||||
/**
|
||||
* Google Sign on certificates in JWK format.
|
||||
*/
|
||||
OAuth2Client.GOOGLE_OAUTH2_FEDERATED_SIGNON_JWK_CERTS_URL_ = 'https://www.googleapis.com/oauth2/v3/certs';
|
||||
/**
|
||||
* Google Sign on certificates in JWK format.
|
||||
*/
|
||||
OAuth2Client.GOOGLE_OAUTH2_IAP_PUBLIC_KEY_URL_ = 'https://www.gstatic.com/iap/verify/public_key';
|
||||
/**
|
||||
* Clock skew - five minutes in seconds
|
||||
*/
|
||||
OAuth2Client.CLOCK_SKEW_SECS_ = 300;
|
||||
/**
|
||||
* Max Token Lifetime is one day in seconds
|
||||
*/
|
||||
OAuth2Client.MAX_TOKEN_LIFETIME_SECS_ = 86400;
|
||||
/**
|
||||
* The allowed oauth token issuers.
|
||||
*/
|
||||
OAuth2Client.ISSUERS_ = [
|
||||
'accounts.google.com',
|
||||
'https://accounts.google.com',
|
||||
];
|
||||
//# sourceMappingURL=oauth2client.js.map
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import { GaxiosOptions } from 'gaxios';
|
||||
/**
|
||||
* OAuth error codes.
|
||||
* https://tools.ietf.org/html/rfc6749#section-5.2
|
||||
*/
|
||||
declare type OAuthErrorCode = 'invalid_request' | 'invalid_client' | 'invalid_grant' | 'unauthorized_client' | 'unsupported_grant_type' | 'invalid_scope' | string;
|
||||
/**
|
||||
* The standard OAuth error response.
|
||||
* https://tools.ietf.org/html/rfc6749#section-5.2
|
||||
*/
|
||||
export interface OAuthErrorResponse {
|
||||
error: OAuthErrorCode;
|
||||
error_description?: string;
|
||||
error_uri?: string;
|
||||
}
|
||||
/**
|
||||
* OAuth client authentication types.
|
||||
* https://tools.ietf.org/html/rfc6749#section-2.3
|
||||
*/
|
||||
export declare type ConfidentialClientType = 'basic' | 'request-body';
|
||||
/**
|
||||
* Defines the client authentication credentials for basic and request-body
|
||||
* credentials.
|
||||
* https://tools.ietf.org/html/rfc6749#section-2.3.1
|
||||
*/
|
||||
export interface ClientAuthentication {
|
||||
confidentialClientType: ConfidentialClientType;
|
||||
clientId: string;
|
||||
clientSecret?: string;
|
||||
}
|
||||
/**
|
||||
* Abstract class for handling client authentication in OAuth-based
|
||||
* operations.
|
||||
* When request-body client authentication is used, only application/json and
|
||||
* application/x-www-form-urlencoded content types for HTTP methods that support
|
||||
* request bodies are supported.
|
||||
*/
|
||||
export declare abstract class OAuthClientAuthHandler {
|
||||
private readonly clientAuthentication?;
|
||||
private crypto;
|
||||
/**
|
||||
* Instantiates an OAuth client authentication handler.
|
||||
* @param clientAuthentication The client auth credentials.
|
||||
*/
|
||||
constructor(clientAuthentication?: ClientAuthentication | undefined);
|
||||
/**
|
||||
* Applies client authentication on the OAuth request's headers or POST
|
||||
* body but does not process the request.
|
||||
* @param opts The GaxiosOptions whose headers or data are to be modified
|
||||
* depending on the client authentication mechanism to be used.
|
||||
* @param bearerToken The optional bearer token to use for authentication.
|
||||
* When this is used, no client authentication credentials are needed.
|
||||
*/
|
||||
protected applyClientAuthenticationOptions(opts: GaxiosOptions, bearerToken?: string): void;
|
||||
/**
|
||||
* Applies client authentication on the request's header if either
|
||||
* basic authentication or bearer token authentication is selected.
|
||||
*
|
||||
* @param opts The GaxiosOptions whose headers or data are to be modified
|
||||
* depending on the client authentication mechanism to be used.
|
||||
* @param bearerToken The optional bearer token to use for authentication.
|
||||
* When this is used, no client authentication credentials are needed.
|
||||
*/
|
||||
private injectAuthenticatedHeaders;
|
||||
/**
|
||||
* Applies client authentication on the request's body if request-body
|
||||
* client authentication is selected.
|
||||
*
|
||||
* @param opts The GaxiosOptions whose headers or data are to be modified
|
||||
* depending on the client authentication mechanism to be used.
|
||||
*/
|
||||
private injectAuthenticatedRequestBody;
|
||||
}
|
||||
/**
|
||||
* Converts an OAuth error response to a native JavaScript Error.
|
||||
* @param resp The OAuth error response to convert to a native Error object.
|
||||
* @param err The optional original error. If provided, the error properties
|
||||
* will be copied to the new error.
|
||||
* @return The converted native Error object.
|
||||
*/
|
||||
export declare function getErrorFromOAuthErrorResponse(resp: OAuthErrorResponse, err?: Error): Error;
|
||||
export {};
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
"use strict";
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getErrorFromOAuthErrorResponse = exports.OAuthClientAuthHandler = void 0;
|
||||
const querystring = require("querystring");
|
||||
const crypto_1 = require("../crypto/crypto");
|
||||
/** List of HTTP methods that accept request bodies. */
|
||||
const METHODS_SUPPORTING_REQUEST_BODY = ['PUT', 'POST', 'PATCH'];
|
||||
/**
|
||||
* Abstract class for handling client authentication in OAuth-based
|
||||
* operations.
|
||||
* When request-body client authentication is used, only application/json and
|
||||
* application/x-www-form-urlencoded content types for HTTP methods that support
|
||||
* request bodies are supported.
|
||||
*/
|
||||
class OAuthClientAuthHandler {
|
||||
/**
|
||||
* Instantiates an OAuth client authentication handler.
|
||||
* @param clientAuthentication The client auth credentials.
|
||||
*/
|
||||
constructor(clientAuthentication) {
|
||||
this.clientAuthentication = clientAuthentication;
|
||||
this.crypto = crypto_1.createCrypto();
|
||||
}
|
||||
/**
|
||||
* Applies client authentication on the OAuth request's headers or POST
|
||||
* body but does not process the request.
|
||||
* @param opts The GaxiosOptions whose headers or data are to be modified
|
||||
* depending on the client authentication mechanism to be used.
|
||||
* @param bearerToken The optional bearer token to use for authentication.
|
||||
* When this is used, no client authentication credentials are needed.
|
||||
*/
|
||||
applyClientAuthenticationOptions(opts, bearerToken) {
|
||||
// Inject authenticated header.
|
||||
this.injectAuthenticatedHeaders(opts, bearerToken);
|
||||
// Inject authenticated request body.
|
||||
if (!bearerToken) {
|
||||
this.injectAuthenticatedRequestBody(opts);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Applies client authentication on the request's header if either
|
||||
* basic authentication or bearer token authentication is selected.
|
||||
*
|
||||
* @param opts The GaxiosOptions whose headers or data are to be modified
|
||||
* depending on the client authentication mechanism to be used.
|
||||
* @param bearerToken The optional bearer token to use for authentication.
|
||||
* When this is used, no client authentication credentials are needed.
|
||||
*/
|
||||
injectAuthenticatedHeaders(opts, bearerToken) {
|
||||
var _a;
|
||||
// Bearer token prioritized higher than basic Auth.
|
||||
if (bearerToken) {
|
||||
opts.headers = opts.headers || {};
|
||||
Object.assign(opts.headers, {
|
||||
Authorization: `Bearer ${bearerToken}}`,
|
||||
});
|
||||
}
|
||||
else if (((_a = this.clientAuthentication) === null || _a === void 0 ? void 0 : _a.confidentialClientType) === 'basic') {
|
||||
opts.headers = opts.headers || {};
|
||||
const clientId = this.clientAuthentication.clientId;
|
||||
const clientSecret = this.clientAuthentication.clientSecret || '';
|
||||
const base64EncodedCreds = this.crypto.encodeBase64StringUtf8(`${clientId}:${clientSecret}`);
|
||||
Object.assign(opts.headers, {
|
||||
Authorization: `Basic ${base64EncodedCreds}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Applies client authentication on the request's body if request-body
|
||||
* client authentication is selected.
|
||||
*
|
||||
* @param opts The GaxiosOptions whose headers or data are to be modified
|
||||
* depending on the client authentication mechanism to be used.
|
||||
*/
|
||||
injectAuthenticatedRequestBody(opts) {
|
||||
var _a;
|
||||
if (((_a = this.clientAuthentication) === null || _a === void 0 ? void 0 : _a.confidentialClientType) === 'request-body') {
|
||||
const method = (opts.method || 'GET').toUpperCase();
|
||||
// Inject authenticated request body.
|
||||
if (METHODS_SUPPORTING_REQUEST_BODY.indexOf(method) !== -1) {
|
||||
// Get content-type.
|
||||
let contentType;
|
||||
const headers = opts.headers || {};
|
||||
for (const key in headers) {
|
||||
if (key.toLowerCase() === 'content-type' && headers[key]) {
|
||||
contentType = headers[key].toLowerCase();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (contentType === 'application/x-www-form-urlencoded') {
|
||||
opts.data = opts.data || '';
|
||||
const data = querystring.parse(opts.data);
|
||||
Object.assign(data, {
|
||||
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 || '',
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new Error(`${contentType} content-types are not supported with ` +
|
||||
`${this.clientAuthentication.confidentialClientType} ` +
|
||||
'client authentication');
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error(`${method} HTTP method does not support ` +
|
||||
`${this.clientAuthentication.confidentialClientType} ` +
|
||||
'client authentication');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.OAuthClientAuthHandler = OAuthClientAuthHandler;
|
||||
/**
|
||||
* Converts an OAuth error response to a native JavaScript Error.
|
||||
* @param resp The OAuth error response to convert to a native Error object.
|
||||
* @param err The optional original error. If provided, the error properties
|
||||
* will be copied to the new error.
|
||||
* @return The converted native Error object.
|
||||
*/
|
||||
function getErrorFromOAuthErrorResponse(resp, err) {
|
||||
// Error response.
|
||||
const errorCode = resp.error;
|
||||
const errorDescription = resp.error_description;
|
||||
const errorUri = resp.error_uri;
|
||||
let message = `Error code ${errorCode}`;
|
||||
if (typeof errorDescription !== 'undefined') {
|
||||
message += `: ${errorDescription}`;
|
||||
}
|
||||
if (typeof errorUri !== 'undefined') {
|
||||
message += ` - ${errorUri}`;
|
||||
}
|
||||
const newError = new Error(message);
|
||||
// Copy properties from original error to newly generated error.
|
||||
if (err) {
|
||||
const keys = Object.keys(err);
|
||||
if (err.stack) {
|
||||
// Copy error.stack if available.
|
||||
keys.push('stack');
|
||||
}
|
||||
keys.forEach(key => {
|
||||
// Do not overwrite the message field.
|
||||
if (key !== 'message') {
|
||||
Object.defineProperty(newError, key, {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: err[key],
|
||||
writable: false,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return newError;
|
||||
}
|
||||
exports.getErrorFromOAuthErrorResponse = getErrorFromOAuthErrorResponse;
|
||||
//# sourceMappingURL=oauth2common.js.map
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/// <reference types="node" />
|
||||
import * as stream from 'stream';
|
||||
import { JWTInput } from './credentials';
|
||||
import { GetTokenResponse, OAuth2Client, RefreshOptions } from './oauth2client';
|
||||
export interface UserRefreshClientOptions extends RefreshOptions {
|
||||
clientId?: string;
|
||||
clientSecret?: string;
|
||||
refreshToken?: string;
|
||||
}
|
||||
export declare class UserRefreshClient extends OAuth2Client {
|
||||
_refreshToken?: string | null;
|
||||
/**
|
||||
* User Refresh Token credentials.
|
||||
*
|
||||
* @param clientId The authentication client ID.
|
||||
* @param clientSecret The authentication client secret.
|
||||
* @param refreshToken The authentication refresh token.
|
||||
*/
|
||||
constructor(clientId?: string, clientSecret?: string, refreshToken?: string);
|
||||
constructor(options: UserRefreshClientOptions);
|
||||
constructor(clientId?: string, clientSecret?: string, refreshToken?: string);
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
* @param refreshToken An ignored refreshToken..
|
||||
* @param callback Optional callback.
|
||||
*/
|
||||
protected refreshTokenNoCache(refreshToken?: string | null): Promise<GetTokenResponse>;
|
||||
/**
|
||||
* Create a UserRefreshClient credentials instance using the given input
|
||||
* options.
|
||||
* @param json The input object.
|
||||
*/
|
||||
fromJSON(json: JWTInput): void;
|
||||
/**
|
||||
* Create a UserRefreshClient credentials instance using the given input
|
||||
* stream.
|
||||
* @param inputStream The input stream.
|
||||
* @param callback Optional callback.
|
||||
*/
|
||||
fromStream(inputStream: stream.Readable): Promise<void>;
|
||||
fromStream(inputStream: stream.Readable, callback: (err?: Error) => void): void;
|
||||
private fromStreamAsync;
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
"use strict";
|
||||
// Copyright 2015 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UserRefreshClient = void 0;
|
||||
const oauth2client_1 = require("./oauth2client");
|
||||
class UserRefreshClient extends oauth2client_1.OAuth2Client {
|
||||
constructor(optionsOrClientId, clientSecret, refreshToken, eagerRefreshThresholdMillis, forceRefreshOnFailure) {
|
||||
const opts = optionsOrClientId && typeof optionsOrClientId === 'object'
|
||||
? optionsOrClientId
|
||||
: {
|
||||
clientId: optionsOrClientId,
|
||||
clientSecret,
|
||||
refreshToken,
|
||||
eagerRefreshThresholdMillis,
|
||||
forceRefreshOnFailure,
|
||||
};
|
||||
super({
|
||||
clientId: opts.clientId,
|
||||
clientSecret: opts.clientSecret,
|
||||
eagerRefreshThresholdMillis: opts.eagerRefreshThresholdMillis,
|
||||
forceRefreshOnFailure: opts.forceRefreshOnFailure,
|
||||
});
|
||||
this._refreshToken = opts.refreshToken;
|
||||
this.credentials.refresh_token = opts.refreshToken;
|
||||
}
|
||||
/**
|
||||
* Refreshes the access token.
|
||||
* @param refreshToken An ignored refreshToken..
|
||||
* @param callback Optional callback.
|
||||
*/
|
||||
async refreshTokenNoCache(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
refreshToken) {
|
||||
return super.refreshTokenNoCache(this._refreshToken);
|
||||
}
|
||||
/**
|
||||
* Create a UserRefreshClient credentials instance using the given input
|
||||
* options.
|
||||
* @param json The input object.
|
||||
*/
|
||||
fromJSON(json) {
|
||||
if (!json) {
|
||||
throw new Error('Must pass in a JSON object containing the user refresh token');
|
||||
}
|
||||
if (json.type !== 'authorized_user') {
|
||||
throw new Error('The incoming JSON object does not have the "authorized_user" type');
|
||||
}
|
||||
if (!json.client_id) {
|
||||
throw new Error('The incoming JSON object does not contain a client_id field');
|
||||
}
|
||||
if (!json.client_secret) {
|
||||
throw new Error('The incoming JSON object does not contain a client_secret field');
|
||||
}
|
||||
if (!json.refresh_token) {
|
||||
throw new Error('The incoming JSON object does not contain a refresh_token field');
|
||||
}
|
||||
this._clientId = json.client_id;
|
||||
this._clientSecret = json.client_secret;
|
||||
this._refreshToken = json.refresh_token;
|
||||
this.credentials.refresh_token = json.refresh_token;
|
||||
this.quotaProjectId = json.quota_project_id;
|
||||
}
|
||||
fromStream(inputStream, callback) {
|
||||
if (callback) {
|
||||
this.fromStreamAsync(inputStream).then(() => callback(), callback);
|
||||
}
|
||||
else {
|
||||
return this.fromStreamAsync(inputStream);
|
||||
}
|
||||
}
|
||||
async fromStreamAsync(inputStream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!inputStream) {
|
||||
return reject(new Error('Must pass in a stream containing the user refresh token.'));
|
||||
}
|
||||
let s = '';
|
||||
inputStream
|
||||
.setEncoding('utf8')
|
||||
.on('error', reject)
|
||||
.on('data', chunk => (s += chunk))
|
||||
.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(s);
|
||||
this.fromJSON(data);
|
||||
return resolve();
|
||||
}
|
||||
catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.UserRefreshClient = UserRefreshClient;
|
||||
//# sourceMappingURL=refreshclient.js.map
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
import { GaxiosResponse } from 'gaxios';
|
||||
import { Headers } from './oauth2client';
|
||||
import { ClientAuthentication, OAuthClientAuthHandler } from './oauth2common';
|
||||
/**
|
||||
* Defines the interface needed to initialize an StsCredentials instance.
|
||||
* The interface does not directly map to the spec and instead is converted
|
||||
* to be compliant with the JavaScript style guide. This is because this is
|
||||
* instantiated internally.
|
||||
* StsCredentials implement the OAuth 2.0 token exchange based on
|
||||
* https://tools.ietf.org/html/rfc8693.
|
||||
* Request options are defined in
|
||||
* https://tools.ietf.org/html/rfc8693#section-2.1
|
||||
*/
|
||||
export interface StsCredentialsOptions {
|
||||
/**
|
||||
* REQUIRED. The value "urn:ietf:params:oauth:grant-type:token-exchange"
|
||||
* indicates that a token exchange is being performed.
|
||||
*/
|
||||
grantType: string;
|
||||
/**
|
||||
* OPTIONAL. A URI that indicates the target service or resource where the
|
||||
* client intends to use the requested security token.
|
||||
*/
|
||||
resource?: string;
|
||||
/**
|
||||
* OPTIONAL. The logical name of the target service where the client
|
||||
* intends to use the requested security token. This serves a purpose
|
||||
* similar to the "resource" parameter but with the client providing a
|
||||
* logical name for the target service.
|
||||
*/
|
||||
audience?: string;
|
||||
/**
|
||||
* OPTIONAL. A list of space-delimited, case-sensitive strings, as defined
|
||||
* in Section 3.3 of [RFC6749], that allow the client to specify the desired
|
||||
* scope of the requested security token in the context of the service or
|
||||
* resource where the token will be used.
|
||||
*/
|
||||
scope?: string[];
|
||||
/**
|
||||
* OPTIONAL. An identifier, as described in Section 3 of [RFC8693], eg.
|
||||
* "urn:ietf:params:oauth:token-type:access_token" for the type of the
|
||||
* requested security token.
|
||||
*/
|
||||
requestedTokenType?: string;
|
||||
/**
|
||||
* REQUIRED. A security token that represents the identity of the party on
|
||||
* behalf of whom the request is being made.
|
||||
*/
|
||||
subjectToken: string;
|
||||
/**
|
||||
* REQUIRED. An identifier, as described in Section 3 of [RFC8693], that
|
||||
* indicates the type of the security token in the "subject_token" parameter.
|
||||
*/
|
||||
subjectTokenType: string;
|
||||
actingParty?: {
|
||||
/**
|
||||
* OPTIONAL. A security token that represents the identity of the acting
|
||||
* party. Typically, this will be the party that is authorized to use the
|
||||
* requested security token and act on behalf of the subject.
|
||||
*/
|
||||
actorToken: string;
|
||||
/**
|
||||
* An identifier, as described in Section 3, that indicates the type of the
|
||||
* security token in the "actor_token" parameter. This is REQUIRED when the
|
||||
* "actor_token" parameter is present in the request but MUST NOT be
|
||||
* included otherwise.
|
||||
*/
|
||||
actorTokenType: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Defines the OAuth 2.0 token exchange successful response based on
|
||||
* https://tools.ietf.org/html/rfc8693#section-2.2.1
|
||||
*/
|
||||
export interface StsSuccessfulResponse {
|
||||
access_token: string;
|
||||
issued_token_type: string;
|
||||
token_type: string;
|
||||
expires_in?: number;
|
||||
refresh_token?: string;
|
||||
scope?: string;
|
||||
res?: GaxiosResponse | null;
|
||||
}
|
||||
/**
|
||||
* Implements the OAuth 2.0 token exchange based on
|
||||
* https://tools.ietf.org/html/rfc8693
|
||||
*/
|
||||
export declare class StsCredentials extends OAuthClientAuthHandler {
|
||||
private readonly tokenExchangeEndpoint;
|
||||
private transporter;
|
||||
/**
|
||||
* Initializes an STS credentials instance.
|
||||
* @param tokenExchangeEndpoint The token exchange endpoint.
|
||||
* @param clientAuthentication The client authentication credentials if
|
||||
* available.
|
||||
*/
|
||||
constructor(tokenExchangeEndpoint: string, clientAuthentication?: ClientAuthentication);
|
||||
/**
|
||||
* Exchanges the provided token for another type of token based on the
|
||||
* rfc8693 spec.
|
||||
* @param stsCredentialsOptions The token exchange options used to populate
|
||||
* the token exchange request.
|
||||
* @param additionalHeaders Optional additional headers to pass along the
|
||||
* request.
|
||||
* @param options Optional additional GCP-specific non-spec defined options
|
||||
* to send with the request.
|
||||
* Example: `&options=${encodeUriComponent(JSON.stringified(options))}`
|
||||
* @return A promise that resolves with the token exchange response containing
|
||||
* the requested token and its expiration time.
|
||||
*/
|
||||
exchangeToken(stsCredentialsOptions: StsCredentialsOptions, additionalHeaders?: Headers, options?: {
|
||||
[key: string]: any;
|
||||
}): Promise<StsSuccessfulResponse>;
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
"use strict";
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.StsCredentials = void 0;
|
||||
const querystring = require("querystring");
|
||||
const transporters_1 = require("../transporters");
|
||||
const oauth2common_1 = require("./oauth2common");
|
||||
/**
|
||||
* Implements the OAuth 2.0 token exchange based on
|
||||
* https://tools.ietf.org/html/rfc8693
|
||||
*/
|
||||
class StsCredentials extends oauth2common_1.OAuthClientAuthHandler {
|
||||
/**
|
||||
* Initializes an STS credentials instance.
|
||||
* @param tokenExchangeEndpoint The token exchange endpoint.
|
||||
* @param clientAuthentication The client authentication credentials if
|
||||
* available.
|
||||
*/
|
||||
constructor(tokenExchangeEndpoint, clientAuthentication) {
|
||||
super(clientAuthentication);
|
||||
this.tokenExchangeEndpoint = tokenExchangeEndpoint;
|
||||
this.transporter = new transporters_1.DefaultTransporter();
|
||||
}
|
||||
/**
|
||||
* Exchanges the provided token for another type of token based on the
|
||||
* rfc8693 spec.
|
||||
* @param stsCredentialsOptions The token exchange options used to populate
|
||||
* the token exchange request.
|
||||
* @param additionalHeaders Optional additional headers to pass along the
|
||||
* request.
|
||||
* @param options Optional additional GCP-specific non-spec defined options
|
||||
* to send with the request.
|
||||
* Example: `&options=${encodeUriComponent(JSON.stringified(options))}`
|
||||
* @return A promise that resolves with the token exchange response containing
|
||||
* the requested token and its expiration time.
|
||||
*/
|
||||
async exchangeToken(stsCredentialsOptions, additionalHeaders,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
options) {
|
||||
var _a, _b, _c;
|
||||
const values = {
|
||||
grant_type: stsCredentialsOptions.grantType,
|
||||
resource: stsCredentialsOptions.resource,
|
||||
audience: stsCredentialsOptions.audience,
|
||||
scope: (_a = stsCredentialsOptions.scope) === null || _a === void 0 ? void 0 : _a.join(' '),
|
||||
requested_token_type: stsCredentialsOptions.requestedTokenType,
|
||||
subject_token: stsCredentialsOptions.subjectToken,
|
||||
subject_token_type: stsCredentialsOptions.subjectTokenType,
|
||||
actor_token: (_b = stsCredentialsOptions.actingParty) === null || _b === void 0 ? void 0 : _b.actorToken,
|
||||
actor_token_type: (_c = stsCredentialsOptions.actingParty) === null || _c === void 0 ? void 0 : _c.actorTokenType,
|
||||
// Non-standard GCP-specific options.
|
||||
options: options && JSON.stringify(options),
|
||||
};
|
||||
// Remove undefined fields.
|
||||
Object.keys(values).forEach(key => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (typeof values[key] === 'undefined') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
delete values[key];
|
||||
}
|
||||
});
|
||||
const headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
// Inject additional STS headers if available.
|
||||
Object.assign(headers, additionalHeaders || {});
|
||||
const opts = {
|
||||
url: this.tokenExchangeEndpoint,
|
||||
method: 'POST',
|
||||
headers,
|
||||
data: querystring.stringify(values),
|
||||
responseType: 'json',
|
||||
};
|
||||
// Apply OAuth client authentication.
|
||||
this.applyClientAuthenticationOptions(opts);
|
||||
try {
|
||||
const response = await this.transporter.request(opts);
|
||||
// Successful response.
|
||||
const stsSuccessfulResponse = response.data;
|
||||
stsSuccessfulResponse.res = response;
|
||||
return stsSuccessfulResponse;
|
||||
}
|
||||
catch (error) {
|
||||
// Translate error to OAuthError.
|
||||
if (error.response) {
|
||||
throw oauth2common_1.getErrorFromOAuthErrorResponse(error.response.data,
|
||||
// Preserve other fields from the original error.
|
||||
error);
|
||||
}
|
||||
// Request could fail before the server responds.
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.StsCredentials = StsCredentials;
|
||||
//# sourceMappingURL=stscredentials.js.map
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { Crypto, JwkCertificate } from '../crypto';
|
||||
export declare class BrowserCrypto implements Crypto {
|
||||
constructor();
|
||||
sha256DigestBase64(str: string): Promise<string>;
|
||||
randomBytesBase64(count: number): string;
|
||||
private static padBase64;
|
||||
verify(pubkey: JwkCertificate, data: string, signature: string): Promise<boolean>;
|
||||
sign(privateKey: JwkCertificate, data: string): Promise<string>;
|
||||
decodeBase64StringUtf8(base64: string): string;
|
||||
encodeBase64StringUtf8(text: string): string;
|
||||
/**
|
||||
* Computes the SHA-256 hash of the provided string.
|
||||
* @param str The plain text string to hash.
|
||||
* @return A promise that resolves with the SHA-256 hash of the provided
|
||||
* string in hexadecimal encoding.
|
||||
*/
|
||||
sha256DigestHex(str: string): Promise<string>;
|
||||
/**
|
||||
* Computes the HMAC hash of a message using the provided crypto key and the
|
||||
* SHA-256 algorithm.
|
||||
* @param key The secret crypto key in utf-8 or ArrayBuffer format.
|
||||
* @param msg The plain text message.
|
||||
* @return A promise that resolves with the HMAC-SHA256 hash in ArrayBuffer
|
||||
* format.
|
||||
*/
|
||||
signWithHmacSha256(key: string | ArrayBuffer, msg: string): Promise<ArrayBuffer>;
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
"use strict";
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/* global window */
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BrowserCrypto = void 0;
|
||||
// This file implements crypto functions we need using in-browser
|
||||
// SubtleCrypto interface `window.crypto.subtle`.
|
||||
const base64js = require("base64-js");
|
||||
// Not all browsers support `TextEncoder`. The following `require` will
|
||||
// provide a fast UTF8-only replacement for those browsers that don't support
|
||||
// text encoding natively.
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
if (typeof process === 'undefined' && typeof TextEncoder === 'undefined') {
|
||||
require('fast-text-encoding');
|
||||
}
|
||||
const crypto_1 = require("../crypto");
|
||||
class BrowserCrypto {
|
||||
constructor() {
|
||||
if (typeof window === 'undefined' ||
|
||||
window.crypto === undefined ||
|
||||
window.crypto.subtle === undefined) {
|
||||
throw new Error("SubtleCrypto not found. Make sure it's an https:// website.");
|
||||
}
|
||||
}
|
||||
async sha256DigestBase64(str) {
|
||||
// SubtleCrypto digest() method is async, so we must make
|
||||
// this method async as well.
|
||||
// To calculate SHA256 digest using SubtleCrypto, we first
|
||||
// need to convert an input string to an ArrayBuffer:
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
const inputBuffer = new TextEncoder().encode(str);
|
||||
// Result is ArrayBuffer as well.
|
||||
const outputBuffer = await window.crypto.subtle.digest('SHA-256', inputBuffer);
|
||||
return base64js.fromByteArray(new Uint8Array(outputBuffer));
|
||||
}
|
||||
randomBytesBase64(count) {
|
||||
const array = new Uint8Array(count);
|
||||
window.crypto.getRandomValues(array);
|
||||
return base64js.fromByteArray(array);
|
||||
}
|
||||
static padBase64(base64) {
|
||||
// base64js requires padding, so let's add some '='
|
||||
while (base64.length % 4 !== 0) {
|
||||
base64 += '=';
|
||||
}
|
||||
return base64;
|
||||
}
|
||||
async verify(pubkey, data, signature) {
|
||||
const algo = {
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: { name: 'SHA-256' },
|
||||
};
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
const dataArray = new TextEncoder().encode(data);
|
||||
const signatureArray = base64js.toByteArray(BrowserCrypto.padBase64(signature));
|
||||
const cryptoKey = await window.crypto.subtle.importKey('jwk', pubkey, algo, true, ['verify']);
|
||||
// SubtleCrypto's verify method is async so we must make
|
||||
// this method async as well.
|
||||
const result = await window.crypto.subtle.verify(algo, cryptoKey, signatureArray, dataArray);
|
||||
return result;
|
||||
}
|
||||
async sign(privateKey, data) {
|
||||
const algo = {
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
hash: { name: 'SHA-256' },
|
||||
};
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
const dataArray = new TextEncoder().encode(data);
|
||||
const cryptoKey = await window.crypto.subtle.importKey('jwk', privateKey, algo, true, ['sign']);
|
||||
// SubtleCrypto's sign method is async so we must make
|
||||
// this method async as well.
|
||||
const result = await window.crypto.subtle.sign(algo, cryptoKey, dataArray);
|
||||
return base64js.fromByteArray(new Uint8Array(result));
|
||||
}
|
||||
decodeBase64StringUtf8(base64) {
|
||||
const uint8array = base64js.toByteArray(BrowserCrypto.padBase64(base64));
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
const result = new TextDecoder().decode(uint8array);
|
||||
return result;
|
||||
}
|
||||
encodeBase64StringUtf8(text) {
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
const uint8array = new TextEncoder().encode(text);
|
||||
const result = base64js.fromByteArray(uint8array);
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Computes the SHA-256 hash of the provided string.
|
||||
* @param str The plain text string to hash.
|
||||
* @return A promise that resolves with the SHA-256 hash of the provided
|
||||
* string in hexadecimal encoding.
|
||||
*/
|
||||
async sha256DigestHex(str) {
|
||||
// SubtleCrypto digest() method is async, so we must make
|
||||
// this method async as well.
|
||||
// To calculate SHA256 digest using SubtleCrypto, we first
|
||||
// need to convert an input string to an ArrayBuffer:
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
const inputBuffer = new TextEncoder().encode(str);
|
||||
// Result is ArrayBuffer as well.
|
||||
const outputBuffer = await window.crypto.subtle.digest('SHA-256', inputBuffer);
|
||||
return crypto_1.fromArrayBufferToHex(outputBuffer);
|
||||
}
|
||||
/**
|
||||
* Computes the HMAC hash of a message using the provided crypto key and the
|
||||
* SHA-256 algorithm.
|
||||
* @param key The secret crypto key in utf-8 or ArrayBuffer format.
|
||||
* @param msg The plain text message.
|
||||
* @return A promise that resolves with the HMAC-SHA256 hash in ArrayBuffer
|
||||
* format.
|
||||
*/
|
||||
async signWithHmacSha256(key, msg) {
|
||||
// Convert key, if provided in ArrayBuffer format, to string.
|
||||
const rawKey = typeof key === 'string'
|
||||
? key
|
||||
: String.fromCharCode(...new Uint16Array(key));
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
const enc = new TextEncoder();
|
||||
const cryptoKey = await window.crypto.subtle.importKey('raw', enc.encode(rawKey), {
|
||||
name: 'HMAC',
|
||||
hash: {
|
||||
name: 'SHA-256',
|
||||
},
|
||||
}, false, ['sign']);
|
||||
return window.crypto.subtle.sign('HMAC', cryptoKey, enc.encode(msg));
|
||||
}
|
||||
}
|
||||
exports.BrowserCrypto = BrowserCrypto;
|
||||
//# sourceMappingURL=crypto.js.map
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/// <reference types="node" />
|
||||
export interface JwkCertificate {
|
||||
kty: string;
|
||||
alg: string;
|
||||
use?: string;
|
||||
kid: string;
|
||||
n: string;
|
||||
e: string;
|
||||
}
|
||||
export interface CryptoSigner {
|
||||
update(data: string): void;
|
||||
sign(key: string, outputFormat: string): string;
|
||||
}
|
||||
export interface Crypto {
|
||||
sha256DigestBase64(str: string): Promise<string>;
|
||||
randomBytesBase64(n: number): string;
|
||||
verify(pubkey: string | JwkCertificate, data: string | Buffer, signature: string): Promise<boolean>;
|
||||
sign(privateKey: string | JwkCertificate, data: string | Buffer): Promise<string>;
|
||||
decodeBase64StringUtf8(base64: string): string;
|
||||
encodeBase64StringUtf8(text: string): string;
|
||||
/**
|
||||
* Computes the SHA-256 hash of the provided string.
|
||||
* @param str The plain text string to hash.
|
||||
* @return A promise that resolves with the SHA-256 hash of the provided
|
||||
* string in hexadecimal encoding.
|
||||
*/
|
||||
sha256DigestHex(str: string): Promise<string>;
|
||||
/**
|
||||
* Computes the HMAC hash of a message using the provided crypto key and the
|
||||
* SHA-256 algorithm.
|
||||
* @param key The secret crypto key in utf-8 or ArrayBuffer format.
|
||||
* @param msg The plain text message.
|
||||
* @return A promise that resolves with the HMAC-SHA256 hash in ArrayBuffer
|
||||
* format.
|
||||
*/
|
||||
signWithHmacSha256(key: string | ArrayBuffer, msg: string): Promise<ArrayBuffer>;
|
||||
}
|
||||
export declare function createCrypto(): Crypto;
|
||||
export declare function hasBrowserCrypto(): boolean;
|
||||
/**
|
||||
* Converts an ArrayBuffer to a hexadecimal string.
|
||||
* @param arrayBuffer The ArrayBuffer to convert to hexadecimal string.
|
||||
* @return The hexadecimal encoding of the ArrayBuffer.
|
||||
*/
|
||||
export declare function fromArrayBufferToHex(arrayBuffer: ArrayBuffer): string;
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
/* global window */
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.fromArrayBufferToHex = exports.hasBrowserCrypto = exports.createCrypto = void 0;
|
||||
const crypto_1 = require("./browser/crypto");
|
||||
const crypto_2 = require("./node/crypto");
|
||||
function createCrypto() {
|
||||
if (hasBrowserCrypto()) {
|
||||
return new crypto_1.BrowserCrypto();
|
||||
}
|
||||
return new crypto_2.NodeCrypto();
|
||||
}
|
||||
exports.createCrypto = createCrypto;
|
||||
function hasBrowserCrypto() {
|
||||
return (typeof window !== 'undefined' &&
|
||||
typeof window.crypto !== 'undefined' &&
|
||||
typeof window.crypto.subtle !== 'undefined');
|
||||
}
|
||||
exports.hasBrowserCrypto = hasBrowserCrypto;
|
||||
/**
|
||||
* Converts an ArrayBuffer to a hexadecimal string.
|
||||
* @param arrayBuffer The ArrayBuffer to convert to hexadecimal string.
|
||||
* @return The hexadecimal encoding of the ArrayBuffer.
|
||||
*/
|
||||
function fromArrayBufferToHex(arrayBuffer) {
|
||||
// Convert buffer to byte array.
|
||||
const byteArray = Array.from(new Uint8Array(arrayBuffer));
|
||||
// Convert bytes to hex string.
|
||||
return byteArray
|
||||
.map(byte => {
|
||||
return byte.toString(16).padStart(2, '0');
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
exports.fromArrayBufferToHex = fromArrayBufferToHex;
|
||||
//# sourceMappingURL=crypto.js.map
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/// <reference types="node" />
|
||||
import { Crypto } from '../crypto';
|
||||
export declare class NodeCrypto implements Crypto {
|
||||
sha256DigestBase64(str: string): Promise<string>;
|
||||
randomBytesBase64(count: number): string;
|
||||
verify(pubkey: string, data: string | Buffer, signature: string): Promise<boolean>;
|
||||
sign(privateKey: string, data: string | Buffer): Promise<string>;
|
||||
decodeBase64StringUtf8(base64: string): string;
|
||||
encodeBase64StringUtf8(text: string): string;
|
||||
/**
|
||||
* Computes the SHA-256 hash of the provided string.
|
||||
* @param str The plain text string to hash.
|
||||
* @return A promise that resolves with the SHA-256 hash of the provided
|
||||
* string in hexadecimal encoding.
|
||||
*/
|
||||
sha256DigestHex(str: string): Promise<string>;
|
||||
/**
|
||||
* Computes the HMAC hash of a message using the provided crypto key and the
|
||||
* SHA-256 algorithm.
|
||||
* @param key The secret crypto key in utf-8 or ArrayBuffer format.
|
||||
* @param msg The plain text message.
|
||||
* @return A promise that resolves with the HMAC-SHA256 hash in ArrayBuffer
|
||||
* format.
|
||||
*/
|
||||
signWithHmacSha256(key: string | ArrayBuffer, msg: string): Promise<ArrayBuffer>;
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
"use strict";
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NodeCrypto = void 0;
|
||||
const crypto = require("crypto");
|
||||
class NodeCrypto {
|
||||
async sha256DigestBase64(str) {
|
||||
return crypto.createHash('sha256').update(str).digest('base64');
|
||||
}
|
||||
randomBytesBase64(count) {
|
||||
return crypto.randomBytes(count).toString('base64');
|
||||
}
|
||||
async verify(pubkey, data, signature) {
|
||||
const verifier = crypto.createVerify('sha256');
|
||||
verifier.update(data);
|
||||
verifier.end();
|
||||
return verifier.verify(pubkey, signature, 'base64');
|
||||
}
|
||||
async sign(privateKey, data) {
|
||||
const signer = crypto.createSign('RSA-SHA256');
|
||||
signer.update(data);
|
||||
signer.end();
|
||||
return signer.sign(privateKey, 'base64');
|
||||
}
|
||||
decodeBase64StringUtf8(base64) {
|
||||
return Buffer.from(base64, 'base64').toString('utf-8');
|
||||
}
|
||||
encodeBase64StringUtf8(text) {
|
||||
return Buffer.from(text, 'utf-8').toString('base64');
|
||||
}
|
||||
/**
|
||||
* Computes the SHA-256 hash of the provided string.
|
||||
* @param str The plain text string to hash.
|
||||
* @return A promise that resolves with the SHA-256 hash of the provided
|
||||
* string in hexadecimal encoding.
|
||||
*/
|
||||
async sha256DigestHex(str) {
|
||||
return crypto.createHash('sha256').update(str).digest('hex');
|
||||
}
|
||||
/**
|
||||
* Computes the HMAC hash of a message using the provided crypto key and the
|
||||
* SHA-256 algorithm.
|
||||
* @param key The secret crypto key in utf-8 or ArrayBuffer format.
|
||||
* @param msg The plain text message.
|
||||
* @return A promise that resolves with the HMAC-SHA256 hash in ArrayBuffer
|
||||
* format.
|
||||
*/
|
||||
async signWithHmacSha256(key, msg) {
|
||||
const cryptoKey = typeof key === 'string' ? key : toBuffer(key);
|
||||
return toArrayBuffer(crypto.createHmac('sha256', cryptoKey).update(msg).digest());
|
||||
}
|
||||
}
|
||||
exports.NodeCrypto = NodeCrypto;
|
||||
/**
|
||||
* Converts a Node.js Buffer to an ArrayBuffer.
|
||||
* https://stackoverflow.com/questions/8609289/convert-a-binary-nodejs-buffer-to-javascript-arraybuffer
|
||||
* @param buffer The Buffer input to covert.
|
||||
* @return The ArrayBuffer representation of the input.
|
||||
*/
|
||||
function toArrayBuffer(buffer) {
|
||||
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
||||
}
|
||||
/**
|
||||
* Converts an ArrayBuffer to a Node.js Buffer.
|
||||
* @param arrayBuffer The ArrayBuffer input to covert.
|
||||
* @return The Buffer representation of the input.
|
||||
*/
|
||||
function toBuffer(arrayBuffer) {
|
||||
return Buffer.from(arrayBuffer);
|
||||
}
|
||||
//# sourceMappingURL=crypto.js.map
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { GoogleAuth } from './auth/googleauth';
|
||||
export { Compute, ComputeOptions } from './auth/computeclient';
|
||||
export { CredentialBody, CredentialRequest, Credentials, JWTInput, } from './auth/credentials';
|
||||
export { GCPEnv } from './auth/envDetect';
|
||||
export { GoogleAuthOptions, ProjectIdCallback } from './auth/googleauth';
|
||||
export { IAMAuth, RequestMetadata } from './auth/iam';
|
||||
export { IdTokenClient, IdTokenProvider } from './auth/idtokenclient';
|
||||
export { Claims, JWTAccess } from './auth/jwtaccess';
|
||||
export { JWT, JWTOptions } from './auth/jwtclient';
|
||||
export { Impersonated, ImpersonatedOptions } from './auth/impersonated';
|
||||
export { Certificates, CodeChallengeMethod, CodeVerifierResults, GenerateAuthUrlOpts, GetTokenOptions, OAuth2Client, OAuth2ClientOptions, RefreshOptions, TokenInfo, VerifyIdTokenOptions, } from './auth/oauth2client';
|
||||
export { LoginTicket, TokenPayload } from './auth/loginticket';
|
||||
export { UserRefreshClient, UserRefreshClientOptions, } from './auth/refreshclient';
|
||||
export { AwsClient, AwsClientOptions } from './auth/awsclient';
|
||||
export { IdentityPoolClient, IdentityPoolClientOptions, } from './auth/identitypoolclient';
|
||||
export { ExternalAccountClient, ExternalAccountClientOptions, } from './auth/externalclient';
|
||||
export { BaseExternalAccountClient, BaseExternalAccountClientOptions, } from './auth/baseexternalclient';
|
||||
export { CredentialAccessBoundary, DownscopedClient, } from './auth/downscopedclient';
|
||||
export { DefaultTransporter } from './transporters';
|
||||
declare const auth: GoogleAuth;
|
||||
export { auth, GoogleAuth };
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GoogleAuth = exports.auth = void 0;
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
const googleauth_1 = require("./auth/googleauth");
|
||||
Object.defineProperty(exports, "GoogleAuth", { enumerable: true, get: function () { return googleauth_1.GoogleAuth; } });
|
||||
var computeclient_1 = require("./auth/computeclient");
|
||||
Object.defineProperty(exports, "Compute", { enumerable: true, get: function () { return computeclient_1.Compute; } });
|
||||
var envDetect_1 = require("./auth/envDetect");
|
||||
Object.defineProperty(exports, "GCPEnv", { enumerable: true, get: function () { return envDetect_1.GCPEnv; } });
|
||||
var iam_1 = require("./auth/iam");
|
||||
Object.defineProperty(exports, "IAMAuth", { enumerable: true, get: function () { return iam_1.IAMAuth; } });
|
||||
var idtokenclient_1 = require("./auth/idtokenclient");
|
||||
Object.defineProperty(exports, "IdTokenClient", { enumerable: true, get: function () { return idtokenclient_1.IdTokenClient; } });
|
||||
var jwtaccess_1 = require("./auth/jwtaccess");
|
||||
Object.defineProperty(exports, "JWTAccess", { enumerable: true, get: function () { return jwtaccess_1.JWTAccess; } });
|
||||
var jwtclient_1 = require("./auth/jwtclient");
|
||||
Object.defineProperty(exports, "JWT", { enumerable: true, get: function () { return jwtclient_1.JWT; } });
|
||||
var impersonated_1 = require("./auth/impersonated");
|
||||
Object.defineProperty(exports, "Impersonated", { enumerable: true, get: function () { return impersonated_1.Impersonated; } });
|
||||
var oauth2client_1 = require("./auth/oauth2client");
|
||||
Object.defineProperty(exports, "CodeChallengeMethod", { enumerable: true, get: function () { return oauth2client_1.CodeChallengeMethod; } });
|
||||
Object.defineProperty(exports, "OAuth2Client", { enumerable: true, get: function () { return oauth2client_1.OAuth2Client; } });
|
||||
var loginticket_1 = require("./auth/loginticket");
|
||||
Object.defineProperty(exports, "LoginTicket", { enumerable: true, get: function () { return loginticket_1.LoginTicket; } });
|
||||
var refreshclient_1 = require("./auth/refreshclient");
|
||||
Object.defineProperty(exports, "UserRefreshClient", { enumerable: true, get: function () { return refreshclient_1.UserRefreshClient; } });
|
||||
var awsclient_1 = require("./auth/awsclient");
|
||||
Object.defineProperty(exports, "AwsClient", { enumerable: true, get: function () { return awsclient_1.AwsClient; } });
|
||||
var identitypoolclient_1 = require("./auth/identitypoolclient");
|
||||
Object.defineProperty(exports, "IdentityPoolClient", { enumerable: true, get: function () { return identitypoolclient_1.IdentityPoolClient; } });
|
||||
var externalclient_1 = require("./auth/externalclient");
|
||||
Object.defineProperty(exports, "ExternalAccountClient", { enumerable: true, get: function () { return externalclient_1.ExternalAccountClient; } });
|
||||
var baseexternalclient_1 = require("./auth/baseexternalclient");
|
||||
Object.defineProperty(exports, "BaseExternalAccountClient", { enumerable: true, get: function () { return baseexternalclient_1.BaseExternalAccountClient; } });
|
||||
var downscopedclient_1 = require("./auth/downscopedclient");
|
||||
Object.defineProperty(exports, "DownscopedClient", { enumerable: true, get: function () { return downscopedclient_1.DownscopedClient; } });
|
||||
var transporters_1 = require("./transporters");
|
||||
Object.defineProperty(exports, "DefaultTransporter", { enumerable: true, get: function () { return transporters_1.DefaultTransporter; } });
|
||||
const auth = new googleauth_1.GoogleAuth();
|
||||
exports.auth = auth;
|
||||
//# sourceMappingURL=index.js.map
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
export declare enum WarningTypes {
|
||||
WARNING = "Warning",
|
||||
DEPRECATION = "DeprecationWarning"
|
||||
}
|
||||
export declare function warn(warning: Warning): void;
|
||||
export interface Warning {
|
||||
code: string;
|
||||
type: WarningTypes;
|
||||
message: string;
|
||||
warned?: boolean;
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
"use strict";
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.warn = exports.WarningTypes = void 0;
|
||||
var WarningTypes;
|
||||
(function (WarningTypes) {
|
||||
WarningTypes["WARNING"] = "Warning";
|
||||
WarningTypes["DEPRECATION"] = "DeprecationWarning";
|
||||
})(WarningTypes = exports.WarningTypes || (exports.WarningTypes = {}));
|
||||
function warn(warning) {
|
||||
// Only show a given warning once
|
||||
if (warning.warned) {
|
||||
return;
|
||||
}
|
||||
warning.warned = true;
|
||||
if (typeof process !== 'undefined' && process.emitWarning) {
|
||||
// @types/node doesn't recognize the emitWarning syntax which
|
||||
// accepts a config object, so `as any` it is
|
||||
// https://nodejs.org/docs/latest-v8.x/api/process.html#process_process_emitwarning_warning_options
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
process.emitWarning(warning.message, warning);
|
||||
}
|
||||
else {
|
||||
console.warn(warning.message);
|
||||
}
|
||||
}
|
||||
exports.warn = warn;
|
||||
//# sourceMappingURL=messages.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function validate(options: any): void;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.validate = void 0;
|
||||
// Accepts an options object passed from the user to the API. In the
|
||||
// previous version of the API, it referred to a `Request` options object.
|
||||
// Now it refers to an Axiox Request Config object. This is here to help
|
||||
// ensure users don't pass invalid options when they upgrade from 0.x to 1.x.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function validate(options) {
|
||||
const vpairs = [
|
||||
{ invalid: 'uri', expected: 'url' },
|
||||
{ invalid: 'json', expected: 'data' },
|
||||
{ invalid: 'qs', expected: 'params' },
|
||||
];
|
||||
for (const pair of vpairs) {
|
||||
if (options[pair.invalid]) {
|
||||
const e = `'${pair.invalid}' is not a valid configuration option. Please use '${pair.expected}' instead. This library is using Axios for requests. Please see https://github.com/axios/axios to learn more about the valid request options.`;
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.validate = validate;
|
||||
//# sourceMappingURL=options.js.map
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { GaxiosError, GaxiosOptions, GaxiosPromise, GaxiosResponse } from 'gaxios';
|
||||
export interface Transporter {
|
||||
request<T>(opts: GaxiosOptions): GaxiosPromise<T>;
|
||||
request<T>(opts: GaxiosOptions, callback?: BodyResponseCallback<T>): void;
|
||||
request<T>(opts: GaxiosOptions, callback?: BodyResponseCallback<T>): GaxiosPromise | void;
|
||||
}
|
||||
export interface BodyResponseCallback<T> {
|
||||
(err: Error | null, res?: GaxiosResponse<T> | null): void;
|
||||
}
|
||||
export interface RequestError extends GaxiosError {
|
||||
errors: Error[];
|
||||
}
|
||||
export declare class DefaultTransporter {
|
||||
/**
|
||||
* Default user agent.
|
||||
*/
|
||||
static readonly USER_AGENT: string;
|
||||
/**
|
||||
* Configures request options before making a request.
|
||||
* @param opts GaxiosOptions options.
|
||||
* @return Configured options.
|
||||
*/
|
||||
configure(opts?: GaxiosOptions): GaxiosOptions;
|
||||
/**
|
||||
* Makes a request using Gaxios with given options.
|
||||
* @param opts GaxiosOptions options.
|
||||
* @param callback optional callback that contains GaxiosResponse object.
|
||||
* @return GaxiosPromise, assuming no callback is passed.
|
||||
*/
|
||||
request<T>(opts: GaxiosOptions): GaxiosPromise<T>;
|
||||
request<T>(opts: GaxiosOptions, callback?: BodyResponseCallback<T>): void;
|
||||
/**
|
||||
* Changes the error to include details from the body.
|
||||
*/
|
||||
private processError;
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
"use strict";
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DefaultTransporter = void 0;
|
||||
const gaxios_1 = require("gaxios");
|
||||
const options_1 = require("./options");
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const pkg = require('../../package.json');
|
||||
const PRODUCT_NAME = 'google-api-nodejs-client';
|
||||
class DefaultTransporter {
|
||||
/**
|
||||
* Configures request options before making a request.
|
||||
* @param opts GaxiosOptions options.
|
||||
* @return Configured options.
|
||||
*/
|
||||
configure(opts = {}) {
|
||||
opts.headers = opts.headers || {};
|
||||
if (typeof window === 'undefined') {
|
||||
// set transporter user agent if not in browser
|
||||
const uaValue = opts.headers['User-Agent'];
|
||||
if (!uaValue) {
|
||||
opts.headers['User-Agent'] = DefaultTransporter.USER_AGENT;
|
||||
}
|
||||
else if (!uaValue.includes(`${PRODUCT_NAME}/`)) {
|
||||
opts.headers['User-Agent'] = `${uaValue} ${DefaultTransporter.USER_AGENT}`;
|
||||
}
|
||||
// track google-auth-library-nodejs version:
|
||||
const authVersion = `auth/${pkg.version}`;
|
||||
if (opts.headers['x-goog-api-client'] &&
|
||||
!opts.headers['x-goog-api-client'].includes(authVersion)) {
|
||||
opts.headers['x-goog-api-client'] = `${opts.headers['x-goog-api-client']} ${authVersion}`;
|
||||
}
|
||||
else if (!opts.headers['x-goog-api-client']) {
|
||||
const nodeVersion = process.version.replace(/^v/, '');
|
||||
opts.headers['x-goog-api-client'] = `gl-node/${nodeVersion} ${authVersion}`;
|
||||
}
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
request(opts, callback) {
|
||||
// ensure the user isn't passing in request-style options
|
||||
opts = this.configure(opts);
|
||||
try {
|
||||
options_1.validate(opts);
|
||||
}
|
||||
catch (e) {
|
||||
if (callback) {
|
||||
return callback(e);
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (callback) {
|
||||
gaxios_1.request(opts).then(r => {
|
||||
callback(null, r);
|
||||
}, e => {
|
||||
callback(this.processError(e));
|
||||
});
|
||||
}
|
||||
else {
|
||||
return gaxios_1.request(opts).catch(e => {
|
||||
throw this.processError(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Changes the error to include details from the body.
|
||||
*/
|
||||
processError(e) {
|
||||
const res = e.response;
|
||||
const err = e;
|
||||
const body = res ? res.data : null;
|
||||
if (res && body && body.error && res.status !== 200) {
|
||||
if (typeof body.error === 'string') {
|
||||
err.message = body.error;
|
||||
err.code = res.status.toString();
|
||||
}
|
||||
else if (Array.isArray(body.error.errors)) {
|
||||
err.message = body.error.errors
|
||||
.map((err2) => err2.message)
|
||||
.join('\n');
|
||||
err.code = body.error.code;
|
||||
err.errors = body.error.errors;
|
||||
}
|
||||
else {
|
||||
err.message = body.error.message;
|
||||
err.code = body.error.code || res.status;
|
||||
}
|
||||
}
|
||||
else if (res && res.status >= 400) {
|
||||
// Consider all 4xx and 5xx responses errors.
|
||||
err.message = body;
|
||||
err.code = res.status.toString();
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
exports.DefaultTransporter = DefaultTransporter;
|
||||
/**
|
||||
* Default user agent.
|
||||
*/
|
||||
DefaultTransporter.USER_AGENT = `${PRODUCT_NAME}/${pkg.version}`;
|
||||
//# sourceMappingURL=transporters.js.map
|
||||
Reference in New Issue
Block a user