src/http/AuthHttpClient.js
import axios from 'axios';
import Util from '../Util';
import AuthHeader from './AuthHeader';
import AuthContext from './AuthContext';
import config from '../config.json';
import ErrorHandler from './ErrorHandler';
import CustomerTrackingMetadataHeader from './CustomerTrackingMetadataHeader';
import MiscHeaders from './MiscHeaders';
import DeveloperHeader from './DeveloperHeader';
import VersionHeader from './VersionHeader';
import stringify from 'fast-json-stable-stringify';
import setAdditionalHeaders from './setAdditionalHeaders';
/**
* Client for making authenticated requests to the Token gateway.
*/
export class AuthHttpClient {
constructor({
env,
memberId,
cryptoEngine,
developerKey,
globalRpcErrorCallback,
loggingEnabled,
customSdkUrl,
customResponseInterceptor,
}) {
if (!(config.urls[env] || customSdkUrl)) {
throw new Error('Invalid environment string. Please use one of: ' +
JSON.stringify(config.urls));
}
this._instance = axios.create({
baseURL: customSdkUrl || config.urls[env],
});
if (loggingEnabled) {
Util.setUpHttpErrorLogging(this._instance);
}
Util.setUpCustomResponseInterceptor(this._instance, customResponseInterceptor);
this._memberId = memberId;
this._cryptoEngine = cryptoEngine;
this._context = new AuthContext();
this._authHeader = new AuthHeader(customSdkUrl || config.urls[env], this);
this._developerKey = developerKey;
this._resetRequestInterceptor();
const errorHandler = new ErrorHandler(globalRpcErrorCallback);
this._instance.interceptors.response.use(null, error => {
throw errorHandler.handleError(error);
});
}
/**
* Creates the necessary signer objects, based on the level requested.
* If the level is not available, attempts to fetch a lower level.
*
* @param {string} level - requested level of key
* @return {Promise} object used to sign
*/
async getSigner(level) {
if (level === config.KeyLevel.LOW) {
return await this._cryptoEngine.createSigner(config.KeyLevel.LOW);
}
if (level === config.KeyLevel.STANDARD) {
try {
return await this._cryptoEngine.createSigner(config.KeyLevel.STANDARD);
} catch (err) {
return await this._cryptoEngine.createSigner(config.KeyLevel.LOW);
}
}
if (level === config.KeyLevel.PRIVILEGED) {
try {
return await this._cryptoEngine.createSigner(config.KeyLevel.PRIVILEGED);
} catch (err) {
try {
return await this._cryptoEngine.createSigner(config.KeyLevel.STANDARD);
} catch (err2) {
return await this._cryptoEngine.createSigner(config.KeyLevel.LOW);
}
}
}
}
/**
* Use the given key level to sign the request.
*
* @param {string} keyLevel - key level
*/
useKeyLevel(keyLevel) {
this._context.keyLevel = keyLevel;
this._resetRequestInterceptor();
}
/**
* Gets all accounts linked to the member.
*
* @return {Object} response to the API call
*/
async getAccounts() {
const request = {
method: 'get',
url: '/accounts',
};
return this._instance(request);
}
/**
* Gets an account.
*
* @param {string} accountId - account to get
* @return {Object} response to the API call
*/
async getAccount(accountId) {
const request = {
method: 'get',
url: `/accounts/${accountId}`,
};
return this._instance(request);
}
/**
* Gets the balance of an account.
*
* @param {string} accountId - accountId
* @param {string} keyLevel - key level
* @return {Object} response to the API call
*/
async getBalance(accountId, keyLevel) {
this.useKeyLevel(keyLevel);
const request = {
method: 'get',
url: `/accounts/${accountId}/balance`,
};
return this._instance(request);
}
/**
* Gets the balances of an array of accounts.
*
* @param {Array} accountIds - array of accountIds
* @param {string} keyLevel - key level
* @return {Object} response to the API call
*/
async getBalances(accountIds, keyLevel) {
this.useKeyLevel(keyLevel);
const url = '/account-balance?' +
accountIds.map(accountId => 'account_id=' + accountId).join('&');
const request = {
method: 'get',
url: url,
};
return this._instance(request);
}
/**
* Gets a transaction for an account, by its ID.
*
* @param {string} accountId - account that initiated the transaction
* @param {string} transactionId - ID of the transaction
* @param {string} keyLevel - key level
* @return {Object} response to the API call
*/
async getTransaction(accountId, transactionId, keyLevel) {
this.useKeyLevel(keyLevel);
const request = {
method: 'get',
url: `/accounts/${accountId}/transaction/${transactionId}`,
};
return this._instance(request);
}
/**
* Gets all transactions for an account.
*
* @param {string} accountId - ID of the account
* @param {string} offset - where to start
* @param {Number} limit - how many to get
* @param {string} keyLevel - key level
* @return {Object} response to the API call
*/
async getTransactions(accountId, offset, limit, keyLevel, startDate, endDate) {
this.useKeyLevel(keyLevel);
const request = {
method: 'get',
url: `/accounts/${accountId}/transactions?offset=${offset}&limit=${limit}&startDate=${startDate || ''}&endDate=${endDate || ''}`,
};
return this._instance(request);
}
/**
* Confirms if an account has sufficient funds for a purchase.
*
* @param {string} accountId
* @param {string} amount
* @param {string} currency
* @return {boolean} true if account has sufficient funds
*/
async confirmFunds(accountId, amount, currency) {
const req = {
accountId,
amount: {
currency,
value: amount.toString(),
},
};
const request = {
method: 'put',
url: `/accounts/${accountId}/funds-confirmation`,
data: req,
};
return this._instance(request);
}
/**
* Gets info about a bank.
*
* @param {string} bankId - ID of the bank to lookup
* @return {Object} response to the API call
*/
async getBankInfo(bankId) {
const request = {
method: 'get',
url: `/banks/${bankId}/info`,
};
return this._instance(request);
}
/**
* Adds a key to the member.
*
* @param {string} prevHash - hash of the previous directory entry.
* @param {Object} key - key to add
* @return {Object} response to the API call
*/
async approveKey(prevHash, key) {
const update = {
memberId: this._memberId,
operations: [
{
addKey: {
key: {
id: key.id,
publicKey: Util.strKey(key.publicKey),
level: key.level,
algorithm: key.algorithm,
...key.expiresAtMs && {expiresAtMs: key.expiresAtMs},
},
},
},
],
};
return this._memberUpdate(update, prevHash);
}
/**
* Adds keys to the member.
*
* @param {string} prevHash - hash of the previous directory entry.
* @param {Array} keys - keys to add
* @return {Object} response to the API call
*/
async approveKeys(prevHash, keys) {
const update = {
memberId: this._memberId,
operations: keys.map(key => ({
addKey: {
key: {
id: key.id,
publicKey: Util.strKey(key.publicKey),
level: key.level,
algorithm: key.algorithm,
...key.expiresAtMs && {expiresAtMs: key.expiresAtMs},
},
},
})),
};
return this._memberUpdate(update, prevHash);
}
/**
* Removes a key from the member.
*
* @param {string} prevHash - hash of the previous directory entry.
* @param {string} keyId - keyId to remove
* @return {Object} response to the API call
*/
async removeKey(prevHash, keyId) {
const update = {
memberId: this._memberId,
operations: [
{
removeKey: {
keyId,
},
},
],
};
return this._memberUpdate(update, prevHash);
}
/**
* Removes keys from the member.
*
* @param {string} prevHash - hash of the previous directory entry.
* @param {Array} keyIds - keys to remove
* @return {Object} response to the API call
*/
async removeKeys(prevHash, keyIds) {
const update = {
memberId: this._memberId,
operations: keyIds.map(keyId => ({
removeKey: {
keyId,
},
})),
};
return this._memberUpdate(update, prevHash);
}
/**
* Adds an alias to the member.
*
* @param {string} prevHash - hash of the previous directory entry.
* @param {Object} alias - alias to add
* @return {Object} response to the API call
*/
async addAlias(prevHash, alias) {
return this.addAliases(prevHash, [alias]);
}
/**
* Gets logged-in member's aliases, verified or not.
*
* @return {Object} response object; has aliases, unverifiedAliases
*/
async getAliases() {
const request = {
method: 'get',
url: '/aliases',
};
return this._instance(request);
}
/**
* Get default recovery agent.
* @return {Object} GetDefaultAgentResponse proto buffer
*/
async getDefaultRecoveryAgent() {
const request = {
method: 'get',
url: '/recovery/defaults/agent',
};
return this._instance(request);
}
/**
* Set member's recovery rule.
* @param {string} prevHash - hash of the previous directory entry.
* @param {Object} rule - RecoveryRule proto buffer specifying behavior.
* @return {Object} UpdateMemberResponse proto buffer
*/
async addRecoveryRule(prevHash, rule) {
const update = {
memberId: this._memberId,
operations: [{
recoveryRules: rule,
}],
};
return this._memberUpdate(update, prevHash);
}
/**
* Adds aliases to the member.
*
* @param {string} prevHash - hash of the previous directory entry.
* @param {Array} aliases - aliases to add
* @return {Object} response to the API call
*/
async addAliases(prevHash, aliases) {
const update = {
memberId: this._memberId,
operations: aliases.map(alias => ({
addAlias: {
aliasHash: Util.hashAndSerializeAlias(alias),
realm: alias.realm,
realmId: alias.realmId,
},
})),
};
const metadata = aliases.map(alias => ({
addAliasMetadata: {
aliasHash: Util.hashAndSerializeAlias(alias),
alias: alias,
},
}));
return this._memberUpdate(update, prevHash, metadata);
}
/**
* Removes an alias from the member.
*
* @param {string} prevHash - hash of the previous directory entry.
* @param {Object} alias - alias to remove
* @return {Object} response to the API call
*/
async removeAlias(prevHash, alias) {
return this.removeAliases(prevHash, [alias]);
}
/**
* Removes aliases from the member.
*
* @param {string} prevHash - hash of the previous directory entry.
* @param {Array} aliases - aliases to remove
* @return {Object} response to the API call
*/
async removeAliases(prevHash, aliases) {
const update = {
memberId: this._memberId,
operations: aliases.map(alias => ({
removeAlias: {
aliasHash: Util.hashAndSerializeAlias(alias),
realm: alias.realm,
realmId: alias.realmId,
},
})),
};
return this._memberUpdate(update, prevHash);
}
/**
* Deletes the member.
*
* @return {Object} response to the api call
*/
async deleteMember() {
this.useKeyLevel(config.KeyLevel.PRIVILEGED);
const request = {
method: 'delete',
url: '/members',
};
return this._instance(request);
}
/**
* Resolves transfer destinations for the given account ID.
*
* @param {string} accountId - ID of account to resolve destinations for
* @return {Object} api response
*/
async resolveTransferDestinations(accountId) {
const request = {
method: 'get',
url: `/accounts/${accountId}/transfer-destinations`,
};
return this._instance(request);
}
/**
* Creates a test bank account.
*
* @param {Number} balance - balance to put in the account
* @param {string} currency - currency in the account
* @return {Object} response to the API call
*/
async createTestBankAccount(balance, currency) {
const req = {
balance: {
currency,
value: balance,
},
};
const request = {
method: 'post',
url: '/test/create-account',
data: req,
};
return this._instance(request);
}
/**
* Links accounts to the member.
*
* @param {string} authorization - oauthBankAuthorization continaing bank_id and
* access_token
* @return {Object} response to the API call
*/
async linkAccountsOauth(authorization) {
const req = {
authorization,
};
const request = {
method: 'post',
url: '/bank-accounts',
data: req,
};
return this._instance(request);
}
/**
* Gets test bank notification.
*
* @param {string} subscriberId - id of subscriber
* @param {string} notificationId - id of notification
* @return {Object} response to the API call
*/
async getTestBankNotification(subscriberId, notificationId) {
const request = {
method: 'get',
url: `/test/subscribers/${subscriberId}/notifications/${notificationId}`,
};
return this._instance(request);
}
/**
* Gets test bank notifications.
*
* @param {string} subscriberId - id of subscriber
* @return {Object} response to the API call
*/
async getTestBankNotifications(subscriberId) {
const request = {
method: 'get',
url: `/test/subscribers/${subscriberId}/notifications`,
};
return this._instance(request);
}
/**
* Gets information about a particular standing order.
*
* @param {string} accountId
* @param {string} standingOrderId
* @param {string} keyLevel
* @returns
* @memberof AuthHttpClient
*/
async getStandingOrder(accountId, standingOrderId, keyLevel) {
this.useKeyLevel(keyLevel);
const request = {
method: 'get',
url: `/accounts/${accountId}/standing-orders/${standingOrderId}`,
};
return this._instance(request);
}
/**
* Gets information about several standing orders.
*
* @param {string} accountId
* @param {string} offset
* @param {int} limit
* @param {string} keyLevel
* @returns
* @memberof AuthHttpClient
*/
async getStandingOrders(accountId, offset, limit, keyLevel) {
this.useKeyLevel(keyLevel);
const request = {
method: 'get',
url: `/accounts/${accountId}/standing-orders?page.offset=${offset}&page.limit=${limit}`,
};
return this._instance(request);
}
/**
* Signs a token payload with given key level and action.
*
* @param {Object} tokenPayload
* @param {string} suffix
* @param {KeyLevel} keyLevel
* @returns {Object} token proto signature object
*/
async tokenOperationSignature(tokenPayload, suffix, keyLevel) {
const payload = stringify(tokenPayload) + `.${suffix}`;
const signer = await this.getSigner(keyLevel);
return {
memberId: this._memberId,
keyId: signer.getKeyId(),
signature: await signer.sign(payload),
};
}
async _memberUpdate(update, prevHash, metadata) {
if (prevHash !== '') {
update.prevHash = prevHash;
}
if (typeof metadata === 'undefined') {
metadata = [];
}
const signer = await this.getSigner(config.KeyLevel.PRIVILEGED);
const req = {
update,
updateSignature: {
memberId: this._memberId,
keyId: signer.getKeyId(),
signature: await signer.signJson(update),
},
metadata,
};
const request = {
method: 'post',
url: `/members/${this._memberId}/updates`,
data: req,
};
return this._instance(request);
}
_resetRequestInterceptor() {
this._instance.interceptors.request.eject(this._interceptor);
const versionHeader = new VersionHeader();
const developerHeader = new DeveloperHeader(this._developerKey);
const customerTrackingMetadataHeader = new CustomerTrackingMetadataHeader();
const miscHeaders = new MiscHeaders();
this._interceptor = this._instance.interceptors.request.use(async request => {
await this._authHeader.addAuthorizationHeader(this._memberId, request, this._context);
versionHeader.addVersionHeader(request);
developerHeader.addDeveloperHeader(request);
customerTrackingMetadataHeader.addCustomerTrackingMetadata(request, this._context);
miscHeaders.setMiscHeaders(request, this._context);
setAdditionalHeaders(request);
return request;
});
}
}
export default AuthHttpClient;