Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mojaloop/#3750): optimize object serialization and add log level silencing #193

Merged
merged 4 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"devDependencies": {
"@mojaloop/api-snippets": "^17.4.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.17",
"@types/node": "^20.11.19",
"audit-ci": "^6.6.1",
"eslint": "^8.56.0",
"eslint-config-airbnb-base": "15.0.0",
Expand Down
10 changes: 5 additions & 5 deletions src/lib/WSO2Auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class WSO2Auth extends EventEmitter {
.toString('base64');
this._reqOpts.uri = opts.tokenEndpoint;
} else if (opts.staticToken) {
this._logger.log('WSO2 auth config token API data not set, fallback to static token');
this._logger.debug('WSO2 auth config token API data not set, fallback to static token');
this._token = opts.staticToken;
} else {
// throw new Error('WSO2 auth error: neither token API data nor static token is set');
Expand Down Expand Up @@ -101,7 +101,7 @@ class WSO2Auth extends EventEmitter {
// Prevent the timeout from expiring and triggering an extraneous refresh
this.stop();

this._logger.log('WSO2 token refresh initiated');
this._logger.debug('WSO2 token refresh initiated');
const reqOpts = {
...this._reqOpts,
headers: {
Expand All @@ -112,7 +112,7 @@ class WSO2Auth extends EventEmitter {
let refreshSeconds;
try {
const response = await request(reqOpts);
this._logger.push({ reqOpts: { ...reqOpts, agent: '[REDACTED]' }, response }).log('Response received from WSO2');
this._logger.push({ reqOpts: { ...reqOpts, agent: '[REDACTED]' }, response }).debug('Response received from WSO2');
if (response.statusCode > 299) {
this.emit('error', 'Error retrieving WSO2 auth token');
throw new Error(`Unexpected response code ${response.statusCode} received from WSO2 token request`);
Expand All @@ -122,11 +122,11 @@ class WSO2Auth extends EventEmitter {
const tokenIsValidNumber = (typeof expires_in === 'number') && (expires_in > 0);
const tokenExpiry = tokenIsValidNumber ? expires_in : Infinity;
refreshSeconds = Math.min(this._refreshSeconds, tokenExpiry);
this._logger.log('WSO2 token refreshed successfully. ' +
this._logger.debug('WSO2 token refreshed successfully. ' +
`Token expiry is ${expires_in}${tokenIsValidNumber ? 's' : ''}, ` +
`next refresh in ${refreshSeconds}s`);
} catch (error) {
this._logger.log(`Error performing WSO2 token refresh: ${error.message}. `
this._logger.debug(`Error performing WSO2 token refresh: ${error.message}. `
+ `Retry in ${this._refreshRetrySeconds}s`);
refreshSeconds = this._refreshRetrySeconds;
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib/errors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
'use strict';


const util = require('util');
const safeStringify = require('fast-safe-stringify');


/** See section 7.6 of "API Definition v1.0.docx". Note that some of the these
Expand Down Expand Up @@ -207,7 +207,7 @@ class MojaloopFSPIOPError extends Error {
replyTo: this.replyTo,
apiErrorCode: this.apiErrorCode,
extensions: this.extensions,
cause: this.cause ? this.cause.stack || util.inspect(this.cause) : undefined
cause: this.cause ? this.cause.stack || safeStringify(this.cause) : undefined
};
}

Expand All @@ -218,7 +218,7 @@ class MojaloopFSPIOPError extends Error {
* @returns {string}
*/
toString() {
return `${util.inspect(this.toFullErrorObject())}`;
return `${safeStringify(this.toFullErrorObject())}`;
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/lib/ilp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

'use strict';

const util = require('util');
const Crypto = require('crypto');
const base64url = require('base64url');
const safeStringify = require('fast-safe-stringify');

// must be pinned at [email protected] for ILP v1 compatibility
const ilpPacket = require('ilp-packet');
Expand Down Expand Up @@ -56,7 +56,7 @@ class Ilp {
condition: generatedCondition
};

this.logger.log(`Generated ILP: transaction object: ${util.inspect(transactionObject)}\nPacket input: ${util.inspect(packetInput)}\nOutput: ${util.inspect(ret)}`);
this.logger.debug(`Generated ILP: transaction object: ${safeStringify(transactionObject)}\nPacket input: ${safeStringify(packetInput)}\nOutput: ${safeStringify(ret)}`);

return ret;
}
Expand Down Expand Up @@ -205,7 +205,7 @@ class Ilp {
const jsonPacket = ilpPacket.deserializeIlpPayment(binaryPacket);
return jsonPacket;
}

/**
* Get the transaction object in the data field of an Ilp packet
*
Expand All @@ -216,7 +216,7 @@ class Ilp {
const decodedData = base64url.decode(jsonPacket.data.toString());
return JSON.parse(decodedData);
}

/**
* Validate the transfer request against the decoded Ilp packet in it
*
Expand Down
6 changes: 3 additions & 3 deletions src/lib/jws/jwsSigner.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

'use strict';

const util = require('util');
const jws = require('jws');
const safeStringify = require('fast-safe-stringify');

// the JWS signature algorithm to use. Note that Mojaloop spec requires RS256 at present
const SIGNATURE_ALGORITHM = 'RS256';
Expand Down Expand Up @@ -43,7 +43,7 @@ class JwsSigner {
* (see https://github.com/axios/axios)
*/
sign(requestOptions) {
this.logger.log(`JWS Signing request: ${util.inspect(requestOptions)}`);
this.logger.debug(`JWS Signing request: ${safeStringify(requestOptions)}`);
const payload = requestOptions.body || requestOptions.data;
const uri = requestOptions.uri || requestOptions.url;

Expand Down Expand Up @@ -81,7 +81,7 @@ class JwsSigner {
* @returns {string} - JWS Signature as a string
*/
getSignature(requestOptions) {
this.logger.log(`Get JWS Signature: ${util.inspect(requestOptions)}`);
this.logger.debug(`Get JWS Signature: ${safeStringify(requestOptions)}`);
const payload = requestOptions.body || requestOptions.data;
const uri = requestOptions.uri || requestOptions.url;

Expand Down
42 changes: 21 additions & 21 deletions src/lib/jws/jwsValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

'use strict';

const util = require('util');
const base64url = require('base64url');
const jwt = require('jsonwebtoken');
const safeStringify = require('fast-safe-stringify');

// the JWS signature algorithm to use. Note that Mojaloop spec requires RS256 at present
const SIGNATURE_ALGORITHM = 'RS256';
Expand Down Expand Up @@ -42,7 +42,7 @@ class JwsValidator {
const { headers, body, data } = request;
const payload = body || data;

this.logger.log(`Validing JWS on request with headers: ${util.inspect(headers)} and body: ${util.inspect(payload)}`);
this.logger.debug(`Validating JWS on request with headers: ${safeStringify(headers)} and body: ${safeStringify(payload)}`);

if(!payload) {
throw new Error('Cannot validate JWS without a body');
Expand All @@ -56,19 +56,19 @@ class JwsValidator {
const pubKey = this.validationKeys[headers['fspiop-source']];

if(!pubKey) {
throw new Error(`JWS public key for '${headers['fspiop-source']}' not available. Unable to verify JWS. Only have keys for: ${util.inspect(Object.keys(this.validationKeys))}`);
throw new Error(`JWS public key for '${headers['fspiop-source']}' not available. Unable to verify JWS. Only have keys for: ${safeStringify(Object.keys(this.validationKeys))}`);
}

// first we check the required headers are present
// first we check the required headers are present
if(!headers['fspiop-uri'] || !headers['fspiop-http-method'] || !headers['fspiop-signature']) {
throw new Error(`fspiop-uri, fspiop-http-method and fspiop-signature HTTP headers are all required for JWS. Only got ${util.inspect(headers)}`);
throw new Error(`fspiop-uri, fspiop-http-method and fspiop-signature HTTP headers are all required for JWS. Only got ${safeStringify(headers)}`);
}

// if all required headers are present we start by extracting the components of the signature header
// if all required headers are present we start by extracting the components of the signature header
const signatureHeader = JSON.parse(headers['fspiop-signature']);
const { protectedHeader, signature } = signatureHeader;

const token = `${protectedHeader}.${base64url(JSON.stringify(payload))}.${signature}`;
const token = `${protectedHeader}.${base64url(JSON.stringify(payload))}.${signature}`;

// validate signature
const result = jwt.verify(token, pubKey, {
Expand All @@ -80,13 +80,13 @@ class JwsValidator {
this._validateProtectedHeader(headers, result.header);

// const result = jwt.verify(token, pubKey, { complete: true, json: true });
this.logger.log(`JWS verify result: ${util.inspect(result)}`);
this.logger.debug(`JWS verify result: ${safeStringify(result)}`);

// all ok if we got here
this.logger.log(`JWS valid for request ${util.inspect(request)}`);
this.logger.debug(`JWS valid for request ${safeStringify(request)}`);
}
catch(err) {
this.logger.log(`Error validating JWS: ${err.stack || util.inspect(err)}`);
this.logger.debug(`Error validating JWS: ${err.stack || safeStringify(err)}`);
throw err;
}
}
Expand All @@ -99,30 +99,30 @@ class JwsValidator {
_validateProtectedHeader(headers, decodedProtectedHeader) {
// check alg is present and is the single permitted value
if(!decodedProtectedHeader['alg']) {
throw new Error(`Decoded protected header does not contain required alg element: ${util.inspect(decodedProtectedHeader)}`);
throw new Error(`Decoded protected header does not contain required alg element: ${safeStringify(decodedProtectedHeader)}`);
}
if(decodedProtectedHeader.alg !== SIGNATURE_ALGORITHM) {
throw new Error(`Invalid protected header alg '${decodedProtectedHeader.alg}' should be '${SIGNATURE_ALGORITHM}'`);
}

// check FSPIOP-URI is present and matches
if(!decodedProtectedHeader['FSPIOP-URI']) {
throw new Error(`Decoded protected header does not contain required FSPIOP-URI element: ${util.inspect(decodedProtectedHeader)}`);
throw new Error(`Decoded protected header does not contain required FSPIOP-URI element: ${safeStringify(decodedProtectedHeader)}`);
}
if(!headers['fspiop-uri']) {
throw new Error(`FSPIOP-URI HTTP header not present in request headers: ${util.inspect(headers)}`);
throw new Error(`FSPIOP-URI HTTP header not present in request headers: ${safeStringify(headers)}`);
}
if(decodedProtectedHeader['FSPIOP-URI'] !== headers['fspiop-uri']) {
throw new Error(`FSPIOP-URI HTTP request header value: ${headers['fspiop-uri']} does not match protected header value: ${decodedProtectedHeader['FSPIOP-URI']}`);
}


// check FSPIOP-HTTP-Method is present and matches
if(!decodedProtectedHeader['FSPIOP-HTTP-Method']) {
throw new Error(`Decoded protected header does not contain required FSPIOP-HTTP-Method element: ${util.inspect(decodedProtectedHeader)}`);
throw new Error(`Decoded protected header does not contain required FSPIOP-HTTP-Method element: ${safeStringify(decodedProtectedHeader)}`);
}
if(!headers['fspiop-http-method']) {
throw new Error(`FSPIOP-HTTP-Method HTTP header not present in request headers: ${util.inspect(headers)}`);
throw new Error(`FSPIOP-HTTP-Method HTTP header not present in request headers: ${safeStringify(headers)}`);
}
if(decodedProtectedHeader['FSPIOP-HTTP-Method'] !== headers['fspiop-http-method']) {
throw new Error(`FSPIOP-HTTP-Method HTTP request header value: ${headers['fspiop-http-method']} does not match protected header value: ${decodedProtectedHeader['FSPIOP-HTTP-Method']}`);
Expand All @@ -131,10 +131,10 @@ class JwsValidator {

// check FSPIOP-Source is present and matches
if(!decodedProtectedHeader['FSPIOP-Source']) {
throw new Error(`Decoded protected header does not contain required FSPIOP-Source element: ${util.inspect(decodedProtectedHeader)}`);
throw new Error(`Decoded protected header does not contain required FSPIOP-Source element: ${safeStringify(decodedProtectedHeader)}`);
}
if(!headers['fspiop-source']) {
throw new Error(`FSPIOP-Source HTTP header not present in request headers: ${util.inspect(headers)}`);
throw new Error(`FSPIOP-Source HTTP header not present in request headers: ${safeStringify(headers)}`);
}
if(decodedProtectedHeader['FSPIOP-Source'] !== headers['fspiop-source']) {
throw new Error(`FSPIOP-Source HTTP request header value: ${headers['fspiop-source']} does not match protected header value: ${decodedProtectedHeader['FSPIOP-Source']}`);
Expand All @@ -143,18 +143,18 @@ class JwsValidator {

// if we have a Date field in the protected header it must be present in the HTTP header and the values should match exactly
if(decodedProtectedHeader['Date'] && !headers['date']) {
throw new Error(`Date header is present in protected header but not in HTTP request: ${util.inspect(headers)}`);
throw new Error(`Date header is present in protected header but not in HTTP request: ${safeStringify(headers)}`);
}
if(decodedProtectedHeader['Date'] && (headers['date'] !== decodedProtectedHeader['Date'])) {
throw new Error(`HTTP date header: ${headers['date']} does not match protected header Date value: ${decodedProtectedHeader['Date']}`);
}

// if we have an HTTP fspiop-destination header it should also be in the protected header and the values should match exactly
if(headers['fspiop-destination'] && !decodedProtectedHeader['FSPIOP-Destination']) {
throw new Error(`HTTP fspiop-destination header is present but is not present in protected header: ${util.inspect(decodedProtectedHeader)}`);
throw new Error(`HTTP fspiop-destination header is present but is not present in protected header: ${safeStringify(decodedProtectedHeader)}`);
}
if(decodedProtectedHeader['FSPIOP-Destination'] && !headers['fspiop-destination']) {
throw new Error(`FSPIOP-Destination header is present in protected header but not in HTTP request: ${util.inspect(headers)}`);
throw new Error(`FSPIOP-Destination header is present in protected header but not in HTTP request: ${safeStringify(headers)}`);
}
if(headers['fspiop-destination'] && (headers['fspiop-destination'] !== decodedProtectedHeader['FSPIOP-Destination'])) {
throw new Error(`HTTP FSPIOP-Destination header: ${headers['fspiop-destination']} does not match protected header FSPIOP-Destination value: ${decodedProtectedHeader['FSPIOP-Destination']}`);
Expand Down
14 changes: 13 additions & 1 deletion src/lib/logger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
// for the same reason no 'pop' method has been implemented.

const util = require('util');

const safeStringify = require('fast-safe-stringify');

// Utility functions
Expand Down Expand Up @@ -130,6 +129,7 @@ class Logger {
this.opts = { ...this.opts, ...opts };
this.opts.levels.forEach(level => {
this[level] = (...args) => {

this._log(level, ...args);
};
});
Expand Down Expand Up @@ -186,6 +186,18 @@ class Logger {
process.stdout.write(msg + '\n');
}
}

// Define empty methods for all standard log levels
// if the user provides their own custom levels that are not standard levels
// so that the logger does not throw an error.
// An easy way to do level silencing.
verbose(){}
debug(){}
warn(){}
error(){}
trace(){}
info(){}
fatal(){}
}

module.exports = {
Expand Down
Loading