import * as jws from 'jws' ; import * as LRU from 'lru-cache' ; import * as stream from 'stream' ; import { JWTInput } from './credentials' ; import { Headers } from './oauth2client' ; const DEFAULT_HEADER = { alg : 'RS256' , typ : 'JWT' , } ; export interface Claims { [ index ] : string ; } export class JWTAccess { email ? : string | null ; key ? : string | null ; keyId ? : string | null ; projectId ? : string ; eagerRefreshThresholdMillis : number ; private cache = new LRU < string , { expiration : number ; headers : Headers ; } > ( { max : 500 , maxAge : 60 * 60 * 1000 , } ) ; param email param key param keyId constructor ( email ? , key ? , keyId ? , eagerRefreshThresholdMillis ? ) { this . email = email ; this . key = key ; this . keyId = keyId ; this . eagerRefreshThresholdMillis = eagerRefreshThresholdMillis ?? 5 * 60 * 1000 ; } param url param scopes returns 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 ; } param url param additionalClaims returns getRequestHeaders ( url ? , additionalClaims ? , scopes ? ) { 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 ; if ( Array . isArray ( scopes ) ) { scopes = scopes . join ( ' ' ) ; } 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 ) { 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 ) ; const signedJWT = jws . sign ( { header , payload , secret : this . key } ) ; const headers = { Authorization : `Bearer ${ signedJWT }` } ; this . cache . set ( key , { expiration : exp * 1000 , headers , } ) ; return headers ; } param iat returns private static getExpirationTime ( iat ) { const exp = iat + 3600 ; return exp ; } 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 ; } 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 ( ( 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 ) ; } } ) ; } ) ; } }