import { exec } from 'child_process' ; import * as fs from 'fs' ; import { GaxiosOptions , GaxiosResponse } from 'gaxios' ; import * as gcpMetadata from 'gcp-metadata' ; import * as os from 'os' ; import * as path from 'path' ; import * as stream from 'stream' ; import { Crypto , createCrypto } from '../crypto/crypto' ; import { DefaultTransporter , Transporter } from '../transporters' ; import { Compute , ComputeOptions } from './computeclient' ; import { CredentialBody , JWTInput } from './credentials' ; import { IdTokenClient } from './idtokenclient' ; import { GCPEnv , getEnv } from './envDetect' ; import { JWT , JWTOptions } from './jwtclient' ; import { Headers , OAuth2ClientOptions , RefreshOptions } from './oauth2client' ; import { UserRefreshClient , UserRefreshClientOptions } from './refreshclient' ; import { Impersonated , ImpersonatedOptions } from './impersonated' ; import { ExternalAccountClient , ExternalAccountClientOptions , } from './externalclient' ; import { EXTERNAL_ACCOUNT_TYPE , BaseExternalAccountClient , } from './baseexternalclient' ; import { AuthClient } from './authclient' ; /**
 * Defines all types of explicit clients that are determined via ADC JSON
 * config file.
 */ export type JSONClient = JWT | UserRefreshClient | BaseExternalAccountClient | Impersonated ; export interface ProjectIdCallback { ( err ? , projectId ? ) : void ; } export interface CredentialCallback { ( err , result ? ) : void ; } interface DeprecatedGetClientOptions { } export interface ADCCallback { ( err , credential ? , projectId ? ) : 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 const CLOUD_SDK_CLIENT_ID = '764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com' ; export class GoogleAuth { transporter ? : Transporter ; private private checkIsGCE ? : boolean = undefined ; useJWTAccessWithScope ? : boolean ; defaultServicePath ? : string ; get isGCE ( ) { return this . checkIsGCE ; } private _getDefaultProjectIdPromise ? : Promise < string | null > ; private _cachedProjectId ? : string | null ; jsonContent : JWTInput | ExternalAccountClientOptions | null = null ; cachedCredential : JSONClient | Impersonated | Compute | null = 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 ? : string ; private scopes ? : string | string [ ] ; private clientOptions ? : RefreshOptions ; /**
     * Export DefaultTransporter as a static property of the class.
     */ static DefaultTransporter = DefaultTransporter ; constructor ( opts ? ) { 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 ; } setGapicJWTValues ( client ) { client . defaultServicePath = this . defaultServicePath ; client . useJWTAccessWithScope = this . useJWTAccessWithScope ; client . defaultScopes = this . defaultScopes ; } param callback returns getProjectId ( ) ; getProjectId ( callback ) ; getProjectId ( callback ? ) { if ( callback ) { this . getProjectIdAsync ( ) . then ( r => callback ( null , r ) , callback ) ; } else { return this . getProjectIdAsync ( ) ; } } private getProjectIdAsync ( ) { if ( this . _cachedProjectId ) { return Promise . resolve ( this . _cachedProjectId ) ; } if ( ! this . _getDefaultProjectIdPromise ) { this . _getDefaultProjectIdPromise = new Promise ( 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 private getAnyScopes ( ) { return this . scopes || this . defaultScopes ; } param callback returns getApplicationDefault ( ) ; getApplicationDefault ( callback ) ; getApplicationDefault ( options ) ; getApplicationDefault ( options , callback ) ; 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 ) ; } } private async getApplicationDefaultAsync ( options = { } ) { if ( this . cachedCredential ) { return { credential : this . cachedCredential as JSONClient , projectId : await this . getProjectIdAsync ( ) , } ; } let credential ; let projectId ; credential = await this . _tryGetApplicationCredentialsFromEnvironmentVariable ( options ) ; if ( credential ) { if ( credential instanceof JWT ) { credential . scopes = this . scopes ; } else if ( credential instanceof BaseExternalAccountClient ) { credential . scopes = this . getAnyScopes ( ) ; } this . cachedCredential = credential ; projectId = await this . getProjectId ( ) ; return { credential , projectId } ; } credential = await this . _tryGetApplicationCredentialsFromWellKnownFile ( options ) ; if ( credential ) { if ( credential instanceof JWT ) { credential . scopes = this . scopes ; } else if ( credential instanceof BaseExternalAccountClient ) { credential . scopes = this . getAnyScopes ( ) ; } this . cachedCredential = credential ; projectId = await this . getProjectId ( ) ; return { credential , projectId } ; } let isGCE ; try { isGCE = await this . _checkIsGCE ( ) ; } catch ( e ) { e . message = `Unexpected error determining execution environment: ${ e . message }` ; throw e ; } if ( ! isGCE ) { throw new Error ( 'Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.' ) ; } ( options as ComputeOptions ) . scopes = this . getAnyScopes ( ) ; this . cachedCredential = new Compute ( options ) ; projectId = await this . getProjectId ( ) ; return { projectId , credential : this . cachedCredential } ; } returns api async _checkIsGCE ( ) { if ( this . checkIsGCE === undefined ) { this . checkIsGCE = await gcpMetadata . isAvailable ( ) ; } return this . checkIsGCE ; } returns api 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 ; } } return api async _tryGetApplicationCredentialsFromWellKnownFile ( options ? ) { let location = null ; if ( this . _isWindows ( ) ) { location = process . env [ 'APPDATA' ] ; } else { const home = process . env [ 'HOME' ] ; if ( home ) { location = path . join ( home , '.config' ) ; } } if ( location ) { location = path . join ( location , 'gcloud' , 'application_default_credentials.json' ) ; if ( ! fs . existsSync ( location ) ) { location = null ; } } if ( ! location ) { return null ; } const client = await this . _getApplicationCredentialsFromFilePath ( location , options ) ; return client ; } param filePath returns api async _getApplicationCredentialsFromFilePath ( filePath , options = { } ) { if ( ! filePath || filePath . length === 0 ) { throw new Error ( 'The file path is invalid.' ) ; } try { 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 ; } const readStream = fs . createReadStream ( filePath ) ; return this . fromStream ( readStream , options ) ; } param json param options returns 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 UserRefreshClient ( options ) ; client . fromJSON ( json ) ; } else if ( json . type === EXTERNAL_ACCOUNT_TYPE ) { client = ExternalAccountClient . fromJSON ( json as ExternalAccountClientOptions , options ) ! ; client . scopes = this . getAnyScopes ( ) ; } else { ( options as JWTOptions ) . scopes = this . scopes ; client = new JWT ( options ) ; this . setGapicJWTValues ( client ) ; client . fromJSON ( json ) ; } return client ; } param json param options returns private _cacheClientFromJSON ( json , options ? ) { let client ; options = options || { } ; if ( json . type === 'authorized_user' ) { client = new UserRefreshClient ( options ) ; client . fromJSON ( json ) ; } else if ( json . type === EXTERNAL_ACCOUNT_TYPE ) { client = ExternalAccountClient . fromJSON ( json as ExternalAccountClientOptions , options ) ! ; client . scopes = this . getAnyScopes ( ) ; } else { ( options as JWTOptions ) . scopes = this . scopes ; client = new JWT ( options ) ; this . setGapicJWTValues ( client ) ; client . fromJSON ( json ) ; } this . jsonContent = json ; this . cachedCredential = client ; return this . cachedCredential ; } param inputStream param callback fromStream ( inputStream ) ; fromStream ( inputStream , callback ) ; fromStream ( inputStream , options ) ; fromStream ( inputStream , options , callback ) ; 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 ) ; } } private 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 ( ! this . keyFilename ) throw err ; const client = new JWT ( { ... this . clientOptions , keyFile : this . keyFilename , } ) ; this . cachedCredential = client ; this . setGapicJWTValues ( client ) ; return resolve ( client ) ; } } catch ( err ) { return reject ( err ) ; } } ) ; } ) ; } param apiKey param options returns fromAPIKey ( apiKey , options ? ) { options = options || { } ; const client = new JWT ( options ) ; client . fromAPIKey ( apiKey ) ; return client ; } 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
     */ private async getDefaultServiceProjectId ( ) { return new Promise < string | null > ( resolve => { 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 ) { } } resolve ( null ) ; } ) ; } ) ; } api private getProductionProjectId ( ) { return ( process . env [ 'GCLOUD_PROJECT' ] || process . env [ 'GOOGLE_CLOUD_PROJECT' ] || process . env [ 'gcloud_project' ] || process . env [ 'google_cloud_project' ] ) ; } api private async getFileProjectId ( ) { if ( this . cachedCredential ) { return this . cachedCredential . projectId ; } if ( this . keyFilename ) { const creds = await this . getClient ( ) ; if ( creds && creds . projectId ) { return creds . projectId ; } } const r = await this . _tryGetApplicationCredentialsFromEnvironmentVariable ( ) ; if ( r ) { return r . projectId ; } else { return null ; } } /**
     * Gets the project ID from external account client if available.
     */ private async getExternalAccountClientProjectId ( ) { if ( ! this . jsonContent || this . jsonContent . type !== EXTERNAL_ACCOUNT_TYPE ) { return null ; } const creds = await this . getClient ( ) ; return await ( creds as BaseExternalAccountClient ) . getProjectId ( ) ; } /**
     * Gets the Compute Engine project ID if it can be inferred.
     */ private async getGCEProjectId ( ) { try { const r = await gcpMetadata . project ( 'project-id' ) ; return r ; } catch ( e ) { return null ; } } param callback getCredentials ( ) ; getCredentials ( callback ) ; getCredentials ( callback ? ) { if ( callback ) { this . getCredentialsAsync ( ) . then ( r => callback ( null , r ) , callback ) ; } else { return this . getCredentialsAsync ( ) ; } } private async getCredentialsAsync ( ) { await this . getClient ( ) ; if ( this . jsonContent ) { const credential = { client_email : ( this . jsonContent as JWTInput ) . client_email , private_key : ( this . jsonContent as JWTInput ) . private_key , } ; return credential ; } const isGCE = await this . _checkIsGCE ( ) ; if ( ! isGCE ) { throw new Error ( 'Unknown error.' ) ; } 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 ! ; } param targetAudience returns 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 ( { 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 ) ; } param opts 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 ; } param opts async request < T = any > ( opts ) { const client = await this . getClient ( ) ; return client . request < T > ( opts ) ; } /**
     * Determine the compute environment in which the code is running.
     */ getEnv ( ) { return getEnv ( ) ; } param data async sign ( data ) { const client = await this . getClient ( ) ; const crypto = createCrypto ( ) ; if ( client instanceof JWT && client . key ) { const sign = await crypto . sign ( client . key , data ) ; return sign ; } if ( client instanceof BaseExternalAccountClient && client . getServiceAccountEmail ( ) ) { return this . signBlob ( crypto , client . getServiceAccountEmail ( ) as string , 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 ) ; } private async signBlob ( crypto , emailOrUniqueId , data ) { const url = 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/' + `${ emailOrUniqueId }:signBlob` ; const res = await this . request < SignBlobResponse > ( { method : 'POST' , url , data : { payload : crypto . encodeBase64StringUtf8 ( data ) , } , } ) ; return res . data . signedBlob ; } } export interface SignBlobResponse { keyId : string ; signedBlob : string ; }