2022-03-06 02:41:01 -08:00
import {
createSign ,
} from 'crypto' ;
2021-03-10 14:51:05 -08:00
import {
IExecuteFunctions ,
IHookFunctions ,
} from 'n8n-core' ;
import {
IDataObject ,
2022-03-06 02:41:01 -08:00
IHttpRequestOptions ,
2021-03-10 14:51:05 -08:00
ILoadOptionsFunctions ,
INodeExecutionData ,
2021-04-16 09:33:36 -07:00
NodeApiError ,
2021-03-10 14:51:05 -08:00
} from 'n8n-workflow' ;
/ * *
* Make an authenticated API request to Wise .
* /
export async function wiseApiRequest (
this : IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions ,
2022-03-06 02:41:01 -08:00
method : 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'PATCH' ,
2021-03-10 14:51:05 -08:00
endpoint : string ,
body : IDataObject = { } ,
qs : IDataObject = { } ,
option : IDataObject = { } ,
) {
2022-03-06 02:41:01 -08:00
const { apiToken , environment , privateKey } = await this . getCredentials ( 'wiseApi' ) as {
2021-03-10 14:51:05 -08:00
apiToken : string ,
environment : 'live' | 'test' ,
2022-03-06 02:41:01 -08:00
privateKey? : string ,
2021-03-10 14:51:05 -08:00
} ;
const rootUrl = environment === 'live'
? 'https://api.transferwise.com/'
: 'https://api.sandbox.transferwise.tech/' ;
2022-03-06 02:41:01 -08:00
const options : IHttpRequestOptions = {
2021-03-10 14:51:05 -08:00
headers : {
'user-agent' : 'n8n' ,
'Authorization' : ` Bearer ${ apiToken } ` ,
} ,
method ,
2022-03-06 02:41:01 -08:00
url : ` ${ rootUrl } ${ endpoint } ` ,
2021-03-10 14:51:05 -08:00
qs ,
body ,
json : true ,
2022-03-06 02:41:01 -08:00
returnFullResponse : true ,
ignoreHttpStatusErrors : true ,
2021-03-10 14:51:05 -08:00
} ;
if ( ! Object . keys ( body ) . length ) {
delete options . body ;
}
if ( ! Object . keys ( qs ) . length ) {
delete options . qs ;
}
if ( Object . keys ( option ) ) {
Object . assign ( options , option ) ;
}
2022-03-06 02:41:01 -08:00
let response ;
2021-03-10 14:51:05 -08:00
try {
2022-03-06 02:41:01 -08:00
response = await this . helpers . httpRequest ! ( options ) ;
2021-03-10 14:51:05 -08:00
} catch ( error ) {
2022-03-06 02:41:01 -08:00
delete error . config ;
2021-04-16 09:33:36 -07:00
throw new NodeApiError ( this . getNode ( ) , error ) ;
2021-03-10 14:51:05 -08:00
}
2022-03-06 02:41:01 -08:00
if ( response . statusCode === 200 ) {
return response . body ;
}
// Request requires SCA approval
if ( response . statusCode === 403 && response . headers [ 'x-2fa-approval' ] ) {
if ( ! privateKey ) {
throw new NodeApiError ( this . getNode ( ) , {
message : 'This request requires Strong Customer Authentication (SCA). Please add a key pair to your account and n8n credentials. See https://api-docs.transferwise.com/#strong-customer-authentication-personal-token' ,
headers : response.headers ,
body : response.body ,
} ) ;
}
// Sign the x-2fa-approval
const oneTimeToken = response . headers [ 'x-2fa-approval' ] as string ;
const signerObject = createSign ( 'RSA-SHA256' ) . update ( oneTimeToken ) ;
try {
const signature = signerObject . sign (
privateKey ,
'base64' ,
) ;
delete option . ignoreHttpStatusErrors ;
options . headers = {
. . . options . headers ,
'X-Signature' : signature ,
'x-2fa-approval' : oneTimeToken ,
} ;
} catch ( error ) {
throw new NodeApiError ( this . getNode ( ) , { message : 'Error signing SCA request, check your private key' , . . . error } ) ;
}
// Retry the request with signed token
try {
response = await this . helpers . httpRequest ! ( options ) ;
return response . body ;
} catch ( error ) {
throw new NodeApiError ( this . getNode ( ) , { message : 'SCA request failed, check your private key is valid' } ) ;
}
} else {
2022-03-06 02:41:50 -08:00
throw new NodeApiError ( this . getNode ( ) , { . . . response , message : response.statusMessage } ) ;
2022-03-06 02:41:01 -08:00
}
2021-03-10 14:51:05 -08:00
}
/ * *
* Populate the binary property of node items with binary data for a PDF file .
* /
export async function handleBinaryData (
this : IExecuteFunctions ,
items : INodeExecutionData [ ] ,
i : number ,
endpoint : string ,
) {
const data = await wiseApiRequest . call ( this , 'GET' , endpoint , { } , { } , { encoding : null } ) ;
const binaryProperty = this . getNodeParameter ( 'binaryProperty' , i ) as string ;
items [ i ] . binary = items [ i ] . binary ? ? { } ;
items [ i ] . binary ! [ binaryProperty ] = await this . helpers . prepareBinaryData ( data ) ;
items [ i ] . binary ! [ binaryProperty ] . fileName = this . getNodeParameter ( 'fileName' , i ) as string ;
items [ i ] . binary ! [ binaryProperty ] . fileExtension = 'pdf' ;
return items ;
}
export function getTriggerName ( eventName : string ) {
const events : IDataObject = {
'tranferStateChange' : 'transfers#state-change' ,
'transferActiveCases' : 'transfers#active-cases' ,
'balanceCredit' : 'balances#credit' ,
} ;
return events [ eventName ] ;
}
export type BorderlessAccount = {
id : number ,
balances : Array < { currency : string } >
} ;
export type ExchangeRateAdditionalFields = {
interval : 'day' | 'hour' | 'minute' ,
range : {
rangeProperties : { from : string , to : string }
} ,
time : string ,
} ;
export type Profile = {
id : number ,
type : 'business' | 'personal' ,
} ;
export type Recipient = {
2022-03-06 02:41:01 -08:00
active : boolean ,
2021-03-10 14:51:05 -08:00
id : number ,
2022-03-06 02:41:01 -08:00
accountHolderName : string ,
country : string | null ,
currency : string ,
type : string ,
2021-03-10 14:51:05 -08:00
} ;
export type StatementAdditionalFields = {
lineStyle : 'COMPACT' | 'FLAT' ,
range : {
rangeProperties : { intervalStart : string , intervalEnd : string }
} ,
} ;
export type TransferFilters = {
[ key : string ] : string | IDataObject ;
range : {
rangeProperties : { createdDateStart : string , createdDateEnd : string }
} ,
sourceCurrency : string ,
status : string ,
targetCurrency : string ,
} ;
export const livePublicKey = `
-- -- - BEGIN PUBLIC KEY -- -- -
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvO8vXV + JksBzZAY6GhSO
XdoTCfhXaaiZ + qAbtaDBiu2AGkGVpmEygFmWP4Li9m5 + Ni85BhVvZOodM9epgW3F
bA5Q1SexvAF1PPjX4JpMstak / QhAgl1qMSqEevL8cmUeTgcMuVWCJmlge9h7B1CS
D4rtlimGZozG39rUBDg6Qt2K + P4wBfLblL0k4C4YUdLnpGYEDIth + i8XsRpFlogx
CAFyH9 + knYsDbR43UJ9shtc42Ybd40Afihj8KnYKXzchyQ42aC8aZ / h5hyZ28yVy
Oj3Vos0VdBIs / gAyJ / 4 yyQFCXYte64I7ssrlbGRaco4nKF3HmaNhxwyKyJafz19e
HwIDAQAB
-- -- - END PUBLIC KEY -- -- - ` ;
export const testPublicKey = `
-- -- - BEGIN PUBLIC KEY -- -- -
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpb91cEYuyJNQepZAVfP
ZIlPZfNUefH + n6w9SW3fykqKu938cR7WadQv87oF2VuT + fDt7kqeRziTmPSUhqPU
ys / V2Q1rlfJuXbE + Gga37t7zwd0egQ + KyOEHQOpcTwKmtZ81ieGHynAQzsn1We3j
wt760MsCPJ7GMT141ByQM + yW1Bx + 4 SG3IGjXWyqOWrcXsxAvIXkpUD / jK / L958Cg
nZEgz0BSEh0QxYLITnW1lLokSx / dTianWPFEhMC9BgijempgNXHNfcVirg1lPSyg
z7KqoKUN0oHqWLr2U1A + 7 kqrl6O2nx3CKs1bj1hToT1 + p4kcMoHXA7kA + VBLUpEs
VwIDAQAB
-- -- - END PUBLIC KEY -- -- - ` ;