src/auth/impersonated.ts
Properties |
| includeEmail |
includeEmail:
|
Type : boolean
|
|
Include the service account email in the token.
If set to |
import {GetTokenResponse, OAuth2Client, RefreshOptions} from './oauth2client';
import {AuthClient} from './authclient';
import {IdTokenProvider} from './idtokenclient';
import {GaxiosError} from 'gaxios';
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 const IMPERSONATED_ACCOUNT_TYPE = 'impersonated_service_account';
export interface TokenResponse {
accessToken: string;
expireTime: string;
}
export interface FetchIdTokenOptions {
/**
* Include the service account email in the token.
* If set to `true`, the token will contain `email` and `email_verified` claims.
*/
includeEmail: boolean;
}
export interface FetchIdTokenResponse {
/** The OpenId Connect ID token. */
token: string;
}
export class Impersonated extends OAuth2Client implements IdTokenProvider {
private sourceClient: AuthClient;
private targetPrincipal: string;
private targetScopes: string[];
private delegates: string[];
private lifetime: number;
private endpoint: string;
/**
* 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 = {}) {
super(options);
this.credentials = {
expiry_date: 1,
refresh_token: 'impersonated-placeholder',
};
this.sourceClient = options.sourceClient ?? new OAuth2Client();
this.targetPrincipal = options.targetPrincipal ?? '';
this.delegates = options.delegates ?? [];
this.targetScopes = options.targetScopes ?? [];
this.lifetime = options.lifetime ?? 3600;
this.endpoint = options.endpoint ?? 'https://iamcredentials.googleapis.com';
}
/**
* Refreshes the access token.
* @param refreshToken Unused parameter
*/
protected async refreshToken(
refreshToken?: string | null
): Promise<GetTokenResponse> {
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<TokenResponse>({
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) {
if (!(error instanceof Error)) throw error;
let status = 0;
let message = '';
if (error instanceof GaxiosError) {
status = error?.response?.data?.error?.status;
message = error?.response?.data?.error?.message;
}
if (status && message) {
error.message = `${status}: unable to impersonate: ${message}`;
throw error;
} else {
error.message = `unable to impersonate: ${error}`;
throw error;
}
}
}
/**
* Generates an OpenID Connect ID token for a service account.
*
* {@link https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken Reference Documentation}
*
* @param targetAudience the audience for the fetched ID token.
* @param options the for the request
* @return an OpenID Connect ID token
*/
async fetchIdToken(
targetAudience: string,
options?: FetchIdTokenOptions
): Promise<string> {
await this.sourceClient.getAccessToken();
const name = `projects/-/serviceAccounts/${this.targetPrincipal}`;
const u = `${this.endpoint}/v1/${name}:generateIdToken`;
const body = {
delegates: this.delegates,
audience: targetAudience,
includeEmail: options?.includeEmail ?? true,
};
const res = await this.sourceClient.request<FetchIdTokenResponse>({
url: u,
data: body,
method: 'POST',
});
return res.data.token;
}
}