diff --git a/CHANGELOG.md b/CHANGELOG.md index 317ae9e2..15ac9a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [3.6.0] - 2022-11-17 + +### Added + +- Support for `px_modify_context`, a custom function that allows more flexibility for changes to the request context + ## [3.5.0] - 2022-10-23 ### Added diff --git a/README.md b/README.md index be8faac4..5e9567e6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [PerimeterX](http://www.perimeterx.com) Shared base for NodeJS enforcers ============================================================= -> Latest stable version: [v3.5.0](https://www.npmjs.com/package/perimeterx-node-core) +> Latest stable version: [v3.6.0](https://www.npmjs.com/package/perimeterx-node-core) This is a shared base implementation for PerimeterX Express enforcer and future NodeJS enforcers. For a fully functioning implementation example, see the [Node-Express enforcer](https://github.com/PerimeterX/perimeterx-node-express/) implementation. diff --git a/lib/pxconfig.js b/lib/pxconfig.js index 39392031..45c3da04 100644 --- a/lib/pxconfig.js +++ b/lib/pxconfig.js @@ -89,7 +89,8 @@ class PxConfig { ['LOGIN_SUCCESSFUL_HEADER_VALUE', 'px_login_successful_header_value'], ['LOGIN_SUCCESSFUL_STATUS', 'px_login_successful_status'], ['LOGIN_SUCCESSFUL_BODY_REGEX', 'px_login_successful_body_regex'], - ['LOGIN_SUCCESSFUL_CUSTOM_CALLBACK', 'px_login_successful_custom_callback'] + ['LOGIN_SUCCESSFUL_CUSTOM_CALLBACK', 'px_login_successful_custom_callback'], + ['MODIFY_CONTEXT', 'px_modify_context'], ]; configKeyMapping.forEach(([targetKey, sourceKey]) => { @@ -161,7 +162,8 @@ class PxConfig { userInput === 'px_additional_activity_handler' || userInput === 'px_custom_request_handler' || userInput === 'px_enrich_custom_parameters' || - userInput === 'px_login_successful_custom_callback' + userInput === 'px_login_successful_custom_callback' || + userInput === 'px_modify_context' ) { if (typeof params[userInput] === 'function') { return params[userInput]; @@ -331,7 +333,8 @@ function pxDefaultConfig() { LOGIN_SUCCESSFUL_HEADER_VALUE: '', LOGIN_SUCCESSFUL_STATUS: 200, LOGIN_SUCCESSFUL_BODY_REGEX: '', - LOGIN_SUCCESSFUL_CUSTOM_CALLBACK: null + LOGIN_SUCCESSFUL_CUSTOM_CALLBACK: null, + MODIFY_CONTEXT: null, }; } @@ -391,7 +394,8 @@ const allowedConfigKeys = [ 'px_login_successful_header_value', 'px_login_successful_status', 'px_login_successful_body_regex', - 'px_login_successful_custom_callback' + 'px_login_successful_custom_callback', + 'px_modify_context', ]; module.exports = PxConfig; diff --git a/lib/pxenforcer.js b/lib/pxenforcer.js index fb3b0056..93461c47 100644 --- a/lib/pxenforcer.js +++ b/lib/pxenforcer.js @@ -112,6 +112,7 @@ class PxEnforcer { } const ctx = new PxContext(this._config, req, this._getAdditionalFields(req)); + this._tryModifyContext(ctx, req); req.locals = { ...req.locals, pxCtx: ctx }; this.logger.debug('Request context created successfully'); @@ -127,6 +128,16 @@ class PxEnforcer { } } + _tryModifyContext(ctx, req) { + if (this._config.MODIFY_CONTEXT && typeof this._config.MODIFY_CONTEXT === 'function') { + try { + this._config.MODIFY_CONTEXT(ctx, req); + } catch (e) { + this.logger.debug(`error modifying context: ${e}`); + } + } + } + _getAdditionalFields(req) { const additionalFields = {}; if (this.loginCredentialsExtractor) { diff --git a/package-lock.json b/package-lock.json index ee4921cc..098c5bd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "perimeterx-node-core", - "version": "3.4.3", + "version": "3.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "perimeterx-node-core", - "version": "3.4.3", + "version": "3.6.0", "license": "ISC", "dependencies": { "agent-phin": "^1.0.4", diff --git a/package.json b/package.json index 91ceff72..cb0dabee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "perimeterx-node-core", - "version": "3.5.0", + "version": "3.6.0", "description": "PerimeterX NodeJS shared core for various applications to monitor and block traffic according to PerimeterX risk score", "main": "index.js", "scripts": { diff --git a/test/pxenforcer.test.js b/test/pxenforcer.test.js index 01700f19..bb5e103d 100644 --- a/test/pxenforcer.test.js +++ b/test/pxenforcer.test.js @@ -842,6 +842,42 @@ describe('PX Enforcer - pxenforcer.js', () => { }); }) + it('Should call px_modify_context if set', (done) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { + return callback ? callback(null, data) : ''; + }); + + const modifyCtx = sinon.stub().callsFake((ctx) => ctx.sensitiveRoute = true); + const curParams = { + ...params, + px_modify_context: modifyCtx, + }; + + const pxenforcer = proxyquire('../lib/pxenforcer', { './pxlogger': logger }); + enforcer = new pxenforcer(curParams, pxClient); + enforcer.enforce(req, null, () => { + (modifyCtx.calledOnce).should.equal(true); + (req.locals.pxCtx.sensitiveRoute).should.equal(true); + done(); + }); + }); + + it('should not throw exception if there is an error in px_modify_context', () => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { + return callback ? callback(null, data) : ''; + }); + + const curParams = { + ...params, + px_modify_context: sinon.stub().throws(), + }; + + const pxenforcer = proxyquire('../lib/pxenforcer', { './pxlogger': logger }); + enforcer = new pxenforcer(curParams, pxClient); + const enforceFunc = enforcer.enforce.bind(enforcer, req, null, () => {}); + (enforceFunc).should.not.throw(); + }); + it('Should add Nonce to CSP header (script-src directive exists)', (done) => { const nonce = 'ImN0nc3Value'; const headerWithoutNonce = 'connect-src \'self\' *.bazaarvoice.com *.google.com *.googleapis.com *.perimeterx.net *.px-cdn.net *.px-client.net; script-src \'self\' \'unsafe-eval\' \'unsafe-inline\' *.bazaarvoice.com *.forter.com *.google-analytics.com report-uri https://csp.px-cloud.net/report?report=1&id=8a3a7c5242c0e7646bd7d86284f408f6&app_id=PXFF0j69T5&p=d767ae06-b964-4b42-96a2-6d4089aab525';