2022-08-17 08:50:24 -07:00
import { createSign } from 'crypto' ;
2022-03-06 02:41:01 -08:00
2023-02-27 19:39:43 -08:00
import type {
IDataObject ,
2023-03-09 09:13:15 -08:00
IExecuteFunctions ,
IHookFunctions ,
2023-02-27 19:39:43 -08:00
IHttpRequestOptions ,
ILoadOptionsFunctions ,
JsonObject ,
} from 'n8n-workflow' ;
2023-01-27 03:22:44 -08:00
import { NodeApiError } from 'n8n-workflow' ;
2021-03-10 14:51:05 -08:00
/ * *
* 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-08-17 08:50:24 -07:00
const { apiToken , environment , privateKey } = ( await this . getCredentials ( 'wiseApi' ) ) as {
apiToken : string ;
environment : 'live' | 'test' ;
privateKey? : string ;
2021-03-10 14:51:05 -08:00
} ;
2022-08-17 08:50:24 -07:00
const rootUrl =
environment === 'live'
? 'https://api.transferwise.com/'
: 'https://api.sandbox.transferwise.tech/' ;
2021-03-10 14:51:05 -08:00
2022-03-06 02:41:01 -08:00
const options : IHttpRequestOptions = {
2021-03-10 14:51:05 -08:00
headers : {
'user-agent' : 'n8n' ,
2022-08-17 08:50:24 -07:00
Authorization : ` Bearer ${ apiToken } ` ,
2021-03-10 14:51:05 -08:00
} ,
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 ;
}
2022-06-13 13:29:21 -07:00
if ( option . encoding ) {
delete options . json ;
}
2021-03-10 14:51:05 -08:00
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-12-02 12:54:28 -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 ;
2023-02-27 19:39:43 -08:00
throw new NodeApiError ( this . getNode ( ) , error as JsonObject ) ;
2021-03-10 14:51:05 -08:00
}
2022-03-06 02:41:01 -08:00
2022-03-25 13:38:37 -07:00
if ( response . statusCode >= 200 && response . statusCode < 300 ) {
2022-03-06 02:41:01 -08:00
return response . body ;
}
// Request requires SCA approval
if ( response . statusCode === 403 && response . headers [ 'x-2fa-approval' ] ) {
if ( ! privateKey ) {
throw new NodeApiError ( this . getNode ( ) , {
2022-08-17 08:50:24 -07:00
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' ,
2022-03-06 02:41:01 -08:00
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 {
2022-08-17 08:50:24 -07:00
const signature = signerObject . sign ( privateKey , 'base64' ) ;
2022-03-06 02:41:01 -08:00
delete option . ignoreHttpStatusErrors ;
options . headers = {
. . . options . headers ,
'X-Signature' : signature ,
'x-2fa-approval' : oneTimeToken ,
} ;
} catch ( error ) {
2022-08-17 08:50:24 -07:00
throw new NodeApiError ( this . getNode ( ) , {
message : 'Error signing SCA request, check your private key' ,
2023-02-27 19:39:43 -08:00
. . . ( error as JsonObject ) ,
2022-08-17 08:50:24 -07:00
} ) ;
2022-03-06 02:41:01 -08:00
}
// Retry the request with signed token
try {
2022-12-02 12:54:28 -08:00
response = await this . helpers . httpRequest ( options ) ;
2022-03-06 02:41:01 -08:00
return response . body ;
} catch ( error ) {
2022-08-17 08:50:24 -07:00
throw new NodeApiError ( this . getNode ( ) , {
message : 'SCA request failed, check your private key is valid' ,
} ) ;
2022-03-06 02:41:01 -08:00
}
} else {
2023-02-27 19:39:43 -08:00
throw new NodeApiError ( this . getNode ( ) , {
. . . ( response as JsonObject ) ,
message : response.statusMessage ,
} ) ;
2022-03-06 02:41:01 -08:00
}
2021-03-10 14:51:05 -08:00
}
export function getTriggerName ( eventName : string ) {
const events : IDataObject = {
2022-08-17 08:50:24 -07:00
tranferStateChange : 'transfers#state-change' ,
transferActiveCases : 'transfers#active-cases' ,
balanceCredit : 'balances#credit' ,
2021-03-10 14:51:05 -08:00
} ;
return events [ eventName ] ;
}
export type BorderlessAccount = {
2022-08-17 08:50:24 -07:00
id : number ;
balances : Array < { currency : string } > ;
2021-03-10 14:51:05 -08:00
} ;
export type ExchangeRateAdditionalFields = {
2022-08-17 08:50:24 -07:00
interval : 'day' | 'hour' | 'minute' ;
2021-03-10 14:51:05 -08:00
range : {
2022-08-17 08:50:24 -07:00
rangeProperties : { from : string ; to : string } ;
} ;
time : string ;
2021-03-10 14:51:05 -08:00
} ;
export type Profile = {
2022-08-17 08:50:24 -07:00
id : number ;
type : 'business' | 'personal' ;
2021-03-10 14:51:05 -08:00
} ;
export type Recipient = {
2022-08-17 08:50:24 -07:00
active : boolean ;
id : number ;
accountHolderName : string ;
country : string | null ;
currency : string ;
type : string ;
2021-03-10 14:51:05 -08:00
} ;
export type StatementAdditionalFields = {
2022-08-17 08:50:24 -07:00
lineStyle : 'COMPACT' | 'FLAT' ;
2021-03-10 14:51:05 -08:00
range : {
2022-08-17 08:50:24 -07:00
rangeProperties : { intervalStart : string ; intervalEnd : string } ;
} ;
2021-03-10 14:51:05 -08:00
} ;
export type TransferFilters = {
[ key : string ] : string | IDataObject ;
range : {
2022-08-17 08:50:24 -07:00
rangeProperties : { createdDateStart : string ; createdDateEnd : string } ;
} ;
sourceCurrency : string ;
status : string ;
targetCurrency : string ;
2021-03-10 14:51:05 -08:00
} ;
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 -- -- - ` ;