import { GaxiosOptions } from 'gaxios' ; import * as querystring from 'querystring' ; import { Crypto , createCrypto } from '../crypto/crypto' ; /** List of HTTP methods that accept request bodies. */ const METHODS_SUPPORTING_REQUEST_BODY = [ 'PUT' , 'POST' , 'PATCH' ] ; /**
 * OAuth error codes.
 * https://tools.ietf.org/html/rfc6749#section-5.2
 */ 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 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 abstract class OAuthClientAuthHandler { private crypto : Crypto ; param clientAuthentication constructor ( private readonly clientAuthentication ? ) { this . crypto = createCrypto ( ) ; } param opts param bearerToken protected applyClientAuthenticationOptions ( opts , bearerToken ? ) { this . injectAuthenticatedHeaders ( opts , bearerToken ) ; if ( ! bearerToken ) { this . injectAuthenticatedRequestBody ( opts ) ; } } param opts param bearerToken private injectAuthenticatedHeaders ( opts , bearerToken ? ) { if ( bearerToken ) { opts . headers = opts . headers || { } ; Object . assign ( opts . headers , { Authorization : `Bearer ${ bearerToken }}` , } ) ; } else if ( this . clientAuthentication ?. 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 }` , } ) ; } } param opts private injectAuthenticatedRequestBody ( opts ) { if ( this . clientAuthentication ?. confidentialClientType === 'request-body' ) { const method = ( opts . method || 'GET' ) . toUpperCase ( ) ; if ( METHODS_SUPPORTING_REQUEST_BODY . indexOf ( method ) !== - 1 ) { 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' ) ; } } } } param resp param err return export function getErrorFromOAuthErrorResponse ( resp , err ? ) { 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 ) ; if ( err ) { const keys = Object . keys ( err ) ; if ( err . stack ) { keys . push ( 'stack' ) ; } keys . forEach ( key => { if ( key !== 'message' ) { Object . defineProperty ( newError , key , { value : ( err ! as { [ index ] : any ; } ) [ key ] , writable : false , enumerable : true , } ) ; } } ) ; } return newError ; }