import { GoogleToken } from 'gtoken' ; import * as stream from 'stream' ; import { CredentialBody , Credentials , JWTInput } from './credentials' ; import { IdTokenProvider } from './idtokenclient' ; import { JWTAccess } from './jwtaccess' ; 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 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 ? : JWTAccess ; param email param keyFile param key param scopes param subject param key_id constructor ( options ) ; constructor ( email ? , keyFile ? , key ? , scopes ? , subject ? , keyId ? ) ; 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 } ; } param scopes return createScoped ( scopes ? ) { return new JWT ( { email : this . email , keyFile : this . keyFile , key : this . key , keyId : this . keyId , scopes , subject : this . subject , additionalClaims : this . additionalClaims , } ) ; } param url protected 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 as { target_audience : string ; } ) . target_audience ) { const { tokens } = await this . refreshToken ( ) ; return { headers : this . addSharedMetadataHeaders ( { Authorization : `Bearer ${ tokens . id_token }` , } ) , } ; } else { if ( ! this . access ) { this . access = new 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 ?? undefined , this . additionalClaims , this . useJWTAccessWithScope ? scopes : undefined ) ; return { headers : this . addSharedMetadataHeaders ( headers ) } ; } } else if ( this . hasAnyScopes ( ) || this . apiKey ) { return super . getRequestMetadataAsync ( url ) ; } else { return { headers : { } } ; } } param targetAudience async fetchIdToken ( targetAudience ) { const gtoken = new 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.
     */ private hasUserScopes ( ) { if ( ! this . scopes ) { return false ; } return this . scopes . length > 0 ; } /**
     * Are there any default or user scopes defined.
     */ private hasAnyScopes ( ) { if ( this . scopes && this . scopes . length > 0 ) return true ; if ( this . defaultScopes && this . defaultScopes . length > 0 ) return true ; return false ; } param callback returns authorize ( ) ; authorize ( callback ) ; authorize ( callback ? ) { if ( callback ) { this . authorizeAsync ( ) . then ( r => callback ( null , r ) , callback ) ; } else { return this . authorizeAsync ( ) ; } } private 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 ; } param refreshToken private protected async refreshTokenNoCache ( 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.
     */ private createGToken ( ) { if ( ! this . gtoken ) { this . gtoken = new GoogleToken ( { iss : this . email , sub : this . subject , scope : this . scopes || this . defaultScopes , keyFile : this . keyFile , key : this . key , additionalClaims : this . additionalClaims , } ) ; } return this . gtoken ; } param json 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' ) ; } 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 ; } param inputStream param callback fromStream ( inputStream ) ; fromStream ( inputStream , callback ) ; fromStream ( inputStream , callback ? ) { if ( callback ) { this . fromStreamAsync ( inputStream ) . then ( ( ) => callback ( ) , callback ) ; } else { return this . fromStreamAsync ( inputStream ) ; } } private fromStreamAsync ( inputStream ) { return new Promise < void > ( ( 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 ) ; } } ) ; } ) ; } param apiKey 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.' ) ; } }