Skip to content
This repository was archived by the owner on Oct 29, 2025. It is now read-only.
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/).

## [1.4.2] - 2018-07-30
### Fixed
- Phin callback related issue
- Better handling of activities when customRequestHandler is used
- Better error messages for requests

## [1.4.1] - 2018-07-29
### Fixed
- Various fixes regarding page_requested and pass_reason
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[PerimeterX](http://www.perimeterx.com) Shared base for NodeJS enforcers
=============================================================

> Latest stable version: [v1.4.1](https://www.npmjs.com/package/perimeterx-node-core)
> Latest stable version: [v1.4.2](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.

Expand Down
4 changes: 3 additions & 1 deletion lib/pxapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function callServer(pxCtx, callback) {
}

pxCtx.hasMadeServerCall = true;
return pxHttpc.callServer(data, reqHeaders, config.SERVER_TO_SERVER_API_URI, 'query', false, callback);
return pxHttpc.callServer(data, reqHeaders, config.SERVER_TO_SERVER_API_URI, 'query', callback);
}


Expand All @@ -124,6 +124,7 @@ function evalByServerCall(pxCtx, callback) {
return callback(config.SCORE_EVALUATE_ACTION.S2S_TIMEOUT_PASS);
}
pxLogger.error(`Unexpected exception while evaluating Risk cookie. ${err}`);
pxCtx.passReason = pxConfig.conf.PASS_REASON.REQUEST_FAILED;
return callback(config.SCORE_EVALUATE_ACTION.UNEXPECTED_RESULT);
}
let action = isBadRiskScore(res, pxCtx);
Expand Down Expand Up @@ -163,6 +164,7 @@ function evalByServerCall(pxCtx, callback) {
*/
function isBadRiskScore(res, pxCtx) {
if (!res || !pxUtil.verifyDefined(res.score) || !res.action) {
pxCtx.passReason = pxConfig.conf.PASS_REASON.INVALID_RESPONSE;
return -1;
}
let score = res.score;
Expand Down
2 changes: 1 addition & 1 deletion lib/pxclient.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class PxClient {
}

callServer(data, path, headers, cb) {
pxHttpc.callServer(data, headers, path, 'activities', true);
pxHttpc.callServer(data, headers, path, 'activities');
if (cb) {
cb();
}
Expand Down
4 changes: 3 additions & 1 deletion lib/pxconfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ PX_INTERNAL.PASS_REASON = {
COOKIE: "cookie",
S2S: "s2s",
S2S_TIMEOUT: "s2s_timeout",
MONITOR_MODE: "monitor_mode"
MONITOR_MODE: "monitor_mode",
INVALID_RESPONSE: "invalid_response",
REQUEST_FAILED: "request_failed"
}

PX_INTERNAL.MONITOR_MODE = {
Expand Down
100 changes: 52 additions & 48 deletions lib/pxenforcer.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ class PxEnforcer {


handleVerification(pxCtx, pxConfig, req, res, cb) {
const verified = pxCtx.score < pxConfig.BLOCKING_SCORE;

// Handle async activities
if (verified) {
this.pxPass(pxCtx);
} else {
this.pxBlock(pxCtx, pxConfig);
}

// check for additional activity handler
if (pxConfig.ADDITIONAL_ACTIVITY_HANDLER) {
pxConfig.ADDITIONAL_ACTIVITY_HANDLER(pxCtx, pxConfig);
Expand All @@ -150,63 +159,58 @@ class PxEnforcer {
return cb(null, result);
}
}

}
if (pxCtx.score < pxConfig.BLOCKING_SCORE) {
this.pxPass(pxCtx);

// If verified, pass the request here
if (verified || pxConfig.MODULE_MODE === pxConfig.MONITOR_MODE.MONITOR) {
return cb();
} else {
this.pxBlock(pxCtx, pxConfig);
if (pxConfig.MODULE_MODE === pxConfig.MONITOR_MODE.MONITOR) {
return cb();
}
}

const acceptHeaderValue = req.headers["accept"] || req.headers["content-type"];
const isJsonResponse = acceptHeaderValue && acceptHeaderValue.split(',').find((value) => value.toLowerCase() === "application/json") && pxCtx.cookieOrigin === "cookie" && pxCtx.blockAction !== 'r';
const acceptHeaderValue = req.headers["accept"] || req.headers["content-type"];
const isJsonResponse = acceptHeaderValue && acceptHeaderValue.split(',').find((value) => value.toLowerCase() === "application/json") && pxCtx.cookieOrigin === "cookie" && pxCtx.blockAction !== 'r';

pxLogger.debug(`Enforcing action: ${pxUtil.parseAction(pxCtx.blockAction)} page is served ${isJsonResponse ? "using advanced protection mode" : ""}`);
this.generateResponse(pxCtx, pxConfig, isJsonResponse, function (responseObject) {
const response = {
status: '403',
statusDescription: "Forbidden"
};
pxLogger.debug(`Enforcing action: ${pxUtil.parseAction(pxCtx.blockAction)} page is served ${isJsonResponse ? "using advanced protection mode" : ""}`);
this.generateResponse(pxCtx, pxConfig, isJsonResponse, function (responseObject) {
const response = {
status: '403',
statusDescription: "Forbidden"
};

if (pxCtx.blockAction === 'r') {
response.status = '429';
response.statusDescription = "Too Many Requests";
}
if (pxCtx.blockAction === 'r') {
response.status = '429';
response.statusDescription = "Too Many Requests";
}

if (isJsonResponse) {
response.header = {key: 'Content-Type', value: 'application/json'};
response.body = {
appId: responseObject.appId,
jsClientSrc: responseObject.jsClientSrc,
firstPartyEnabled: responseObject.firstPartyEnabled,
vid: responseObject.vid,
uuid: responseObject.uuid,
hostUrl: responseObject.hostUrl,
blockScript: responseObject.blockScript
}
return cb(null, response);
if (isJsonResponse) {
response.header = {key: 'Content-Type', value: 'application/json'};
response.body = {
appId: responseObject.appId,
jsClientSrc: responseObject.jsClientSrc,
firstPartyEnabled: responseObject.firstPartyEnabled,
vid: responseObject.vid,
uuid: responseObject.uuid,
hostUrl: responseObject.hostUrl,
blockScript: responseObject.blockScript
}
return cb(null, response);
}

response.header = {key: 'Content-Type', value: 'text/html'};
response.body = responseObject;

if (pxCtx.cookieOrigin !== "cookie") {
response.header = {key: 'Content-Type', value: 'application/json'};
response.body = {
action: pxUtil.parseAction(pxCtx.blockAction),
uuid: pxCtx.uuid,
vid: pxCtx.vid,
appId: pxConfig.PX_APP_ID,
page: new Buffer(responseObject).toString('base64'),
collectorUrl: pxCtx.collectorUrl
}
response.header = {key: 'Content-Type', value: 'text/html'};
response.body = responseObject;

if (pxCtx.cookieOrigin !== "cookie") {
response.header = {key: 'Content-Type', value: 'application/json'};
response.body = {
action: pxUtil.parseAction(pxCtx.blockAction),
uuid: pxCtx.uuid,
vid: pxCtx.vid,
appId: pxConfig.PX_APP_ID,
page: new Buffer(responseObject).toString('base64'),
collectorUrl: pxCtx.collectorUrl
}
cb(null, response);
});
}
}
cb(null, response);
});
}

get config() {
Expand Down
9 changes: 4 additions & 5 deletions lib/pxhttpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ module.exports = {
* @param {string} callType - indication for a query or activities sending
* @param {Function} callback - callback function.
*/
function callServer(data, headers, uri, callType, ignoreResponse, callback) {
callback = callback || ((err) => { pxLogger.debug(`callServer callback missing. Error: ${err}`) });
function callServer(data, headers, uri, callType, callback) {
callback = callback || ((err) => { err && pxLogger.debug(`callServer default callback. Error: ${err}`); });
const config = pxConfig.conf;
const callData = {
url: `https://${config.SERVER_HOST}${uri}`,
Expand All @@ -26,16 +26,15 @@ function callServer(data, headers, uri, callType, ignoreResponse, callback) {
};

callData.timeout = callType === 'query' ? config.API_TIMEOUT_MS : config.ACTIVITIES_TIMEOUT;
callData.ignoreResponse = ignoreResponse;

try {
request.post(callData, function (err, response) {
let data;
if (err) {
if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT' || err.code === 'ECONNRESET') {
if (err == "Error: Timeout has been reached.") {
return callback('timeout');
} else {
return callback('perimeterx server did not return a valid response');
return callback(`perimeterx server did not return a valid response. Error: ${err}`);
}
}
if (typeof(response.body) !== "undefined" && response.body !== null) {
Expand Down
6 changes: 4 additions & 2 deletions lib/pxoriginaltoken.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ const TokenV3 = require('./cookie/tokenV3');
const TokenV1 = require('./cookie/tokenV1');
const pxLogger = require('./pxlogger');

function evalCookie(pxCtx, pxConfig) {
function evalCookie(pxCtx, pxConfig, delimiter = ":") {
try {
const cookie = (pxCtx.cookies['_px3'] ? new TokenV3(pxCtx, pxConfig, pxCtx.originalToken) : new TokenV1(pxCtx, pxConfig, pxCtx.originalToken))
let [version, ...extractedCookie] = pxCtx.originalToken.split(delimiter);
let noVersionOriginalToken = extractedCookie.join(delimiter);
const cookie = (pxCtx.cookies['_px3'] ? new TokenV3(pxCtx, pxConfig, noVersionOriginalToken) : new TokenV1(pxCtx, pxConfig, noVersionOriginalToken))
pxLogger.debug('Original token found, Evaluating');

if (!cookie.deserialize()) {
Expand Down
8 changes: 0 additions & 8 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,10 @@ const request = {
function makeRequest(options, cb) {
options.agent = keepAliveAgent;
try {
if (options.ignoreResponse) {
return makeRequestWithNoResponse(options);
}
p(options, cb)
} catch (e) {
pxLogger.error(`Error making request: ${e.message}`);
}
}


function makeRequestWithNoResponse(options) {
p(options);
}

module.exports = request;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "perimeterx-node-core",
"version": "1.4.1",
"version": "1.4.2",
"description": "PerimeterX NodeJS shared core for various applications to monitor and block traffic according to PerimeterX risk score",
"main": "index.js",
"scripts": {
Expand Down
8 changes: 4 additions & 4 deletions test/pxapi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('PX API - pxapi.js', () => {
let pxconfig = require('../lib/pxconfig');
pxconfig.init(params, new PxClient());
config = pxconfig.mergeDefaults(params);
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, ignore, callback) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => {
return callback(data)
});
});
Expand Down Expand Up @@ -72,7 +72,7 @@ describe('PX API - pxapi.js', () => {
cookies: {
_px3: 'aaaa'
},
originalToken: '68a1bf96ab3af2e0683a377d332b125dda3e195ee56cf3ce4d61b99cd0860dc6:xTMRZvJnzxM=:1000:0pjajaPCjssb2HjG2436zyFXIvIEbE87nFBrHEQPDRT7fqiQ5RA05+njsLUVpOtdJjLvWNNAlSG70DW2wqWM5VmF9UR420/wxPkx6Ebyz/L9q7Mxk5fcdF8p+dGcMc3uD7Qh8y3WiPSN389cXhfKfMttUABQYvRpOxo7rMC+ngpHEVYg+lfBZCliHB1PZKLy'
originalToken: '3:68a1bf96ab3af2e0683a377d332b125dda3e195ee56cf3ce4d61b99cd0860dc6:xTMRZvJnzxM=:1000:0pjajaPCjssb2HjG2436zyFXIvIEbE87nFBrHEQPDRT7fqiQ5RA05+njsLUVpOtdJjLvWNNAlSG70DW2wqWM5VmF9UR420/wxPkx6Ebyz/L9q7Mxk5fcdF8p+dGcMc3uD7Qh8y3WiPSN389cXhfKfMttUABQYvRpOxo7rMC+ngpHEVYg+lfBZCliHB1PZKLy'
};

originalTokenValidator.evalCookie(pxCtx, config);
Expand All @@ -98,7 +98,7 @@ describe('PX API - pxapi.js', () => {
cookies: {
_px3: 'aaaa'
},
originalToken: '68a1bf96ab3af2e0683a377d332b125dda3e195ee56cf3ce4d61b99cd0860dc:xTMRZvJnzxM=:1000:0pjajaPCjssb2HjG2436zyFXIvIEbE87nFBrHEQPDRT7fqiQ5RA05+njsLUVpOtdJjLvWNNAlSG70DW2wqWM5VmF9UR420/wxPkx6Ebyz/L9q7Mxk5fcdF8p+dGcMc3uD7Qh8y3WiPSN389cXhfKfMttUABQYvRpOxo7rMC+ngpHEVYg+lfBZCliHB1PZKLy'
originalToken: '3:68a1bf96ab3af2e0683a377d332b125dda3e195ee56cf3ce4d61b99cd0860dc:xTMRZvJnzxM=:1000:0pjajaPCjssb2HjG2436zyFXIvIEbE87nFBrHEQPDRT7fqiQ5RA05+njsLUVpOtdJjLvWNNAlSG70DW2wqWM5VmF9UR420/wxPkx6Ebyz/L9q7Mxk5fcdF8p+dGcMc3uD7Qh8y3WiPSN389cXhfKfMttUABQYvRpOxo7rMC+ngpHEVYg+lfBZCliHB1PZKLy'
};

originalTokenValidator.evalCookie(pxCtx, config);
Expand All @@ -112,7 +112,7 @@ describe('PX API - pxapi.js', () => {
cookies: {
_px: 'aaaa'
},
originalToken: 'Gy9z3mQPYNE=:1000:I7A44BXmO5IlgqhXLM5Mmuq4/jESNgse51Zj/l4bpkAaymDQzcrUMHBofVQ8Q9IYfon3bVQn7gHA124xunjlSlPMlj133wuFBzt7r/yJKpcTEex5WBxynCQAXXx8tymeO1gWXLmPchrV93ysxPl/AeV2/ofVN3YzUR/0PQbXB2fzxkPc5bMPdxLMJCrgLtR4msoMGvg9qaiufMFDWWzah1kvUq1Kvrlk3UQm0y6UU1j6GoLHkTSnDBTg3GexETotOoUkM5FYMPZm8TxK0as+mg=='
originalToken: '3:Gy9z3mQPYNE=:1000:I7A44BXmO5IlgqhXLM5Mmuq4/jESNgse51Zj/l4bpkAaymDQzcrUMHBofVQ8Q9IYfon3bVQn7gHA124xunjlSlPMlj133wuFBzt7r/yJKpcTEex5WBxynCQAXXx8tymeO1gWXLmPchrV93ysxPl/AeV2/ofVN3YzUR/0PQbXB2fzxkPc5bMPdxLMJCrgLtR4msoMGvg9qaiufMFDWWzah1kvUq1Kvrlk3UQm0y6UU1j6GoLHkTSnDBTg3GexETotOoUkM5FYMPZm8TxK0as+mg=='
};

originalTokenValidator.evalCookie(pxCtx, config);
Expand Down
3 changes: 2 additions & 1 deletion test/pxconfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ describe('PX Configurations - pxconfig.js', () => {
blockingScore: 60,
debugMode: true,
ipHeader: 'x-px-true-ip',
maxBufferLength: 1
maxBufferLength: 1,
customRequestHandler: null
};

pxconfig = require('../lib/pxconfig');
Expand Down
20 changes: 10 additions & 10 deletions test/pxenforcer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('PX Enforcer - pxenforcer.js', () => {
});

it('enforces a call in a disabled module', (done) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, ignore, callback) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => {
return callback ? callback(null, data) : '';
});
params.enableModule = false;
Expand All @@ -56,7 +56,7 @@ describe('PX Enforcer - pxenforcer.js', () => {
});

it('enforces a call in an enabled module', (done) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, ignore, callback) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => {
return callback ? callback(null, data) : '';
});
enforcer = new PxEnforcer(params, new PxClient());
Expand All @@ -67,8 +67,8 @@ describe('PX Enforcer - pxenforcer.js', () => {
});
});

it.only('uses first party to get client', (done) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, ignore, callback) => {
it('uses first party to get client', (done) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => {
return callback ? callback(null, data) : '';
});
let reqStub = sinon.stub(request, 'get').callsFake((data, callback) => {
Expand All @@ -86,7 +86,7 @@ describe('PX Enforcer - pxenforcer.js', () => {
});

it('uses first party for xhr post request', (done) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, ignore, callback) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => {
return callback ? callback(null, data) : '';
});
let reqStub = sinon.stub(request, 'post').callsFake((data, callback) => {
Expand All @@ -106,7 +106,7 @@ describe('PX Enforcer - pxenforcer.js', () => {
});

it('uses first party for xhr get request', (done) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, ignore, callback) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => {
return callback ? callback(null, data) : '';
});
let reqStub = sinon.stub(request, 'get').callsFake((data, callback) => {
Expand All @@ -126,7 +126,7 @@ describe('PX Enforcer - pxenforcer.js', () => {
});

it('uses first party with pxvid cookie', (done) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, ignore, callback) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => {
return callback ? callback(null, data) : '';
});
let reqStub = sinon.stub(request, 'post').callsFake((data, callback) => {
Expand All @@ -147,7 +147,7 @@ describe('PX Enforcer - pxenforcer.js', () => {
});

it('uses first party for xhr and passed trough bodyParser', (done) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, ignore, callback) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => {
return callback ? callback(null, data) : '';
});
let reqStub = sinon.stub(request, 'post').callsFake((data, callback) => {
Expand All @@ -167,7 +167,7 @@ describe('PX Enforcer - pxenforcer.js', () => {
});

it('uses upper case for xhr', (done) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, ignore, callback) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => {
return callback ? callback(null, data) : '';
});
let reqStub = sinon.stub(request, 'post').callsFake((data, callback) => {
Expand All @@ -187,7 +187,7 @@ describe('PX Enforcer - pxenforcer.js', () => {
});

it('should not use first party paths if originated from mobile', (done) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, ignore, callback) => {
stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => {
data.score = 100;
data.action = 'b';
return callback ? callback(null, data) : '';
Expand Down
Loading