diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bef7351..71a57fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,17 @@ 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.8.0] - 2018-02-25 +## [2.0.0] - 2019-03-14 +### Added +- Support for multiple instances of PxEnforcer (for multi px-app in same web app) + +### Refactored +- Major parts of the code to inject an instance of PxLogger and PX config. + +### Changed +- Changed PxClient.submitActivities() signature to receive a config object. + +## [1.8.0] - 2019-02-25 ### Added - Support for testing blocking flow in monitor mode diff --git a/README.md b/README.md index 2798d94a..91e18a20 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [PerimeterX](http://www.perimeterx.com) Shared base for NodeJS enforcers ============================================================= -> Latest stable version: [v1.8.0](https://www.npmjs.com/package/perimeterx-node-core) +> Latest stable version: [v2.0.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. @@ -28,9 +28,9 @@ Table of Contents ### Basic Usage Example To integrate this module into an enforcer, users should initialize the enforcer. ```javascript -function initPXModule(params) { +function initPXModule(params, client) { params.moduleVersion = ''; - enforcer = new PxEnforcer(params); + enforcer = new PxEnforcer(params, client); //if dynamic configurations is configured if (enforcer.config.conf.DYNAMIC_CONFIGURATIONS) { setInterval(enforcer.config.confManager.loadData.bind(enforcer.config.confManager), enforcer.config.conf.CONFIGURATION_LOAD_INTERVAL); @@ -64,9 +64,9 @@ Extend the `PxClient` class to send activities to PerimeterX. ```javascript const PxClient = require('perimeterx-node-core').PxClient; class MyClient extends PxClient { - init() { + init(config) { setInterval(() => { - this.submitActivities(); + this.submitActivities(config); }, 1000); } } @@ -77,7 +77,7 @@ Make sure to pass the client instance when initializing the enforcer. ```javascript function initPXModule(params) { params.moduleVersion = ''; - let pxClient = new MyClient(); + const pxClient = new MyClient(); enforcer = new PxEnforcer(params, pxClient); //if dynamic configurations is configured if (enforcer.config.conf.DYNAMIC_CONFIGURATIONS) { diff --git a/index.js b/index.js index cc807570..c04c1402 100644 --- a/index.js +++ b/index.js @@ -26,5 +26,4 @@ module.exports = { PxEnforcer: require('./lib/pxenforcer'), PxClient: require('./lib/pxclient'), - request: require('./lib/request') }; \ No newline at end of file diff --git a/lib/configloader.js b/lib/configloader.js index 008bfc6a..01e8e129 100644 --- a/lib/configloader.js +++ b/lib/configloader.js @@ -1,10 +1,10 @@ - -const logger = require('./pxlogger'); +const request = require('./request'); class ConfigLoader { - constructor(config, pxClient) { - this.config = config; + constructor(pxConfig, pxClient) { + this.pxConfig = pxConfig; + this.config = pxConfig.conf; this.pxClient = pxClient; } @@ -13,18 +13,17 @@ class ConfigLoader { } loadData() { - const request = require('./request'); const checksum = this.config.checksum; const callData = { 'url': `https://${this.config.CONFIGURATIONS_HOST}${this.config.CONFIGURATIONS_URI + (checksum ? `?checksum=${checksum}` : '')}`, 'headers': {Authorization: 'Bearer ' + this.config.AUTH_TOKEN}, 'timeout': this.config.API_TIMEOUT_MS }; - request.get(callData, (error, response) => { + request.get(callData, this.config, (error, response) => { if (error || !response || !(response.statusCode === 200 || response.statusCode === 204)) { - logger.error(`Failed to get configurations: ${error}`); + this.config.logger.error(`Failed to get configurations: ${error}`); if (!checksum) { //no configuration loaded and we can't get configuration - disable module - logger.debug('Failed to pull initial config, switching module to disable until remote configuration found'); + this.config.logger.debug('Failed to pull initial config, switching module to disable until remote configuration found'); this.config.ENABLE_MODULE = false; } return; @@ -33,7 +32,7 @@ class ConfigLoader { // new configuration available if (response.statusCode === 200) { const body = JSON.parse(response.body.toString()); - logger.debug(`Found new configuration - checksum: ${body.checksum}, new configuration: ${JSON.stringify(body)}`); + this.config.logger.debug(`Found new configuration - checksum: ${body.checksum}, new configuration: ${JSON.stringify(body)}`); this.config.checksum = body.checksum; this.config.COOKIE_SECRET_KEY = body.cookieKey; this.config.PX_APP_ID = body.appId; @@ -47,7 +46,7 @@ class ConfigLoader { this.config.MODULE_MODE = body.moduleMode === 'blocking' ? this.config.MONITOR_MODE.BLOCK : this.config.MONITOR_MODE.MONITOR; this.config.FIRST_PARTY_ENABLED = body.firstPartyEnabled; this.config.FIRST_PARTY_XHR_ENABLED = body.firstPartyXhrEnabled; - this.pxClient.sendEnforcerTelemetry('remote_config'); + this.pxClient.sendEnforcerTelemetry('remote_config', this.config); } }); } diff --git a/lib/cookie/cookieV1.js b/lib/cookie/cookieV1.js index fef789f0..a65fbe05 100644 --- a/lib/cookie/cookieV1.js +++ b/lib/cookie/cookieV1.js @@ -5,10 +5,9 @@ const Payload = require('../pxpayload'); class CookieV1 extends Payload { constructor(ctx, config) { - super(); + super(config); this.pxCookie = ctx.cookies['_px']; - this.pxConfig = config; - this.pxContext = ctx; + this.ctx = ctx; this.cookieSecret = config.COOKIE_SECRET_KEY; } @@ -33,10 +32,10 @@ class CookieV1 extends Payload { const baseHmacStr = '' + this.getTime() + this.decodedCookie.s.a + this.getScore() + this.getUuid() + this.getVid(); // hmac string with IP - for backward support - const hmacWithIp = baseHmacStr + this.pxContext.ip + this.pxContext.userAgent; + const hmacWithIp = baseHmacStr + this.ctx.ip + this.ctx.userAgent; // hmac string without IP - const hmacWithoutIp = baseHmacStr + this.pxContext.userAgent; + const hmacWithoutIp = baseHmacStr + this.ctx.userAgent; return this.isHmacValid(hmacWithoutIp, this.getHmac()) || this.isHmacValid(hmacWithIp, this.getHmac()); } diff --git a/lib/cookie/cookieV3.js b/lib/cookie/cookieV3.js index cb705034..bdcfc003 100644 --- a/lib/cookie/cookieV3.js +++ b/lib/cookie/cookieV3.js @@ -4,13 +4,12 @@ const Payload = require('../pxpayload'); class CookieV3 extends Payload { constructor(ctx, config) { - super(); + super(config); let [hash, ...cookie] = ctx.cookies['_px3'].split(':'); cookie = cookie.join(':'); this.pxCookie = cookie; this.cookieHash = hash; - this.pxConfig = config; - this.pxContext = ctx; + this.ctx = ctx; this.cookieSecret = config.COOKIE_SECRET_KEY; } @@ -31,7 +30,7 @@ class CookieV3 extends Payload { } isSecure() { - const hmacStr = this.pxCookie + this.pxContext.userAgent; + const hmacStr = this.pxCookie + this.ctx.userAgent; return this.isHmacValid(hmacStr, this.getHmac()); } } diff --git a/lib/cookie/tokenV1.js b/lib/cookie/tokenV1.js index f3598288..78bf1b12 100644 --- a/lib/cookie/tokenV1.js +++ b/lib/cookie/tokenV1.js @@ -5,10 +5,9 @@ const Payload = require('../pxpayload'); class TokenV1 extends Payload { constructor(ctx, config, token) { - super(); + super(config); this.pxCookie = token; - this.pxConfig = config; - this.pxContext = ctx; + this.ctx = ctx; this.cookieSecret = config.COOKIE_SECRET_KEY; } @@ -33,7 +32,7 @@ class TokenV1 extends Payload { const baseHmacStr = '' + this.getTime() + this.decodedCookie.s.a + this.getScore() + this.getUuid() + this.getVid(); // hmac string with IP - for backward support - const hmacWithIp = baseHmacStr + this.pxContext.ip; + const hmacWithIp = baseHmacStr + this.ctx.ip; // hmac string without IP const hmacWithoutIp = baseHmacStr; diff --git a/lib/cookie/tokenV3.js b/lib/cookie/tokenV3.js index 8e3b71d6..13ba0346 100644 --- a/lib/cookie/tokenV3.js +++ b/lib/cookie/tokenV3.js @@ -4,13 +4,12 @@ const Payload = require('../pxpayload'); class TokenV3 extends Payload { constructor(ctx, config, token) { - super(); + super(config); let [hash, ...cookie] = token.split(':'); cookie = cookie.join(':'); this.pxCookie = cookie; this.cookieHash = hash; - this.pxConfig = config; - this.pxContext = ctx; + this.ctx = ctx; this.cookieSecret = config.COOKIE_SECRET_KEY; } diff --git a/lib/pxapi.js b/lib/pxapi.js index 37c75da1..d20b9307 100644 --- a/lib/pxapi.js +++ b/lib/pxapi.js @@ -1,28 +1,25 @@ 'use strict'; const pxUtil = require('./pxutil'); const pxHttpc = require('./pxhttpc'); -const pxLogger = require('./pxlogger'); -const pxConfig = require('./pxconfig'); exports.evalByServerCall = evalByServerCall; /** * callServer - call the perimeterx api server to receive a score for a given user. * - * @param {Object} pxCtx - current request context + * @param {Object} ctx - current request context * @param {Function} callback - callback function. */ -function callServer(pxCtx, callback) { - const config = pxConfig.conf; - const ip = pxCtx.ip; - const fullUrl = pxCtx.fullUrl; - const vid = pxCtx.vid || ''; - const pxhd = pxCtx.pxhd || ''; - const vidSource = pxCtx.vidSource || ''; - const uuid = pxCtx.uuid || ''; - const uri = pxCtx.uri || '/'; - const headers = pxUtil.formatHeaders(pxCtx.headers); - const httpVersion = pxCtx.httpVersion; +function callServer(ctx, config, callback) { + const ip = ctx.ip; + const fullUrl = ctx.fullUrl; + const vid = ctx.vid || ''; + const pxhd = ctx.pxhd || ''; + const vidSource = ctx.vidSource || ''; + const uuid = ctx.uuid || ''; + const uri = ctx.uri || '/'; + const headers = pxUtil.formatHeaders(ctx.headers, config.SENSITIVE_HEADERS); + const httpVersion = ctx.httpVersion; const riskMode = config.MODULE_MODE === config.MONITOR_MODE.MONITOR ? 'monitor' : 'active_blocking'; const data = { @@ -34,22 +31,22 @@ function callServer(pxCtx, callback) { firstParty: config.FIRST_PARTY_ENABLED }, additional: { - s2s_call_reason: pxCtx.s2sCallReason, + s2s_call_reason: ctx.s2sCallReason, http_version: httpVersion, - http_method: pxCtx.httpMethod, + http_method: ctx.httpMethod, risk_mode: riskMode, module_version: config.MODULE_VERSION, - cookie_origin: pxCtx.cookieOrigin, - request_cookie_names: pxCtx.requestCookieNames + cookie_origin: ctx.cookieOrigin, + request_cookie_names: ctx.requestCookieNames } }; - if (pxCtx.s2sCallReason === 'cookie_decryption_failed') { - data.additional.px_orig_cookie = pxCtx.getCookie(); //No need strigify, already a string + if (ctx.s2sCallReason === 'cookie_decryption_failed') { + data.additional.px_orig_cookie = ctx.getCookie(); //No need strigify, already a string } - if (pxCtx.s2sCallReason === 'cookie_expired' || pxCtx.s2sCallReason === 'cookie_validation_failed') { - data.additional.px_cookie = JSON.stringify(pxCtx.decodedCookie); + if (ctx.s2sCallReason === 'cookie_expired' || ctx.s2sCallReason === 'cookie_validation_failed') { + data.additional.px_cookie = JSON.stringify(ctx.decodedCookie); } pxUtil.prepareCustomParams(config, data.additional); @@ -74,59 +71,57 @@ function callServer(pxCtx, callback) { if (pxhd && data.additional.s2s_call_reason === 'no_cookie') { data.additional.s2s_call_reason = 'no_cookie_w_vid'; } - if (pxCtx.originalUuid) { - data.additional['original_uuid'] = pxCtx.originalUuid; + if (ctx.originalUuid) { + data.additional['original_uuid'] = ctx.originalUuid; } - if (pxCtx.originalTokenError) { - data.additional['original_token_error'] = pxCtx.originalTokenError; + if (ctx.originalTokenError) { + data.additional['original_token_error'] = ctx.originalTokenError; } - if (pxCtx.originalToken) { - data.additional['original_token'] = pxCtx.originalToken; + if (ctx.originalToken) { + data.additional['original_token'] = ctx.originalToken; } - if (pxCtx.decodedOriginalToken) { - data.additional['px_decoded_original_token'] = pxCtx.decodedOriginalToken; + if (ctx.decodedOriginalToken) { + data.additional['px_decoded_original_token'] = ctx.decodedOriginalToken; } - if(pxCtx.hmac) { - data.additional['px_cookie_hmac'] = pxCtx.hmac; + if(ctx.hmac) { + data.additional['px_cookie_hmac'] = ctx.hmac; } - pxCtx.hasMadeServerCall = true; - return pxHttpc.callServer(data, reqHeaders, config.SERVER_TO_SERVER_API_URI, 'query', callback); + ctx.hasMadeServerCall = true; + return pxHttpc.callServer(data, reqHeaders, config.SERVER_TO_SERVER_API_URI, 'query', config, callback); } /** * evalByServerCall - main server to server function, execute a server call for score and process its value to make blocking decisions. * ' - * @param {Object} pxCtx - current request context. + * @param {Object} ctx - current request context. * @param {Function} callback - callback function. */ -function evalByServerCall(pxCtx, callback) { - - const config = pxConfig.conf; - if (!pxCtx.ip || !pxCtx.headers) { - pxLogger.error('perimeterx score evaluation failed. bad parameters.'); +function evalByServerCall(ctx, config, callback) { + if (!ctx.ip || !ctx.headers) { + config.logger.error('perimeterx score evaluation failed. bad parameters.'); return callback(config.SCORE_EVALUATE_ACTION.UNEXPECTED_RESULT); } - pxLogger.debug(`Evaluating Risk API request, call reason: ${pxCtx.s2sCallReason}`); - callServer(pxCtx, (err, res) => { + config.logger.debug(`Evaluating Risk API request, call reason: ${ctx.s2sCallReason}`); + callServer(ctx, config, (err, res) => { if (err) { if (err === 'timeout') { - pxCtx.passReason = config.PASS_REASON.S2S_TIMEOUT; + ctx.passReason = config.PASS_REASON.S2S_TIMEOUT; 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; + config.logger.error(`Unexpected exception while evaluating Risk cookie. ${err}`); + ctx.passReason = config.PASS_REASON.REQUEST_FAILED; return callback(config.SCORE_EVALUATE_ACTION.UNEXPECTED_RESULT); } - pxCtx.pxhd = res.pxhd; - const action = isBadRiskScore(res, pxCtx); + ctx.pxhd = res.pxhd; + const action = isBadRiskScore(res, ctx, config); /* score response invalid - pass traffic */ if (action === -1) { - pxLogger.error('perimeterx server query response is invalid'); + config.logger.error('perimeterx server query response is invalid'); return callback(config.SCORE_EVALUATE_ACTION.UNEXPECTED_RESULT); } @@ -137,7 +132,7 @@ function evalByServerCall(pxCtx, callback) { /* score crossed threshold - block traffic */ if (action === 0) { - pxCtx.uuid = res.uuid || ''; + ctx.uuid = res.uuid || ''; return callback(config.SCORE_EVALUATE_ACTION.BAD_SCORE); } @@ -150,7 +145,7 @@ function evalByServerCall(pxCtx, callback) { * isBadRiskScore - processing response score and return a block indicator. * * @param {object} res - perimeterx response object. - * @param {object} pxCtx - current request context. + * @param {object} ctx - current request context. * * @return {Number} indicator to the validity of the cookie. * -1 response object is not valid @@ -158,23 +153,22 @@ function evalByServerCall(pxCtx, callback) { * 1 response valid with good score * */ -function isBadRiskScore(res, pxCtx) { +function isBadRiskScore(res, ctx, config) { if (!res || !pxUtil.verifyDefined(res.score) || !res.action) { - pxCtx.passReason = pxConfig.conf.PASS_REASON.INVALID_RESPONSE; + ctx.passReason = config.PASS_REASON.INVALID_RESPONSE; return -1; } const score = res.score; - pxCtx.score = score; - pxCtx.uuid = res.uuid; - if (score >= pxConfig.conf.BLOCKING_SCORE) { - pxCtx.blockAction = res.action; + ctx.score = score; + ctx.uuid = res.uuid; + if (score >= config.BLOCKING_SCORE) { + ctx.blockAction = res.action; if (res.action === 'j' && res.action_data && res.action_data.body) { - pxCtx.blockActionData = res.action_data.body; + ctx.blockActionData = res.action_data.body; } return 0; } else { - pxCtx.passReason = pxConfig.conf.PASS_REASON.S2S; + ctx.passReason = config.PASS_REASON.S2S; return 1; } } - diff --git a/lib/pxclient.js b/lib/pxclient.js index fff950ec..a8bbc884 100644 --- a/lib/pxclient.js +++ b/lib/pxclient.js @@ -1,10 +1,7 @@ 'use strict'; -const pxLogger = require('./pxlogger'); -const pxConfig = require('./pxconfig'); const pxUtil = require('./pxutil'); const pxHttpc = require('./pxhttpc'); const os = require('os'); - class PxClient { constructor() { this.activitiesBuffer = []; @@ -19,32 +16,32 @@ class PxClient { * * @param {string} activityType - name of the activity * @param {object} details - activities details in key-val format - * @param {object} pxCtx - request context + * @param {object} ctx - request context + * @param {object} config - perimeterx config */ - sendToPerimeterX(activityType, details, pxCtx) { - const config = pxConfig.conf; + sendToPerimeterX(activityType, details, ctx, config) { if (activityType === 'page_requested' && !config.SEND_PAGE_ACTIVITIES) { return; } - details['cookie_origin'] = pxCtx.cookieOrigin; + details['cookie_origin'] = ctx.cookieOrigin; details['module_version'] = config.MODULE_VERSION; - if (pxCtx.blockAction && activityType === 'block') { - details['block_action'] = pxCtx.blockAction; + if (ctx.blockAction && activityType === 'block') { + details['block_action'] = ctx.blockAction; } const pxData = {}; pxData.type = activityType; - pxData.headers = pxCtx.headers; + pxData.headers = ctx.headers; pxData.timestamp = Date.now(); - pxData.socket_ip = pxCtx.ip; + pxData.socket_ip = ctx.ip; pxData.px_app_id = config.PX_APP_ID; - pxData.url = pxCtx.fullUrl; - if (pxCtx.vid) { - pxData.vid = pxCtx.vid; + pxData.url = ctx.fullUrl; + if (ctx.vid) { + pxData.vid = ctx.vid; } - if (pxCtx.pxhd && (activityType === 'page_requested') || activityType === 'block') { - pxData.pxhd = pxCtx.pxhd; + if (ctx.pxhd && (activityType === 'page_requested') || activityType === 'block') { + pxData.pxhd = ctx.pxhd; pxUtil.prepareCustomParams(config, details); } pxData.details = details; @@ -52,8 +49,7 @@ class PxClient { this.activitiesBuffer.push(pxData); } - sendEnforcerTelemetry(updateReason) { - const config = pxConfig.conf; + sendEnforcerTelemetry(updateReason, config) { const headers = { 'Authorization': 'Bearer ' + config.AUTH_TOKEN, 'Content-Type': 'application/json' @@ -73,31 +69,31 @@ class PxClient { pxData.px_app_id = config.PX_APP_ID; pxData.details = details; - pxLogger.debug('Sending telemetry activity to perimeterx servers'); + config.logger.debug('Sending telemetry activity to perimeterx servers'); - this.callServer(pxData, pxConfig.conf.TELEMETRY_URI, headers); + this.callServer(pxData, config.TELEMETRY_URI, headers, config); } /** * submitActivities - flash activities buffer and send to px servers for processing * */ - submitActivities(cb) { + submitActivities(config, cb) { if (this.activitiesBuffer.length > 0) { const headers = { 'Content-Type': 'application/json' }; - pxLogger.debug('Sending activities to perimeterx servers'); + config.logger.debug('Sending activities to perimeterx servers'); const tempActivities = this.activitiesBuffer.concat(); //duplicate this.activitiesBuffer.splice(0, tempActivities.length); - this.callServer(tempActivities, pxConfig.conf.SERVER_COLLECT_URI, headers, cb); + this.callServer(tempActivities, config.SERVER_COLLECT_URI, headers, config, cb); } } - callServer(data, path, headers, cb) { - pxHttpc.callServer(data, headers, path, 'activities'); + callServer(data, path, headers, config, cb) { + pxHttpc.callServer(data, headers, path, 'activities', config); if (cb) { cb(); } diff --git a/lib/pxconfig.js b/lib/pxconfig.js index e01bbb4c..b26fb4c8 100644 --- a/lib/pxconfig.js +++ b/lib/pxconfig.js @@ -1,165 +1,44 @@ 'use strict'; -const ConfigLoader = require('./configloader'); const TestModeRequestHandler = require('./pxtesthandler'); const HttpsProxyAgent = require('https-proxy-agent'); -let config = {}; -let configLoader = null; -const PX_DEFAULT = {}; -const PX_INTERNAL = {}; - -PX_INTERNAL.MODULE_VERSION = 'NodeJS Module'; -/* internal configurations */ -PX_INTERNAL.SERVER_HOST = 'sapi.perimeterx.net'; -PX_INTERNAL.COLLECTOR_HOST = 'collector.perimeterx.net'; -PX_INTERNAL.CAPTCHA_HOST = 'captcha.px-cdn.net'; -PX_INTERNAL.CLIENT_HOST = 'client.perimeterx.net'; -PX_INTERNAL.CONFIGURATIONS_HOST = 'px-conf.perimeterx.net'; -PX_INTERNAL.SERVER_TO_SERVER_API_URI = '/api/v3/risk'; -PX_INTERNAL.SERVER_CAPTCHA_URI = '/api/v2/risk/captcha'; -PX_INTERNAL.SERVER_COLLECT_URI = '/api/v1/collector/s2s'; -PX_INTERNAL.CONFIGURATIONS_URI = '/api/v1/enforcer'; -PX_INTERNAL.TELEMETRY_URI = '/api/v2/risk/telemetry'; -PX_INTERNAL.ENFORCER_TRUE_IP_HEADER = 'x-px-enforcer-true-ip'; -PX_INTERNAL.FIRST_PARTY_HEADER = 'x-px-first-party'; -PX_INTERNAL.FORWARDED_FOR_HEADER = 'x-forwarded-for'; -PX_INTERNAL.FIRST_PARTY_VENDOR_PATH = '/init.js'; -PX_INTERNAL.FIRST_PARTY_XHR_PATH = '/xhr'; -PX_INTERNAL.FIRST_PARTY_CAPTCHA_PATH = '/captcha'; -PX_INTERNAL.EMPTY_GIF_B64 = 'R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='; - -// Cookie encryption configurations -PX_INTERNAL.COOKIE_ENCRYPTION = true; -PX_INTERNAL.CE_KEYLEN = 32; -PX_INTERNAL.CE_IVLEN = 16; -PX_INTERNAL.CE_ITERATIONS = 1000; -PX_INTERNAL.CE_DIGEST = 'sha256'; -PX_INTERNAL.CE_ALGO = 'aes-256-cbc'; - -PX_INTERNAL.STATIC_FILES_EXT = ['.css', '.bmp', '.tif', '.ttf', '.docx', '.woff2', '.js', '.pict', '.tiff', '.eot', '.xlsx', '.jpg', '.csv', '.eps', '.woff', '.xls', '.jpeg', '.doc', '.ejs', '.otf', '.pptx', '.gif', '.pdf', '.swf', '.svg', '.ps', '.ico', '.pls', '.midi', '.svgz', '.class', '.png', '.ppt', '.mid', 'webp', '.jar']; - -/* actions */ -PX_INTERNAL.SCORE_EVALUATE_ACTION = { - SPECIAL_TOKEN: -6, - SENSITIVE_ROUTE: -5, - UNEXPECTED_RESULT: -4, - NO_COOKIE: -3, - COOKIE_INVALID: -2, - COOKIE_EXPIRED: -1, - - S2S_PASS_TRAFFIC: 11, - COOKIE_PASS_TRAFFIC: 10, - S2S_TIMEOUT_PASS: 9, - COOKIE_BLOCK_TRAFFIC: -10, - S2S_BLOCK_TRAFFIC: -11, - CAPTCHA_BLOCK_TRAFFIC: -12, - CHALLENGE_BLOCK_TRAFFIC: -13, - - CAPTCHA_PASS: 0, - CAPTCHA_BLOCK: 1, - - GOOD_SCORE: 1, - BAD_SCORE: 0 -}; - -PX_INTERNAL.PASS_REASON = { - CAPTCHA_TIMEOUT: 'captcha_timeout', - CAPTCHA: 'captcha', - COOKIE: 'cookie', - S2S: 's2s', - S2S_TIMEOUT: 's2s_timeout', - MONITOR_MODE: 'monitor_mode', - INVALID_RESPONSE: 'invalid_response', - REQUEST_FAILED: 'request_failed' -}; - -PX_INTERNAL.MONITOR_MODE = { - MONITOR: 0, - BLOCK: 1 -}; +class PxConfig { + constructor(params, logger) { + this.PX_INTERNAL = pxInternalConfig(); + this.PX_DEFAULT = pxDefaultConfig(this.PX_INTERNAL); + this.config = this.mergeDefaults(params); + this.logger = logger; + this.config.logger = this.logger; -/* to be defined by the initiating user or dynamically via configuration service */ -PX_DEFAULT.PX_APP_ID = 'PX_APP_ID'; -PX_DEFAULT.ENABLE_MODULE = true; -PX_DEFAULT.API_TIMEOUT_MS = 1000; -PX_DEFAULT.BLOCKING_SCORE = 100; -PX_DEFAULT.COOKIE_SECRET_KEY = 'cookie_secret_key'; -PX_DEFAULT.AUTH_TOKEN = 'auth_token'; -PX_DEFAULT.IP_HEADERS = []; -PX_DEFAULT.BLOCK_HTML = 'BLOCK'; -PX_DEFAULT.SENSITIVE_HEADERS = ['cookie', 'cookies']; -PX_DEFAULT.PROXY_URL = ''; -PX_DEFAULT.SEND_PAGE_ACTIVITIES = true; -PX_DEFAULT.DEBUG_MODE = false; -PX_DEFAULT.CUSTOM_REQUEST_HANDLER = ''; -PX_DEFAULT.MAX_BUFFER_LEN = 30; -PX_DEFAULT.GET_USER_IP = ''; -PX_DEFAULT.CSS_REF = []; -PX_DEFAULT.JS_REF = []; -PX_DEFAULT.CUSTOM_LOGO = ''; -PX_DEFAULT.LOGO_VISIBILITY = 'hidden'; -PX_DEFAULT.SENSITIVE_ROUTES = []; -PX_DEFAULT.WHITELIST_ROUTES = []; -PX_DEFAULT.DYNAMIC_CONFIGURATIONS = false; -PX_DEFAULT.CONFIGURATION_LOAD_INTERVAL = 5000; -PX_DEFAULT.MODULE_MODE = PX_INTERNAL.MONITOR_MODE.MONITOR; -PX_DEFAULT.ADDITIONAL_ACTIVITY_HANDLER = ''; -PX_DEFAULT.ENRICH_CUSTOM_PARAMETERS = ''; -PX_DEFAULT.FIRST_PARTY_ENABLED = true; -PX_DEFAULT.FIRST_PARTY_XHR_ENABLED = true; -PX_DEFAULT.TESTING_MODE = false; -PX_DEFAULT.WHITELIST_EXT = []; -PX_DEFAULT.BYPASS_MONITOR_HEADER = ''; + this.config.WHITELIST_EXT = [...this.PX_INTERNAL.STATIC_FILES_EXT, ...this.PX_DEFAULT.WHITELIST_EXT]; -class PxConfig { - static init(params, pxClient) { - config = PxConfig.mergeDefaults(params); - config.WHITELIST_EXT = [...PX_INTERNAL.STATIC_FILES_EXT, ...PX_DEFAULT.WHITELIST_EXT]; - if (PX_DEFAULT.TESTING_MODE) { - PX_DEFAULT.CUSTOM_REQUEST_HANDLER = TestModeRequestHandler.testModeRequestHandler; - } - if (config.PROXY_URL) { - config.agent = new HttpsProxyAgent(config.PROXY_URL); + if (this.PX_DEFAULT.TESTING_MODE) { + this.PX_DEFAULT.CUSTOM_REQUEST_HANDLER = TestModeRequestHandler.testModeRequestHandler; } - pxClient.sendEnforcerTelemetry('initial_config'); - if (PX_DEFAULT.DYNAMIC_CONFIGURATIONS) { - configLoader = new ConfigLoader(config, pxClient); - configLoader.init(); + + if (this.config.PROXY_URL) { + this.config.agent = new HttpsProxyAgent(this._config.PROXY_URL); } + + this.configLoader = null; } - static mergeDefaults(params) { - PX_DEFAULT.ENABLE_MODULE = this.configurationsOverriding(PX_DEFAULT, params, 'ENABLE_MODULE', 'enableModule'); - PX_DEFAULT.PX_APP_ID = this.configurationsOverriding(PX_DEFAULT, params, 'PX_APP_ID', 'pxAppId'); - //Set SEVER_HOST if app_id is set. - PX_INTERNAL.SERVER_HOST = PX_DEFAULT.PX_APP_ID !== 'PX_APP_ID' ? PX_INTERNAL.SERVER_HOST = 'sapi-' + PX_DEFAULT.PX_APP_ID.toLowerCase() + '.perimeterx.net' : 'sapi.perimeterx.net'; - PX_INTERNAL.COLLECTOR_HOST = PX_DEFAULT.PX_APP_ID !== 'PX_APP_ID' ? PX_INTERNAL.COLLECTOR_HOST = 'collector-' + PX_DEFAULT.PX_APP_ID.toLowerCase() + '.perimeterx.net' : 'collector.perimeterx.net'; - PX_DEFAULT.COOKIE_SECRET_KEY = this.configurationsOverriding(PX_DEFAULT, params, 'COOKIE_SECRET_KEY', 'cookieSecretKey'); - PX_DEFAULT.AUTH_TOKEN = this.configurationsOverriding(PX_DEFAULT, params, 'AUTH_TOKEN', 'authToken'); - PX_DEFAULT.PROXY_URL = this.configurationsOverriding(PX_DEFAULT, params, 'PROXY_URL', 'proxy'); - PX_DEFAULT.API_TIMEOUT_MS = this.configurationsOverriding(PX_DEFAULT, params, 'API_TIMEOUT_MS', 'apiTimeoutMS'); - PX_DEFAULT.CUSTOM_REQUEST_HANDLER = this.configurationsOverriding(PX_DEFAULT, params, 'CUSTOM_REQUEST_HANDLER', 'customRequestHandler'); - PX_DEFAULT.GET_USER_IP = this.configurationsOverriding(PX_DEFAULT, params, 'GET_USER_IP', 'getUserIp'); - PX_DEFAULT.BLOCKING_SCORE = this.configurationsOverriding(PX_DEFAULT, params, 'BLOCKING_SCORE', 'blockingScore'); - PX_DEFAULT.IP_HEADERS = this.configurationsOverriding(PX_DEFAULT, params, 'IP_HEADERS', 'ipHeaders'); - PX_DEFAULT.SEND_PAGE_ACTIVITIES = this.configurationsOverriding(PX_DEFAULT, params, 'SEND_PAGE_ACTIVITIES', 'sendPageActivities'); - PX_DEFAULT.SENSITIVE_HEADERS = this.configurationsOverriding(PX_DEFAULT, params, 'SENSITIVE_HEADERS', 'sensitiveHeaders'); - PX_DEFAULT.DEBUG_MODE = this.configurationsOverriding(PX_DEFAULT, params, 'DEBUG_MODE', 'debugMode'); - PX_DEFAULT.MAX_BUFFER_LEN = this.configurationsOverriding(PX_DEFAULT, params, 'MAX_BUFFER_LEN', 'maxBufferLength'); - PX_DEFAULT.JS_REF = this.configurationsOverriding(PX_DEFAULT, params, 'JS_REF', 'jsRef'); - PX_DEFAULT.CSS_REF = this.configurationsOverriding(PX_DEFAULT, params, 'CSS_REF', 'cssRef'); - PX_DEFAULT.CUSTOM_LOGO = this.configurationsOverriding(PX_DEFAULT, params, 'CUSTOM_LOGO', 'customLogo'); - PX_DEFAULT.SENSITIVE_ROUTES = this.configurationsOverriding(PX_DEFAULT, params, 'SENSITIVE_ROUTES', 'sensitiveRoutes'); - PX_DEFAULT.WHITELIST_ROUTES = this.configurationsOverriding(PX_DEFAULT, params, 'WHITELIST_ROUTES', 'whitelistRoutes'); - PX_DEFAULT.DYNAMIC_CONFIGURATIONS = this.configurationsOverriding(PX_DEFAULT, params, 'DYNAMIC_CONFIGURATIONS', 'dynamicConfigurations'); - PX_DEFAULT.MODULE_MODE = this.configurationsOverriding(PX_DEFAULT, params, 'MODULE_MODE', 'moduleMode'); - PX_DEFAULT.FIRST_PARTY_ENABLED = this.configurationsOverriding(PX_DEFAULT, params, 'FIRST_PARTY_ENABLED', 'firstPartyEnabled'); - PX_INTERNAL.MODULE_VERSION = this.configurationsOverriding(PX_INTERNAL, params, 'MODULE_VERSION', 'moduleVersion'); - PX_DEFAULT.ADDITIONAL_ACTIVITY_HANDLER = this.configurationsOverriding(PX_DEFAULT, params, 'ADDITIONAL_ACTIVITY_HANDLER', 'additionalActivityHandler'); - PX_DEFAULT.ENRICH_CUSTOM_PARAMETERS = this.configurationsOverriding(PX_DEFAULT, params, 'ENRICH_CUSTOM_PARAMETERS', 'enrichCustomParameters'); - PX_DEFAULT.TESTING_MODE = this.configurationsOverriding(PX_DEFAULT, params, 'TESTING_MODE', 'testingMode'); - PX_DEFAULT.WHITELIST_EXT = this.configurationsOverriding(PX_DEFAULT, params, 'WHITELIST_EXT', 'whitelistExt'); - PX_DEFAULT.BYPASS_MONITOR_HEADER = this.configurationsOverriding(PX_DEFAULT, params, 'BYPASS_MONITOR_HEADER', 'bypassMonitorHeader'); - return Object.assign(PX_DEFAULT, PX_INTERNAL); + mergeDefaults(params) { + const configKeyMapping = [['ENABLE_MODULE', 'enableModule'], ['PX_APP_ID', 'pxAppId'], ['COOKIE_SECRET_KEY', 'cookieSecretKey'], ['AUTH_TOKEN', 'authToken'], ['PROXY_URL', 'proxy'], + ['API_TIMEOUT_MS', 'apiTimeoutMS'], ['CUSTOM_REQUEST_HANDLER', 'customRequestHandler'], ['GET_USER_IP', 'getUserIp'], ['BLOCKING_SCORE', 'blockingScore'], ['IP_HEADERS', 'ipHeaders'], + ['SEND_PAGE_ACTIVITIES', 'sendPageActivities'], ['SENSITIVE_HEADERS', 'sensitiveHeaders'], ['DEBUG_MODE', 'debugMode'], ['MAX_BUFFER_LEN', 'maxBufferLength'], ['JS_REF', 'jsRef'], + ['CSS_REF', 'cssRef'], ['CUSTOM_LOGO', 'customLogo'], ['SENSITIVE_ROUTES', 'sensitiveRoutes'], ['WHITELIST_ROUTES', 'whitelistRoutes'], ['DYNAMIC_CONFIGURATIONS', 'dynamicConfigurations'], + ['MODULE_MODE', 'moduleMode'], ['FIRST_PARTY_ENABLED', 'firstPartyEnabled'], ['ADDITIONAL_ACTIVITY_HANDLER', 'additionalActivityHandler'], ['ENRICH_CUSTOM_PARAMETERS', 'enrichCustomParameters'], + ['TESTING_MODE', 'testingMode'], ['WHITELIST_EXT', 'whitelistExt'], ['BYPASS_MONITOR_HEADER', 'bypassMonitorHeader']]; + + configKeyMapping.forEach(([targetKey, sourceKey]) => { + this.PX_DEFAULT[targetKey] = PxConfig.configurationsOverriding(this.PX_DEFAULT, params, targetKey, sourceKey); + }); + + this.PX_INTERNAL.SERVER_HOST = this.PX_DEFAULT.PX_APP_ID !== 'PX_APP_ID' ? this.PX_INTERNAL.SERVER_HOST = 'sapi-' + this.PX_DEFAULT.PX_APP_ID.toLowerCase() + '.perimeterx.net' : 'sapi.perimeterx.net'; + this.PX_INTERNAL.COLLECTOR_HOST = this.PX_DEFAULT.PX_APP_ID !== 'PX_APP_ID' ? this.PX_INTERNAL.COLLECTOR_HOST = 'collector-' + this.PX_DEFAULT.PX_APP_ID.toLowerCase() + '.perimeterx.net' : 'collector.perimeterx.net'; + this.PX_INTERNAL.MODULE_VERSION = PxConfig.configurationsOverriding(this.PX_INTERNAL, params, 'MODULE_VERSION', 'moduleVersion'); + + return Object.assign(this.PX_DEFAULT, this.PX_INTERNAL); } static configurationsOverriding(conf, params, defaultName, userInput) { @@ -184,14 +63,122 @@ class PxConfig { return params[userInput]; } - static get conf() { - return config; + get conf() { + return this.config; } - static get confManager() { - return configLoader; + get confManager() { + return this.configLoader; } } -module.exports = PxConfig; +function pxInternalConfig() { + return { + MODULE_VERSION: 'NodeJS Module', + SERVER_HOST: 'sapi.perimeterx.net', + COLLECTOR_HOST: 'collector.perimeterx.net', + CAPTCHA_HOST: 'captcha.px-cdn.net', + CLIENT_HOST: 'client.perimeterx.net', + CONFIGURATIONS_HOST: 'px-conf.perimeterx.net', + SERVER_TO_SERVER_API_URI: '/api/v3/risk', + SERVER_CAPTCHA_URI: '/api/v2/risk/captcha', + SERVER_COLLECT_URI: '/api/v1/collector/s2s', + CONFIGURATIONS_URI: '/api/v1/enforcer', + TELEMETRY_URI: '/api/v2/risk/telemetry', + ENFORCER_TRUE_IP_HEADER: 'x-px-enforcer-true-ip', + FIRST_PARTY_HEADER: 'x-px-first-party', + FORWARDED_FOR_HEADER: 'x-forwarded-for', + FIRST_PARTY_VENDOR_PATH: '/init.js', + FIRST_PARTY_XHR_PATH: '/xhr', + FIRST_PARTY_CAPTCHA_PATH: '/captcha', + EMPTY_GIF_B64: 'R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', + // Cookie encryption configurations + COOKIE_ENCRYPTION: true, + CE_KEYLEN: 32, + CE_IVLEN: 16, + CE_ITERATIONS: 1000, + CE_DIGEST: 'sha256', + CE_ALGO: 'aes-256-cbc', + + STATIC_FILES_EXT: ['.css', '.bmp', '.tif', '.ttf', '.docx', '.woff2', '.js', '.pict', '.tiff', '.eot', '.xlsx', '.jpg', '.csv', '.eps', '.woff', '.xls', '.jpeg', '.doc', '.ejs', '.otf', '.pptx', '.gif', '.pdf', '.swf', '.svg', '.ps', '.ico', '.pls', '.midi', '.svgz', '.class', '.png', '.ppt', '.mid', 'webp', '.jar'], + + /* actions */ + SCORE_EVALUATE_ACTION: { + SPECIAL_TOKEN: -6, + SENSITIVE_ROUTE: -5, + UNEXPECTED_RESULT: -4, + NO_COOKIE: -3, + COOKIE_INVALID: -2, + COOKIE_EXPIRED: -1, + + S2S_PASS_TRAFFIC: 11, + COOKIE_PASS_TRAFFIC: 10, + S2S_TIMEOUT_PASS: 9, + COOKIE_BLOCK_TRAFFIC: -10, + S2S_BLOCK_TRAFFIC: -11, + CAPTCHA_BLOCK_TRAFFIC: -12, + CHALLENGE_BLOCK_TRAFFIC: -13, + + CAPTCHA_PASS: 0, + CAPTCHA_BLOCK: 1, + + GOOD_SCORE: 1, + BAD_SCORE: 0 + }, + + PASS_REASON: { + CAPTCHA_TIMEOUT: 'captcha_timeout', + CAPTCHA: 'captcha', + COOKIE: 'cookie', + S2S: 's2s', + S2S_TIMEOUT: 's2s_timeout', + MONITOR_MODE: 'monitor_mode', + INVALID_RESPONSE: 'invalid_response', + REQUEST_FAILED: 'request_failed' + }, + + MONITOR_MODE: { + MONITOR: 0, + BLOCK: 1 + }, + }; +} + +/* to be defined by the initiating user or dynamically via configuration service */ +function pxDefaultConfig(PX_INTERNAL) { + return { + PX_APP_ID: 'PX_APP_ID', + ENABLE_MODULE: true, + API_TIMEOUT_MS: 1000, + BLOCKING_SCORE: 100, + COOKIE_SECRET_KEY: 'cookie_secret_key', + AUTH_TOKEN: 'auth_token', + IP_HEADERS: [], + BLOCK_HTML: 'BLOCK', + SENSITIVE_HEADERS: ['cookie', 'cookies'], + PROXY_URL: '', + SEND_PAGE_ACTIVITIES: true, + DEBUG_MODE: false, + CUSTOM_REQUEST_HANDLER: '', + MAX_BUFFER_LEN: 30, + GET_USER_IP: '', + CSS_REF: [], + JS_REF: [], + CUSTOM_LOGO: '', + LOGO_VISIBILITY: 'hidden', + SENSITIVE_ROUTES: [], + WHITELIST_ROUTES: [], + DYNAMIC_CONFIGURATIONS: false, + CONFIGURATION_LOAD_INTERVAL: 5000, + MODULE_MODE: PX_INTERNAL.MONITOR_MODE.MONITOR, + ADDITIONAL_ACTIVITY_HANDLER: '', + ENRICH_CUSTOM_PARAMETERS: '', + FIRST_PARTY_ENABLED: true, + FIRST_PARTY_XHR_ENABLED: true, + TESTING_MODE: false, + WHITELIST_EXT: [], + BYPASS_MONITOR_HEADER: '', + }; +} +module.exports = PxConfig; \ No newline at end of file diff --git a/lib/pxcontext.js b/lib/pxcontext.js index dc2eb8d4..84f15f9d 100644 --- a/lib/pxcontext.js +++ b/lib/pxcontext.js @@ -1,74 +1,48 @@ -const net = require('net'); -const pxLogger = require('./pxlogger'); const pxUtil = require('./pxutil'); class PxContext { - - constructor(config, request) { - const userAgent = request.get('user-agent') || request.get('User-Agent') || 'none'; + constructor(config, req) { + const userAgent = req.get('user-agent') || req.get('User-Agent') || 'none'; const mobileSdkHeader = 'x-px-authorization'; const mobileSdkOriginalTokenHeader = 'x-px-original-token'; const vidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; this.cookies = {}; this.score = 0; - this.ip = PxContext.extractIP(config, request); + this.ip = pxUtil.extractIP(config, req); - this.headers = pxUtil.filterSensitiveHeaders(request.headers); - this.hostname = request.hostname || request.get('host'); + this.headers = pxUtil.filterSensitiveHeaders(req.headers, config.SENSITIVE_HEADERS); + this.hostname = req.hostname || req.get('host'); this.userAgent = userAgent; - this.uri = request.originalUrl || '/'; - this.fullUrl = request.protocol + '://' + request.get('host') + request.originalUrl; - this.httpVersion = request.httpVersion || ''; - this.httpMethod = request.method || ''; + this.uri = req.originalUrl || '/'; + this.fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl; + this.httpVersion = req.httpVersion || ''; + this.httpMethod = req.method || ''; this.sensitiveRoute = this.isSpecialRoute(config.SENSITIVE_ROUTES, this.uri); this.whitelistRoute = this.isSpecialRoute(config.WHITELIST_ROUTES, this.uri); this.cookieOrigin = 'cookie'; const mobileHeader = this.headers[mobileSdkHeader]; if (mobileHeader !== undefined) { this.cookieOrigin = 'header'; - pxLogger.debug('Mobile SDK token detected'); + config.logger.debug('Mobile SDK token detected'); this.originalToken = this.headers[mobileSdkOriginalTokenHeader]; const tokenObject = this.getTokenObject(mobileHeader); this.cookies[tokenObject.key] = tokenObject.value; } else { - this.requestCookieNames = Object.keys(request.cookies); - Object.keys(request.cookies).forEach(key => { + this.requestCookieNames = Object.keys(req.cookies); + Object.keys(req.cookies).forEach(key => { if (key.match(/^_px\d?$/)) { - this.cookies[key] = request.cookies[key]; + this.cookies[key] = req.cookies[key]; } else if (key === '_pxhd') { - this.pxhd = request.cookies[key]; - } else if ((key === '_pxvid' || key === 'pxvid') && vidRegex.test(request.cookies[key])) { - this.vid = request.cookies[key]; + this.pxhd = req.cookies[key]; + } else if ((key === '_pxvid' || key === 'pxvid') && vidRegex.test(req.cookies[key])) { + this.vid = req.cookies[key]; this.vidSource = 'vid_cookie'; } }); } } - static extractIP(config, request) { - let ip; - if (Array.isArray(config.IP_HEADERS)) { - config.IP_HEADERS.some(ipHeader => { - try { - const headerValue = request.get(ipHeader); - if (headerValue) { - ip = headerValue; - return true; - } - } catch (e) { - pxLogger.debug('Failed to use IP_HEADERS from config.'); - } - }); - } else { - ip = typeof config.GET_USER_IP === 'function' && config.GET_USER_IP(request); - } - if (ip && net.isIP(ip) > 0) { - return ip; - } - return request.ip; - } - getCookie() { return this.cookies['_px3'] ? this.cookies['_px3'] : this.cookies['_px']; } diff --git a/lib/pxcookie.js b/lib/pxcookie.js index ea790c9f..c50d32a7 100644 --- a/lib/pxcookie.js +++ b/lib/pxcookie.js @@ -1,6 +1,4 @@ 'use strict'; -const pxConfig = require('./pxconfig'); -const pxLogger = require('./pxlogger'); const CookieV1 = require('./cookie/cookieV1'); const CookieV3 = require('./cookie/cookieV3'); const TokenV1 = require('./cookie/tokenV1'); @@ -12,86 +10,84 @@ exports.evalCookie = evalCookie; /** * evalCookie - main cookie evaluation function. dectypt, decode and verify cookie content. if score. * - * @param {Object} pxCtx - current request context. + * @param {Object} ctx - current request context. * * @return {Number} evaluation results, derived from configured enum on PX_DEFAULT.COOKIE_EVAL. possible values: (NO_COOKIE, COOKIE_INVALID, COOKIE_EXPIRED, UNEXPECTED_RESULT, BAD_SCORE, GOOD_SCORE). * */ -function evalCookie(pxCtx) { - - const config = pxConfig.conf; - const pxCookie = pxCtx.getCookie(); +function evalCookie(ctx, config) { + const pxCookie = ctx.getCookie(); try { - if (Object.keys(pxCtx.cookies).length === 0) { - pxLogger.debug('Cookie is missing'); - pxCtx.s2sCallReason = 'no_cookie'; + if (Object.keys(ctx.cookies).length === 0) { + config.logger.debug('Cookie is missing'); + ctx.s2sCallReason = 'no_cookie'; return config.SCORE_EVALUATE_ACTION.NO_COOKIE; } if (!config.COOKIE_SECRET_KEY) { - pxLogger.debug('No cookie key found, pause cookie evaluation'); - pxCtx.s2sCallReason = 'no_cookie_key'; + config.logger.debug('No cookie key found, pause cookie evaluation'); + ctx.s2sCallReason = 'no_cookie_key'; return config.SCORE_EVALUATE_ACTION.UNEXPECTED_RESULT; } // Mobile SDK traffic - if (pxCookie && pxCtx.cookieOrigin === 'header') { + if (pxCookie && ctx.cookieOrigin === 'header') { if (pxCookie.match(/^\d+$/)) { - pxCtx.s2sCallReason = `mobile_error_${pxCookie}`; - if (pxCtx.originalToken) { - originalTokenValidator.evalCookie(pxCtx, config); + ctx.s2sCallReason = `mobile_error_${pxCookie}`; + if (ctx.originalToken) { + originalTokenValidator.evalCookie(ctx, config); } return config.SCORE_EVALUATE_ACTION.SPECIAL_TOKEN; } } - const cookie = pxCookieFactory(pxCtx, config); - pxLogger.debug(`Cookie ${getCookieVersion(pxCtx)} found, Evaluating`); + const cookie = pxCookieFactory(ctx, config); + config.logger.debug(`Cookie ${getCookieVersion(ctx)} found, Evaluating`); if (!cookie.deserialize()) { - pxCtx.s2sCallReason = 'cookie_decryption_failed'; - pxCtx.px_orig_cookie = getPxCookieFromContext(pxCtx); - pxLogger.debug(`Cookie decryption failed, value: ${pxCtx.px_orig_cookie}`); + ctx.s2sCallReason = 'cookie_decryption_failed'; + ctx.px_orig_cookie = getPxCookieFromContext(ctx); + config.logger.debug(`Cookie decryption failed, value: ${ctx.px_orig_cookie}`); return config.SCORE_EVALUATE_ACTION.COOKIE_INVALID; } - pxCtx.decodedCookie = cookie.decodedCookie; - pxCtx.score = cookie.getScore(); - pxCtx.vid = cookie.getVid(); - pxCtx.vidSource = 'risk_cookie'; - pxCtx.uuid = cookie.getUuid(); - pxCtx.hmac = cookie.getHmac(); - pxCtx.blockAction = cookie.getBlockAction(); + ctx.decodedCookie = cookie.decodedCookie; + ctx.score = cookie.getScore(); + ctx.vid = cookie.getVid(); + ctx.vidSource = 'risk_cookie'; + ctx.uuid = cookie.getUuid(); + ctx.hmac = cookie.getHmac(); + ctx.blockAction = cookie.getBlockAction(); if (cookie.isExpired()) { - pxLogger.debug(`Cookie TTL is expired, value: ${JSON.stringify(cookie.decodedCookie)}, age: ${Date.now() - cookie.getTime()}`); - pxCtx.s2sCallReason = 'cookie_expired'; + config.logger.debug(`Cookie TTL is expired, value: ${JSON.stringify(cookie.decodedCookie)}, age: ${Date.now() - cookie.getTime()}`); + ctx.s2sCallReason = 'cookie_expired'; return config.SCORE_EVALUATE_ACTION.COOKIE_EXPIRED; } if (cookie.isHighScore()) { - pxLogger.debug(`Cookie evaluation ended successfully, risk score: ${cookie.getScore()}`); + config.logger.debug(`Cookie evaluation ended successfully, risk score: ${cookie.getScore()}`); return config.SCORE_EVALUATE_ACTION.BAD_SCORE; } if (!cookie.isSecure()) { - pxLogger.debug(`Cookie HMAC validation failed, value: ${JSON.stringify(cookie.decodedCookie)} user-agent: ${pxCtx.userAgent}`); - pxCtx.s2sCallReason = 'cookie_validation_failed'; + config.logger.debug(`Cookie HMAC validation failed, value: ${JSON.stringify(cookie.decodedCookie)} user-agent: ${ctx.userAgent}`); + ctx.s2sCallReason = 'cookie_validation_failed'; return config.SCORE_EVALUATE_ACTION.COOKIE_INVALID; } - if (pxCtx.sensitiveRoute) { - pxLogger.debug(`Sensitive route match, sending Risk API. path: ${pxCtx.uri}`); - pxCtx.s2sCallReason = 'sensitive_route'; + if (ctx.sensitiveRoute) { + config.logger.debug(`Sensitive route match, sending Risk API. path: ${ctx.uri}`); + ctx.s2sCallReason = 'sensitive_route'; return config.SCORE_EVALUATE_ACTION.SENSITIVE_ROUTE; } - pxCtx.passReason = config.PASS_REASON.COOKIE; - pxLogger.debug(`Cookie evaluation ended successfully, risk score: ${cookie.getScore()}`); + ctx.passReason = config.PASS_REASON.COOKIE; + config.logger.debug(`Cookie evaluation ended successfully, risk score: ${cookie.getScore()}`); return config.SCORE_EVALUATE_ACTION.GOOD_SCORE; } catch (e) { - pxLogger.error('Error while evaluating perimeterx cookie: ' + e.message); - pxCtx.s2sCallReason = 'cookie_decryption_failed'; + config.logger.error('Error while evaluating perimeterx cookie: ' + e.message); + ctx.s2sCallReason = 'cookie_decryption_failed'; return config.SCORE_EVALUATE_ACTION.UNEXPECTED_RESULT; } } @@ -99,20 +95,20 @@ function evalCookie(pxCtx) { /** * Factory method for creating PX Cookie object according to cookie version and type found on the request */ -function pxCookieFactory(pxCtx, pxConfig) { - if (pxCtx.cookieOrigin == 'cookie') { - return (pxCtx.cookies['_px3'] ? new CookieV3(pxCtx, pxConfig) : new CookieV1(pxCtx, pxConfig)); +function pxCookieFactory(ctx, config) { + if (ctx.cookieOrigin == 'cookie') { + return (ctx.cookies['_px3'] ? new CookieV3(ctx, config, config.logger) : new CookieV1(ctx, config, config.logger)); } else { - return (pxCtx.cookies['_px3'] ? new TokenV3(pxCtx, pxConfig, pxCtx.cookies['_px3']) : new TokenV1(pxCtx, pxConfig, pxCtx.cookies['_px'])); + return (ctx.cookies['_px3'] ? new TokenV3(ctx, config, ctx.cookies['_px3'], config.logger) : new TokenV1(ctx, config, ctx.cookies['_px'], config.logger)); } } -function getCookieVersion(pxCtx) { - return pxCtx.cookies['_px3'] ? 'V3' : 'V1'; +function getCookieVersion(ctx) { + return ctx.cookies['_px3'] ? 'V3' : 'V1'; } -function getPxCookieFromContext(pxCtx) { - if (Object.keys(pxCtx.cookies).length) { - return pxCtx.cookies['_px3'] ? pxCtx.cookies['_px3'] : pxCtx.cookies['_px']; +function getPxCookieFromContext(ctx) { + if (Object.keys(ctx.cookies).length) { + return ctx.cookies['_px3'] ? ctx.cookies['_px3'] : ctx.cookies['_px']; } } diff --git a/lib/pxenforcer.js b/lib/pxenforcer.js index e2903a62..954123c3 100644 --- a/lib/pxenforcer.js +++ b/lib/pxenforcer.js @@ -1,141 +1,148 @@ 'use strict'; +const mu = require('mu2'); const PxClient = require('./pxclient'); -const pxLogger = require('./pxlogger'); const PxContext = require('./pxcontext'); const PxConfig = require('./pxconfig'); -const mu = require('mu2'); const pxProxy = require('./pxproxy'); const pxUtil = require('./pxutil'); const pxApi = require('./pxapi'); const pxCookie = require('./pxcookie'); +const PxLogger = require('./pxlogger'); +const ConfigLoader = require('./configloader'); class PxEnforcer { - constructor(params, client) { - if (client) { - this.pxClient = client; - } else { - this.pxClient = new PxClient(); + this.logger = new PxLogger(); + + this.pxConfig = new PxConfig(params, this.logger); + this._config = this.pxConfig.conf; + + this.logger.init(this.pxConfig); + + this.pxClient = (client) ? client : new PxClient(); + this.pxClient.init(this._config); + this.pxClient.sendEnforcerTelemetry('initial_config', this); + if (this._config.DYNAMIC_CONFIGURATIONS) { + this.config.configLoader = new ConfigLoader(this.pxConfig, this.pxClient); + this.config.configLoader.init(); } - this.pxClient.init(); - PxConfig.init(params, this.pxClient); - this.reversePrefix = PxConfig.conf.PX_APP_ID.substring(2); + + this.reversePrefix = this.pxConfig.conf.PX_APP_ID.substring(2); } enforce(req, res, cb) { - const pxConfig = PxConfig.conf; const requestUrl = req.originalUrl; - pxLogger.debug('Starting request verification'); + this.logger.debug('Starting request verification'); - if (requestUrl.startsWith(`/${this.reversePrefix}${pxConfig.FIRST_PARTY_VENDOR_PATH}`)) { + if (requestUrl.startsWith(`/${this.reversePrefix}${this._config.FIRST_PARTY_VENDOR_PATH}`)) { // reverse proxy client - return pxProxy.getClient(req, pxConfig, PxContext.extractIP(pxConfig, req), cb); + return pxProxy.getClient(req, this._config, pxUtil.extractIP(this._config, req), cb); } - if (requestUrl.startsWith(`/${this.reversePrefix}${pxConfig.FIRST_PARTY_XHR_PATH}`)) { + if (requestUrl.startsWith(`/${this.reversePrefix}${this._config.FIRST_PARTY_XHR_PATH}`)) { //reverse proxy xhr - return pxProxy.sendXHR(req, pxConfig, PxContext.extractIP(pxConfig, req), this.reversePrefix, cb); + return pxProxy.sendXHR(req, this._config, pxUtil.extractIP(this._config, req), this.reversePrefix, cb); } - if (requestUrl.startsWith(`/${this.reversePrefix}${pxConfig.FIRST_PARTY_CAPTCHA_PATH}`)) { + if (requestUrl.startsWith(`/${this.reversePrefix}${this._config.FIRST_PARTY_CAPTCHA_PATH}`)) { // reverse proxy captcha - return pxProxy.getCaptcha(req, pxConfig, PxContext.extractIP(pxConfig, req), this.reversePrefix, cb); + return pxProxy.getCaptcha(req, this._config, pxUtil.extractIP(this._config, req), this.reversePrefix, cb); } - if (!pxConfig.ENABLE_MODULE || pxUtil.checkForStatic(req, pxConfig.STATIC_FILES_EXT)) { - pxLogger.debug('Request will not be verified, module is disabled'); + if (!this._config.ENABLE_MODULE || pxUtil.checkForStatic(req, this._config.STATIC_FILES_EXT)) { + this.logger.debug('Request will not be verified, module is disabled'); return cb(); } try { - const pxCtx = new PxContext(pxConfig, req); - pxLogger.debug('Request context created successfully'); - pxCtx.collectorUrl = `https://collector-${pxConfig.PX_APP_ID}.perimeterx.net`; + const ctx = new PxContext(this._config, req, this.logger); + this.logger.debug('Request context created successfully'); + ctx.collectorUrl = `https://collector-${this._config.PX_APP_ID}.perimeterx.net`; - if (pxCtx.whitelistRoute) { - pxLogger.debug(`Whitelist route match: ${pxCtx.uri}`); + if (ctx.whitelistRoute) { + this.logger.debug(`Whitelist route match: ${ctx.uri}`); return cb(); } - this.verifyUserScore(pxCtx, pxConfig, () => { - this.handleVerification(pxCtx, pxConfig, req, res, cb); + this.verifyUserScore(ctx, () => { + this.handleVerification(ctx, req, res, cb); }); } catch (err) { return cb(); } } - verifyUserScore(pxCtx, pxConfig, callback) { + verifyUserScore(ctx, callback) { const startRiskRtt = Date.now(); - pxCtx.riskRtt = 0; + ctx.riskRtt = 0; try { - if (!pxCtx.ip || !pxCtx.uri) { - pxLogger.error('perimeterx score evaluation failed. bad parameters.'); - return callback(pxConfig.SCORE_EVALUATE_ACTION.COOKIE_PASS_TRAFFIC); + if (!ctx.ip || !ctx.uri) { + this.logger.error('perimeterx score evaluation failed. bad parameters.'); + return callback(this._config.SCORE_EVALUATE_ACTION.COOKIE_PASS_TRAFFIC); } - const action = pxCookie.evalCookie(pxCtx); + const action = pxCookie.evalCookie(ctx, this._config); /* score did not cross threshold - pass traffic */ - if (action === pxConfig.SCORE_EVALUATE_ACTION.GOOD_SCORE) { - return callback(pxConfig.SCORE_EVALUATE_ACTION.COOKIE_PASS_TRAFFIC); + if (action === this._config.SCORE_EVALUATE_ACTION.GOOD_SCORE) { + return callback(this._config.SCORE_EVALUATE_ACTION.COOKIE_PASS_TRAFFIC); } /* score crossed threshold - block traffic */ - if (action === pxConfig.SCORE_EVALUATE_ACTION.BAD_SCORE) { - pxCtx.blockReason = 'cookie_high_score'; - return callback(pxConfig.SCORE_EVALUATE_ACTION.COOKIE_BLOCK_TRAFFIC); + if (action === this._config.SCORE_EVALUATE_ACTION.BAD_SCORE) { + ctx.blockReason = 'cookie_high_score'; + return callback(this._config.SCORE_EVALUATE_ACTION.COOKIE_BLOCK_TRAFFIC); } /* when no fallback to s2s call if cookie does not exist or failed on evaluation */ - pxApi.evalByServerCall(pxCtx, (action) => { - pxCtx.riskRtt = Date.now() - startRiskRtt; + pxApi.evalByServerCall(ctx, this._config, (action) => { + ctx.riskRtt = Date.now() - startRiskRtt; - if (action === pxConfig.SCORE_EVALUATE_ACTION.UNEXPECTED_RESULT) { - pxLogger.error('perimeterx score evaluation failed. unexpected error. passing traffic'); - return callback(pxConfig.SCORE_EVALUATE_ACTION.S2S_PASS_TRAFFIC); + if (action === this._config.SCORE_EVALUATE_ACTION.UNEXPECTED_RESULT) { + this.logger.error('perimeterx score evaluation failed. unexpected error. passing traffic'); + return callback(this._config.SCORE_EVALUATE_ACTION.S2S_PASS_TRAFFIC); } - pxLogger.debug(`Risk API response returned successfully, risk score: ${pxCtx.score}, round_trip_time: ${pxCtx.riskRtt}ms`); + this.logger.debug(`Risk API response returned successfully, risk score: ${ctx.score}, round_trip_time: ${ctx.riskRtt}ms`); - if (action === pxConfig.SCORE_EVALUATE_ACTION.GOOD_SCORE) { - pxLogger.debug(`Risk score is lower than blocking score. score: ${pxCtx.score} blocking score: ${pxConfig.BLOCKING_SCORE}`); - return callback(pxConfig.SCORE_EVALUATE_ACTION.S2S_PASS_TRAFFIC); + if (action === this._config.SCORE_EVALUATE_ACTION.GOOD_SCORE) { + this.logger.debug(`Risk score is lower than blocking score. score: ${ctx.score} blocking score: ${this._config.BLOCKING_SCORE}`); + return callback(this._config.SCORE_EVALUATE_ACTION.S2S_PASS_TRAFFIC); } - if (action === pxConfig.SCORE_EVALUATE_ACTION.BAD_SCORE) { - pxLogger.debug(`Risk score is higher or equal to blocking score. score: ${pxCtx.score} blocking score: ${pxConfig.BLOCKING_SCORE}`); - switch (pxCtx.blockAction) { + if (action === this._config.SCORE_EVALUATE_ACTION.BAD_SCORE) { + this.logger.debug(`Risk score is higher or equal to blocking score. score: ${ctx.score} blocking score: ${this._config.BLOCKING_SCORE}`); + switch (ctx.blockAction) { case 'j': - pxCtx.blockReason = 'challenge'; + ctx.blockReason = 'challenge'; break; case 'r': - pxCtx.blockReason = 'exceeded_rate_limit'; + ctx.blockReason = 'exceeded_rate_limit'; break; default: - pxCtx.blockReason = 's2s_high_score'; + ctx.blockReason = 's2s_high_score'; } - return callback(pxConfig.SCORE_EVALUATE_ACTION.S2S_BLOCK_TRAFFIC); + return callback(this._config.SCORE_EVALUATE_ACTION.S2S_BLOCK_TRAFFIC); } - if(action === pxConfig.SCORE_EVALUATE_ACTION.S2S_TIMEOUT_PASS) { - pxLogger.debug(`Risk API timed out , round_trip_time: ${pxCtx.riskRtt}ms`); - return callback(pxConfig.SCORE_EVALUATE_ACTION.S2S_TIMEOUT_PASS); + if(action === this._config.SCORE_EVALUATE_ACTION.S2S_TIMEOUT_PASS) { + this.logger.debug(`Risk API timed out , round_trip_time: ${ctx.riskRtt}ms`); + return callback(this._config.SCORE_EVALUATE_ACTION.S2S_TIMEOUT_PASS); } }); } catch (e) { - pxLogger.error('perimeterx score evaluation failed. unexpected error. ' + e.message); - pxCtx.riskRtt = Date.now() - startRiskRtt; - return callback(pxConfig.SCORE_EVALUATE_ACTION.S2S_PASS_TRAFFIC); + this.logger.error('perimeterx score evaluation failed. unexpected error. ' + e.message); + ctx.riskRtt = Date.now() - startRiskRtt; + return callback(this._config.SCORE_EVALUATE_ACTION.S2S_PASS_TRAFFIC); } } - handleVerification(pxCtx, pxConfig, req, res, cb) { - const verified = pxCtx.score < pxConfig.BLOCKING_SCORE; + handleVerification(ctx, req, res, cb) { + const verified = ctx.score < this._config.BLOCKING_SCORE; if (res) { const setCookie = res.getHeader('Set-Cookie') ? res.getHeader('Set-Cookie') : ''; - const pxhdCookie = pxCtx.pxhd ? '_pxhd=' + pxCtx.pxhd : ''; + const pxhdCookie = ctx.pxhd ? '_pxhd=' + ctx.pxhd : ''; const setCookieModified = [setCookie, pxhdCookie].filter(Boolean); if (setCookieModified.length > 0) { res.setHeader('Set-Cookie', setCookieModified); @@ -143,24 +150,24 @@ class PxEnforcer { } // Handle async activities if (verified) { - this.pxPass(pxCtx); + this.pxPass(ctx); } else { - this.pxBlock(pxCtx, pxConfig); + this.pxBlock(ctx); } // check for additional activity handler - if (pxConfig.ADDITIONAL_ACTIVITY_HANDLER) { - pxConfig.ADDITIONAL_ACTIVITY_HANDLER(pxCtx, pxConfig); + if (this._config.ADDITIONAL_ACTIVITY_HANDLER) { + this._config.ADDITIONAL_ACTIVITY_HANDLER(ctx, this._config); } - if (pxConfig.CUSTOM_REQUEST_HANDLER) { + if (this._config.CUSTOM_REQUEST_HANDLER) { if (res) { - pxConfig.CUSTOM_REQUEST_HANDLER(pxCtx, pxConfig, req, res); + this._config.CUSTOM_REQUEST_HANDLER(ctx, this._config, req, res); if (res.headersSent) { return; } } else { - const result = pxConfig.CUSTOM_REQUEST_HANDLER(pxCtx, pxConfig, req); + const result = this._config.CUSTOM_REQUEST_HANDLER(ctx, this._config, req); if (result) { return cb(null, result); } @@ -168,22 +175,23 @@ class PxEnforcer { } // If verified, pass the request here - const shouldBypassMonitor = pxConfig.BYPASS_MONITOR_HEADER && req.headers[pxConfig.BYPASS_MONITOR_HEADER] === '1'; - if (verified || (pxConfig.MODULE_MODE === pxConfig.MONITOR_MODE.MONITOR && !shouldBypassMonitor)) { + const shouldBypassMonitor = this._config.BYPASS_MONITOR_HEADER && req.headers[this._config.BYPASS_MONITOR_HEADER] === '1'; + if (verified || (this._config.MODULE_MODE === this._config.MONITOR_MODE.MONITOR && !shouldBypassMonitor)) { 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 isJsonResponse = acceptHeaderValue && acceptHeaderValue.split(',').find((value) => value.toLowerCase() === 'application/json') && ctx.cookieOrigin === 'cookie' && ctx.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) { + this.logger.debug(`Enforcing action: ${pxUtil.parseAction(ctx.blockAction)} page is served ${isJsonResponse ? 'using advanced protection mode' : ''}`); + const config = this._config; + this.generateResponse(ctx, isJsonResponse, function (responseObject) { const response = { status: '403', statusDescription: 'Forbidden' }; - if (pxCtx.blockAction === 'r') { + if (ctx.blockAction === 'r') { response.status = '429'; response.statusDescription = 'Too Many Requests'; } @@ -205,79 +213,80 @@ class PxEnforcer { response.header = {key: 'Content-Type', value: 'text/html'}; response.body = responseObject; - if (pxCtx.cookieOrigin !== 'cookie') { + if (ctx.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, + action: pxUtil.parseAction(ctx.blockAction), + uuid: ctx.uuid, + vid: ctx.vid, + appId: config.PX_APP_ID, page: new Buffer(responseObject).toString('base64'), - collectorUrl: pxCtx.collectorUrl + collectorUrl: ctx.collectorUrl }; } cb(null, response); }); } - + get config() { - return PxConfig; + return this.pxConfig; } /** * pxPass - pass handler, sends page_requested activity and passes the request using next() - * @param {Object} pxCtx - current request context. + * @param {Object} ctx - current request context. + * @param {Object} config - perimeterx config. */ - pxPass(pxCtx) { + pxPass(ctx) { const details = { - 'px_cookie': pxCtx.decodedCookie, - 'client_uuid': pxCtx.uuid, - 'pass_reason': pxCtx.passReason, - 'risk_rtt': pxCtx.riskRtt, - 'module_version': PxConfig.conf.MODULE_VERSION + 'px_cookie': ctx.decodedCookie, + 'client_uuid': ctx.uuid, + 'pass_reason': ctx.passReason, + 'risk_rtt': ctx.riskRtt, + 'module_version': this.pxConfig.conf.MODULE_VERSION }; - pxLogger.debug('Sending page requested activity'); - this.pxClient.sendToPerimeterX('page_requested', details, pxCtx); + this.logger.debug('Sending page requested activity'); + this.pxClient.sendToPerimeterX('page_requested', details, ctx, this._config); } /** * pxBlock - block handler, send blocking activity to px and render the block html back to screen * - * @param pxConfig - * @param {Object} pxCtx - current request context. + * @param {Object} ctx - current request context. + * @param config */ - pxBlock(pxCtx, pxConfig) { + pxBlock(ctx) { const details = { - block_reason: pxCtx.blockReason, - client_uuid: pxCtx.uuid, + block_reason: ctx.blockReason, + client_uuid: ctx.uuid, block_module: 'px-node-express', - block_score: pxCtx.score, - module_version: PxConfig.conf.MODULE_VERSION, - simulated_block: pxConfig.MODULE_MODE === pxConfig.MONITOR_MODE.MONITOR + block_score: ctx.score, + module_version: this.pxConfig.conf.MODULE_VERSION, + simulated_block: this._config.MODULE_MODE === this._config.MONITOR_MODE.MONITOR }; - pxLogger.debug(`Sending block activity`); - this.pxClient.sendToPerimeterX('block', details, pxCtx); + this.logger.debug(`Sending block activity`); + this.pxClient.sendToPerimeterX('block', details, ctx, this._config); } /** - * generateResponse - genarating HTML string from pxContext and pxConfig in case + * generateResponse - genarating HTML string from ctx in case * action was to block the request * - * @param {Object} pxContext - current request context. - * @param {Object} pxConfig - current Px configs + * @param {Object} ctx - current request context. + * @param {Object} config - current Px configs * @param {Function} cb - send the generated html value. */ - generateResponse(pxContext, pxConfig, jsonResponse, cb) { + generateResponse(ctx, jsonResponse, cb) { let template; - switch (pxContext.blockAction) { + switch (ctx.blockAction) { case 'j': - return cb(pxContext.blockActionData); + return cb(ctx.blockActionData); case 'r': return this.compileMustache('ratelimit', {}, cb); } - const props = this.getProps(pxContext, pxConfig, template); + const props = this.getProps(ctx, template); if (jsonResponse) { return cb(props); } else { @@ -285,30 +294,30 @@ class PxEnforcer { } } - getProps(pxContext, pxConfig) { - let jsClientSrc = `//${pxConfig.CLIENT_HOST}/${pxConfig.PX_APP_ID}/main.min.js`; - let captchaSrc = `//${pxConfig.CAPTCHA_HOST}/${pxConfig.PX_APP_ID}/captcha.js?a=${pxContext.blockAction}&u=${pxContext.uuid}&v=${pxContext.vid || ''}&m=${pxContext.isMobile() ? '1' :'0'}`; - let hostUrl = pxContext.collectorUrl; + getProps(ctx) { + let jsClientSrc = `//${this._config.CLIENT_HOST}/${this._config.PX_APP_ID}/main.min.js`; + let captchaSrc = `//${this._config.CAPTCHA_HOST}/${this._config.PX_APP_ID}/captcha.js?a=${ctx.blockAction}&u=${ctx.uuid}&v=${ctx.vid || ''}&m=${ctx.isMobile() ? '1' :'0'}`; + let hostUrl = ctx.collectorUrl; - if (pxConfig.FIRST_PARTY_ENABLED && !pxContext.isMobile()) { - const prefix = pxConfig.PX_APP_ID.substring(2); - jsClientSrc = `/${prefix}${pxConfig.FIRST_PARTY_VENDOR_PATH}`; - captchaSrc = `/${prefix}${pxConfig.FIRST_PARTY_CAPTCHA_PATH}/captcha.js?a=${pxContext.blockAction}&u=${pxContext.uuid}&v=${pxContext.vid || ''}&m=${pxContext.isMobile() ? '1' :'0'}`; - hostUrl = `/${prefix}${pxConfig.FIRST_PARTY_XHR_PATH}`; + if (this._config.FIRST_PARTY_ENABLED && !ctx.isMobile()) { + const prefix = this._config.PX_APP_ID.substring(2); + jsClientSrc = `/${prefix}${this._config.FIRST_PARTY_VENDOR_PATH}`; + captchaSrc = `/${prefix}${this._config.FIRST_PARTY_CAPTCHA_PATH}/captcha.js?a=${ctx.blockAction}&u=${ctx.uuid}&v=${ctx.vid || ''}&m=${ctx.isMobile() ? '1' :'0'}`; + hostUrl = `/${prefix}${this._config.FIRST_PARTY_XHR_PATH}`; } return { - refId: pxContext.uuid, - appId: pxConfig.PX_APP_ID, - vid: pxContext.vid, - uuid: pxContext.uuid, - customLogo: pxConfig.CUSTOM_LOGO, - cssRef: pxConfig.CSS_REF, - jsRef: pxConfig.JS_REF, - logoVisibility: pxConfig.CUSTOM_LOGO ? 'visible' : 'hidden', + refId: ctx.uuid, + appId: this._config.PX_APP_ID, + vid: ctx.vid, + uuid: ctx.uuid, + customLogo: this._config.CUSTOM_LOGO, + cssRef: this._config.CSS_REF, + jsRef: this._config.JS_REF, + logoVisibility: this._config.CUSTOM_LOGO ? 'visible' : 'hidden', hostUrl: hostUrl, jsClientSrc: jsClientSrc, - firstPartyEnabled: pxConfig.FIRST_PARTY_ENABLED, + firstPartyEnabled: this._config.FIRST_PARTY_ENABLED, blockScript: captchaSrc }; } diff --git a/lib/pxhttpc.js b/lib/pxhttpc.js index b87426d3..f0d1c4cb 100644 --- a/lib/pxhttpc.js +++ b/lib/pxhttpc.js @@ -1,7 +1,6 @@ 'use strict'; + const request = require('./request'); -const pxConfig = require('./pxconfig'); -const pxLogger = require('./pxlogger'); module.exports = { callServer @@ -16,9 +15,8 @@ module.exports = { * @param {string} callType - indication for a query or activities sending * @param {Function} callback - callback function. */ -function callServer(data, headers, uri, callType, callback) { - callback = callback || ((err) => { err && pxLogger.debug(`callServer default callback. Error: ${err}`); }); - const config = pxConfig.conf; +function callServer(data, headers, uri, callType, config, callback) { + callback = callback || ((err) => { err && config.logger.debug(`callServer default callback. Error: ${err}`); }); const callData = { 'url': `https://${config.SERVER_HOST}${uri}`, 'data': JSON.stringify(data), @@ -28,7 +26,7 @@ function callServer(data, headers, uri, callType, callback) { callData.timeout = callType === 'query' ? config.API_TIMEOUT_MS : config.ACTIVITIES_TIMEOUT; try { - request.post(callData, function (err, response) { + request.post(callData, config, function (err, response) { let data; if (err) { if (err == 'Error: Timeout has been reached.') { diff --git a/lib/pxlogger.js b/lib/pxlogger.js index bf7e6aaf..eff1ddb6 100644 --- a/lib/pxlogger.js +++ b/lib/pxlogger.js @@ -3,22 +3,28 @@ * Version 1.0 Published 12 May 2016 */ -module.exports = { - error: error, - debug: debug -}; +class PxLogger { + constructor() { + this.debugMode = false; + this.appId = ''; + } -function debug(msg) { - const pxConfig = require('./pxconfig'); - if (pxConfig.conf.DEBUG_MODE && msg) { - console.info(`[PerimeterX - DEBUG][${pxConfig.conf.PX_APP_ID}] - ${msg}`); + init(pxConfig) { + this.debugMode = pxConfig.conf.DEBUG_MODE; + this.appId = pxConfig.conf.PX_APP_ID; } -} -function error(msg) { - const pxConfig = require('./pxconfig'); - if (typeof msg === 'string') { - console.error(new Error(`[PerimeterX - ERROR][${pxConfig.conf.PX_APP_ID}] - ${msg}`).stack); + debug(msg) { + if (this.debugMode && msg) { + console.info(`[PerimeterX - DEBUG][${this.appId}] - ${msg}`); + } + } + + error(msg) { + if (typeof msg === 'string') { + console.error(new Error(`[PerimeterX - ERROR][${this.appId}] - ${msg}`).stack); + } } } +module.exports = PxLogger; diff --git a/lib/pxoriginaltoken.js b/lib/pxoriginaltoken.js index b3792491..b38a76f4 100644 --- a/lib/pxoriginaltoken.js +++ b/lib/pxoriginaltoken.js @@ -1,33 +1,32 @@ const TokenV3 = require('./cookie/tokenV3'); const TokenV1 = require('./cookie/tokenV1'); -const pxLogger = require('./pxlogger'); -function evalCookie(pxCtx, pxConfig) { +function evalCookie(ctx, config) { try { - const noVersionOriginalToken = pxCtx.originalToken; - const cookie = (pxCtx.cookies['_px3'] ? new TokenV3(pxCtx, pxConfig, noVersionOriginalToken) : new TokenV1(pxCtx, pxConfig, noVersionOriginalToken)); - pxLogger.debug('Original token found, Evaluating'); + const noVersionOriginalToken = ctx.originalToken; + const cookie = (ctx.cookies['_px3'] ? new TokenV3(ctx, config, noVersionOriginalToken) : new TokenV1(ctx, config, noVersionOriginalToken)); + config.logger.debug('Original token found, Evaluating'); if (!cookie.deserialize()) { - pxLogger.debug(`Original token decryption failed, value: ${pxCtx.originalToken}`); - pxCtx.originalTokenError = 'decryption_failed'; + config.logger.debug(`Original token decryption failed, value: ${ctx.originalToken}`); + ctx.originalTokenError = 'decryption_failed'; return; } - pxCtx.decodedOriginalToken = cookie.decodedCookie; - pxCtx.vid = cookie.getVid(); - pxCtx.originalUuid = cookie.getUuid(); + ctx.decodedOriginalToken = cookie.decodedCookie; + ctx.vid = cookie.getVid(); + ctx.originalUuid = cookie.getUuid(); if (!cookie.isSecure()) { - pxLogger.debug(`Original token HMAC validation failed, value: ${JSON.stringify(cookie.decodedCookie)} user-agent: ${pxCtx.userAgent}`); - pxCtx.originalTokenError = 'validation_failed'; + config.logger.debug(`Original token HMAC validation failed, value: ${JSON.stringify(cookie.decodedCookie)} user-agent: ${ctx.userAgent}`); + ctx.originalTokenError = 'validation_failed'; return; } return; } catch (e) { - pxLogger.error(`Error while evaluating perimeterx original token: ${e.message}`); - pxCtx.originalTokenError = 'decryption_failed'; + config.logger.error(`Error while evaluating perimeterx original token: ${e.message}`); + ctx.originalTokenError = 'decryption_failed'; return; } } diff --git a/lib/pxpayload.js b/lib/pxpayload.js index 96ca6998..2d89e8da 100644 --- a/lib/pxpayload.js +++ b/lib/pxpayload.js @@ -1,11 +1,9 @@ 'use strict'; -const pxLogger = require('./pxlogger'); const crypto = require('crypto'); - class Payload { - constructor() { + constructor(config) { //cookie string this.pxCookie = ''; @@ -13,10 +11,10 @@ class Payload { this.decodedCookie = ''; //PerimeterX configuration object - this.pxConfig = {}; + this.config = config; //PerimeterX context - this.pxContext = {}; + this.ctx = {}; //cookie secret string this.cookieSecret = ''; @@ -27,7 +25,7 @@ class Payload { * @returns {boolean} */ isHighScore() { - return this.getScore() >= this.pxConfig.BLOCKING_SCORE; + return this.getScore() >= this.config.BLOCKING_SCORE; } /** @@ -69,7 +67,7 @@ class Payload { return true; } let cookie; - if (this.pxConfig.COOKIE_ENCRYPTION) { + if (this.config.COOKIE_ENCRYPTION) { cookie = this.decrypt(); } else { cookie = this.decode(); @@ -90,47 +88,47 @@ class Payload { try { const data = this.pxCookie.split(':'); if (data.length !== 3) { - pxLogger.debug('invalid cookie format - wrong number of parts'); + this.config.logger.debug('invalid cookie format - wrong number of parts'); return ''; } const iterations = Number(data[1]); const encryptedCookie = data[2]; /* iterations value is not a number */ if (!iterations) { - pxLogger.debug('invalid cookie format - iterations value is not a number'); + this.config.logger.debug('invalid cookie format - iterations value is not a number'); return ''; } /* iterations value is not in the legit range */ if (iterations > 5000 || iterations < 500) { - pxLogger.debug('invalid cookie format - iterations out of bounds'); + this.config.logger.debug('invalid cookie format - iterations out of bounds'); return ''; } /* salt value is not as expected */ if (!data[0] || typeof data[0] !== 'string' || data[0].length > 100) { - pxLogger.debug('invalid cookie format - invalid salt value'); + this.config.logger.debug('invalid cookie format - invalid salt value'); return ''; } /* cookie value is not as expected */ if (!encryptedCookie || typeof encryptedCookie !== 'string') { - pxLogger.debug('invalid cookie format - no cookie value'); + this.config.logger.debug('invalid cookie format - no cookie value'); return ''; } const salt = new Buffer(data[0], 'base64'); - const derivation = crypto.pbkdf2Sync(this.cookieSecret, salt, iterations, this.pxConfig.CE_KEYLEN + this.pxConfig.CE_IVLEN, this.pxConfig.CE_DIGEST); - const key = derivation.slice(0, this.pxConfig.CE_KEYLEN); - const iv = derivation.slice(this.pxConfig.CE_KEYLEN); + const derivation = crypto.pbkdf2Sync(this.cookieSecret, salt, iterations, this.config.CE_KEYLEN + this.config.CE_IVLEN, this.config.CE_DIGEST); + const key = derivation.slice(0, this.config.CE_KEYLEN); + const iv = derivation.slice(this.config.CE_KEYLEN); - const cipher = crypto.createDecipheriv(this.pxConfig.CE_ALGO, key, iv); + const cipher = crypto.createDecipheriv(this.config.CE_ALGO, key, iv); let decrypted = cipher.update(encryptedCookie, 'base64', 'utf8'); decrypted += cipher.final('utf8'); return JSON.parse(decrypted); } catch (err) { - pxLogger.error('Error while decrypting Perimeterx cookie: ' + err.message); + this.config.logger.error('Error while decrypting Perimeterx cookie: ' + err.message); return ''; } } @@ -145,14 +143,14 @@ class Payload { isHmacValid(hmacStr, cookieHmac) { try { - const hmac = crypto.createHmac(this.pxConfig.CE_DIGEST, this.cookieSecret); + const hmac = crypto.createHmac(this.config.CE_DIGEST, this.cookieSecret); hmac.setEncoding('hex'); hmac.write(hmacStr); hmac.end(); const h = hmac.read(); return h === cookieHmac; } catch (err) { - pxLogger.error('Error while validating Perimeterx cookie: ' + err.stack); + this.config.logger.error('Error while validating Perimeterx cookie: ' + err.stack); } } } diff --git a/lib/pxproxy.js b/lib/pxproxy.js index 98d3055c..64d25759 100644 --- a/lib/pxproxy.js +++ b/lib/pxproxy.js @@ -1,5 +1,4 @@ const request = require('./request'); -const pxLogger = require('./pxlogger'); const pxUtil = require('./pxutil'); const rawBody = require('raw-body'); const contentType = require('content-type'); @@ -8,7 +7,7 @@ const contentType = require('content-type'); * getClient - process the proxy request to get the captcha script file from PX servers. * * @param {object} req - the request object. - * @param {object} pxConfig - the PerimeterX config object. + * @param {object} config - the PerimeterX config object. * @param {string} ip - the ip that initiated the call. * @param {string} reversePrefix - the prefix of the xhr request. * @param {function} cb - the callback function to call at the end of the process. @@ -16,9 +15,9 @@ const contentType = require('content-type'); * @return {function} the callback function passed as param with the server response (captcha file) or error. * */ -function getCaptcha(req, pxConfig, ip, reversePrefix, cb) { +function getCaptcha(req, config, ip, reversePrefix, cb) { let res = {}; - if (!pxConfig.FIRST_PARTY_ENABLED) { + if (!config.FIRST_PARTY_ENABLED) { res = { status: 200, header: {key: 'Content-Type', value:'application/javascript'}, @@ -26,22 +25,23 @@ function getCaptcha(req, pxConfig, ip, reversePrefix, cb) { }; return cb(null, res); } else { - const searchMask = `/${reversePrefix}${pxConfig.FIRST_PARTY_CAPTCHA_PATH}`; + const searchMask = `/${reversePrefix}${config.FIRST_PARTY_CAPTCHA_PATH}`; const regEx = new RegExp(searchMask, 'ig'); - const pxRequestUri = `/${pxConfig.PX_APP_ID}${req.originalUrl.replace(regEx, '')}`; - pxLogger.debug(`Forwarding request from ${req.originalUrl} to xhr at ${pxConfig.CAPTCHA_HOST}${pxRequestUri}`); + const pxRequestUri = `/${config.PX_APP_ID}${req.originalUrl.replace(regEx, '')}`; + config.logger.debug(`Forwarding request from ${req.originalUrl} to xhr at ${config.CAPTCHA_HOST}${pxRequestUri}`); const callData = { - url: `https://${pxConfig.CAPTCHA_HOST}${pxRequestUri}`, - headers: pxUtil.filterSensitiveHeaders(req.headers), - timeout: pxConfig.API_TIMEOUT_MS + url: `https://${config.CAPTCHA_HOST}${pxRequestUri}`, + headers: pxUtil.filterSensitiveHeaders(req.headers, config.SENSITIVE_HEADERS), + timeout: config.API_TIMEOUT_MS }; - callData.headers['host'] = pxConfig.CAPTCHA_HOST; - callData.headers[pxConfig.ENFORCER_TRUE_IP_HEADER] = ip; - callData.headers[pxConfig.FIRST_PARTY_HEADER] = 1; - request.get(callData, (error, response) => { + callData.headers['host'] = config.CAPTCHA_HOST; + callData.headers[config.ENFORCER_TRUE_IP_HEADER] = ip; + callData.headers[config.FIRST_PARTY_HEADER] = 1; + request.get(callData, config, (error, response) => { if (error || !response) { - pxLogger.error(`Error while fetching first party captcha: ${error}`); + config.logger.error(`Error while fetching first party captcha: ${error}`); } + response = response || {statusCode: 200, headers: {}}; res = { status: response.statusCode, @@ -58,16 +58,16 @@ function getCaptcha(req, pxConfig, ip, reversePrefix, cb) { * getClient - process the proxy request to get the client file from PX servers. * * @param {object} req - the request object. - * @param {object} pxConfig - the PerimeterX config object. + * @param {object} config - the PerimeterX config object. * @param {string} ip - the ip that initiated the call. * @param {function} cb - the callback function to call at the end of the process. * * @return {function} the callback function passed as param with the server response (client file) or error. * */ -function getClient(req, pxConfig, ip, cb) { +function getClient(req, config, ip, cb) { let res = {}; - if (!pxConfig.FIRST_PARTY_ENABLED) { + if (!config.FIRST_PARTY_ENABLED) { res = { status: 200, header: {key: 'Content-Type', value:'application/javascript'}, @@ -75,19 +75,19 @@ function getClient(req, pxConfig, ip, cb) { }; return cb(null, res); } else { - const clientRequestUri = `/${pxConfig.PX_APP_ID}/main.min.js`; - pxLogger.debug(`Forwarding request from ${req.originalUrl.toLowerCase()} to client at ${pxConfig.CLIENT_HOST}${clientRequestUri}`); + const clientRequestUri = `/${config.PX_APP_ID}/main.min.js`; + config.logger.debug(`Forwarding request from ${req.originalUrl.toLowerCase()} to client at ${config.CLIENT_HOST}${clientRequestUri}`); const callData = { - url: `https://${pxConfig.CLIENT_HOST}${clientRequestUri}`, - headers: pxUtil.filterSensitiveHeaders(req.headers), - timeout: pxConfig.API_TIMEOUT_MS + url: `https://${config.CLIENT_HOST}${clientRequestUri}`, + headers: pxUtil.filterSensitiveHeaders(req.headers, config.SENSITIVE_HEADERS), + timeout: config.API_TIMEOUT_MS }; - callData.headers['host'] = pxConfig.CLIENT_HOST; - callData.headers[pxConfig.ENFORCER_TRUE_IP_HEADER] = ip; - callData.headers[pxConfig.FIRST_PARTY_HEADER] = 1; - request.get(callData, (error, response) => { + callData.headers['host'] = config.CLIENT_HOST; + callData.headers[config.ENFORCER_TRUE_IP_HEADER] = ip; + callData.headers[config.FIRST_PARTY_HEADER] = 1; + request.get(callData, config, (error, response) => { if (error || !response) { - pxLogger.error(`Error while fetching first party client: ${error}`); + config.logger.error(`Error while fetching first party client: ${error}`); } response = response || {statusCode: 200, headers: {}}; @@ -106,7 +106,7 @@ function getClient(req, pxConfig, ip, cb) { * sendXHR - process the proxy request for sending activities to PX servers. * * @param {object} req - the request object. - * @param {object} pxConfig - the PerimeterX config object. + * @param {object} config - the PerimeterX config object. * @param {string} ip - the ip that initiated the call. * @param {string} reversePrefix - the prefix of the xhr request. * @param {function} cb - the callback function to call at the end of the process. @@ -114,15 +114,15 @@ function getClient(req, pxConfig, ip, cb) { * @return {function} the callback function passed as param with the server response or error. * */ -function sendXHR(req, pxConfig, ip, reversePrefix, cb) { +function sendXHR(req, config, ip, reversePrefix, cb) { let res = {}; let vid; - if (!pxConfig.FIRST_PARTY_ENABLED || !pxConfig.FIRST_PARTY_XHR_ENABLED) { + if (!config.FIRST_PARTY_ENABLED || !config.FIRST_PARTY_XHR_ENABLED) { if (req.originalUrl.toLowerCase().includes('gif')) { res = { status: 200, header: {key: 'Content-Type', value:'image/gif'}, - body: Buffer.from(pxConfig.EMPTY_GIF_B64, 'base64') + body: Buffer.from(config.EMPTY_GIF_B64, 'base64') }; } else { res = { @@ -135,21 +135,21 @@ function sendXHR(req, pxConfig, ip, reversePrefix, cb) { } // handle proxy - parseBody(req).then(() => { - const searchMask = `/${reversePrefix}${pxConfig.FIRST_PARTY_XHR_PATH}`; + parseBody(req, config.logger).then(() => { + const searchMask = `/${reversePrefix}${config.FIRST_PARTY_XHR_PATH}`; const regEx = new RegExp(searchMask, 'ig'); const pxRequestUri = req.originalUrl.replace(regEx, ''); - pxLogger.debug(`Forwarding request from ${req.originalUrl} to xhr at ${pxConfig.COLLECTOR_HOST}${pxRequestUri}`); + config.logger.debug(`Forwarding request from ${req.originalUrl} to xhr at ${config.COLLECTOR_HOST}${pxRequestUri}`); const callData = { - url: `https://${pxConfig.COLLECTOR_HOST}${pxRequestUri}`, - headers: pxUtil.generateProxyHeaders(req.headers, req.ip), - timeout: pxConfig.API_TIMEOUT_MS + url: `https://${config.COLLECTOR_HOST}${pxRequestUri}`, + headers: pxUtil.generateProxyHeaders(req.headers, req.ip, config.SENSITIVE_HEADERS, config.FORWARDED_FOR_HEADER), + timeout: config.API_TIMEOUT_MS }; - callData.headers['host'] = pxConfig.COLLECTOR_HOST; - callData.headers[pxConfig.ENFORCER_TRUE_IP_HEADER] = ip; - callData.headers[pxConfig.FIRST_PARTY_HEADER] = 1; + callData.headers['host'] = config.COLLECTOR_HOST; + callData.headers[config.ENFORCER_TRUE_IP_HEADER] = ip; + callData.headers[config.FIRST_PARTY_HEADER] = 1; if (req.rawBody) { callData.headers['content-length'] = Buffer.byteLength(req.rawBody); // makes sure the length of the body is correct, as the body might go trough different body parsers. callData.data = req.rawBody; @@ -173,7 +173,7 @@ function sendXHR(req, pxConfig, ip, reversePrefix, cb) { } if (req.method === 'POST') { - request.post(callData, (error, response) => { + request.post(callData, config, (error, response) => { if (error || !response || response.statusCode >= 400) { if (req.originalUrl.toLowerCase().includes('/beacon')) { res = { @@ -197,13 +197,13 @@ function sendXHR(req, pxConfig, ip, reversePrefix, cb) { return cb(null, res); }); } else if (req.method === 'GET') { - request.get(callData, (error, response) => { + request.get(callData, config, (error, response) => { if (error || !response || response.statusCode >= 400) { if (req.originalUrl.toLowerCase().includes('.gif')) { res = { status: 200, header: {key: 'Content-Type', value:'image/gif'}, - body: Buffer.from(pxConfig.EMPTY_GIF_B64, 'base64') + body: Buffer.from(config.EMPTY_GIF_B64, 'base64') }; } else { res = { @@ -225,7 +225,7 @@ function sendXHR(req, pxConfig, ip, reversePrefix, cb) { }); } -function parseBody(req) { +function parseBody(req, pxLogger) { return new Promise((resolve) => { try { if (req.method == 'GET') { diff --git a/lib/pxtesthandler.js b/lib/pxtesthandler.js index fe1065a9..9ff1b41c 100644 --- a/lib/pxtesthandler.js +++ b/lib/pxtesthandler.js @@ -2,37 +2,37 @@ module.exports = { testModeRequestHandler: customRequestHandler}; -function customRequestHandler(pxCtx, pxconfig, req, res) { +function customRequestHandler(ctx, config, req, res) { const result = { - px_cookies : pxCtx.cookies, - uuid: pxCtx.uuid, - vid: pxCtx.vid, - ip: pxCtx.ip, - full_url: pxCtx.fullUrl, - score: pxCtx.score, - px_cookie_hmac : pxCtx.hmac, - block_action: pxCtx.blockAction, - http_method: pxCtx.httpMethod, - hostname: pxCtx.hostname, - headers: pxCtx.headers, - user_agent: pxCtx.userAgent, - uri: pxCtx.uri, - is_made_s2s_api_call: pxCtx.hasMadeServerCall || false, - sensitive_route: pxCtx.sensitiveRoute, - sensitive_routes_list: pxconfig.SENSITIVE_ROUTES, - whitelist_routes: pxconfig.WHITELIST_ROUTES, - decoded_px_cookie: pxCtx.decodedCookie, - cookie_origin: pxCtx.cookieOrigin, - http_version: pxCtx.httpVersion, - s2s_call_reason: pxCtx.s2sCallReason || 'none', - block_reason: pxCtx.blockReason || 'none', - module_mode: pxconfig.MODULE_MODE + px_cookies : ctx.cookies, + uuid: ctx.uuid, + vid: ctx.vid, + ip: ctx.ip, + full_url: ctx.fullUrl, + score: ctx.score, + px_cookie_hmac : ctx.hmac, + block_action: ctx.blockAction, + http_method: ctx.httpMethod, + hostname: ctx.hostname, + headers: ctx.headers, + user_agent: ctx.userAgent, + uri: ctx.uri, + is_made_s2s_api_call: ctx.hasMadeServerCall || false, + sensitive_route: ctx.sensitiveRoute, + sensitive_routes_list: config.SENSITIVE_ROUTES, + whitelist_routes: config.WHITELIST_ROUTES, + decoded_px_cookie: ctx.decodedCookie, + cookie_origin: ctx.cookieOrigin, + http_version: ctx.httpVersion, + s2s_call_reason: ctx.s2sCallReason || 'none', + block_reason: ctx.blockReason || 'none', + module_mode: config.MODULE_MODE }; - if (pxCtx.originalUuid) { - result['original_uuid'] = pxCtx.originalUuid; + if (ctx.originalUuid) { + result['original_uuid'] = ctx.originalUuid; } - if (pxCtx.originalTokenError) { - result['original_token_error'] = pxCtx.originalTokenError; + if (ctx.originalTokenError) { + result['original_token_error'] = ctx.originalTokenError; } res.json(result); diff --git a/lib/pxutil.js b/lib/pxutil.js index 8a68a126..598cf889 100644 --- a/lib/pxutil.js +++ b/lib/pxutil.js @@ -1,5 +1,7 @@ 'use strict'; +const net = require('net'); + /** * PerimeterX (http://www.perimeterx.com) NodeJS-Express SDK * Version 1.0 Published 12 May 2016 @@ -11,8 +13,7 @@ * @param {Object} headers - request headers in key value format. * @return {Array} request headers an array format. */ -function formatHeaders(headers) { - const pxConfig = require('./pxconfig').conf; +function formatHeaders(headers, sensitiveHeaders) { const retval = []; try { if (!headers || typeof headers !== 'object' || Object.keys(headers).length === 0) { @@ -20,7 +21,7 @@ function formatHeaders(headers) { } for (const header in headers) { - if (header && headers[header] && pxConfig.SENSITIVE_HEADERS.indexOf(header) === -1) { + if (header && headers[header] && sensitiveHeaders.indexOf(header) === -1) { retval.push({name: header, value: headers[header]}); } } @@ -55,12 +56,9 @@ function checkForStatic(req, exts) { * * @return {object} filtered headers. */ -function filterSensitiveHeaders(headers) { +function filterSensitiveHeaders(headers, sensitiveKeys) { try { - const pxConfig = require('./pxconfig').conf; const retval = {}; - - const sensitiveKeys = pxConfig.SENSITIVE_HEADERS; for (const key in headers) { if (sensitiveKeys.findIndex(item => key.toLowerCase() === item.toLowerCase()) === -1) { retval[key] = headers[key]; @@ -72,15 +70,14 @@ function filterSensitiveHeaders(headers) { } } -function generateProxyHeaders(headers, ip) { +function generateProxyHeaders(headers, ip, sensitiveHeaders, forwardedForHeader) { try { - const pxConfig = require('./pxconfig').conf; - const filteredHeaders = filterSensitiveHeaders(headers); - const xffHeader = Object.keys(filteredHeaders).find(item => item.toLowerCase() === pxConfig.FORWARDED_FOR_HEADER); + const filteredHeaders = filterSensitiveHeaders(headers, sensitiveHeaders); + const xffHeader = Object.keys(filteredHeaders).find(item => item.toLowerCase() === forwardedForHeader); if (xffHeader) { filteredHeaders[xffHeader] += `, ${ip}`; } else { - filteredHeaders[pxConfig.FORWARDED_FOR_HEADER] = ip; + filteredHeaders[forwardedForHeader] = ip; } return filteredHeaders; } catch(e) { @@ -165,6 +162,29 @@ function prepareCustomParams(config, dict) { } } +function extractIP(config, req) { + let ip; + if (Array.isArray(config.IP_HEADERS)) { + config.IP_HEADERS.some(ipHeader => { + try { + const headerValue = req.get(ipHeader); + if (headerValue) { + ip = headerValue; + return true; + } + } catch (e) { + config.logger.debug('Failed to use IP_HEADERS from config.'); + } + }); + } else { + ip = typeof config.GET_USER_IP === 'function' && config.GET_USER_IP(req); + } + if (ip && net.isIP(ip) > 0) { + return ip; + } + return req.ip; +} + module.exports = { formatHeaders, filterSensitiveHeaders, @@ -174,4 +194,5 @@ module.exports = { parseAction, generateProxyHeaders, prepareCustomParams, + extractIP }; diff --git a/lib/request.js b/lib/request.js index d0a8a5f6..d33d1449 100644 --- a/lib/request.js +++ b/lib/request.js @@ -1,35 +1,21 @@ -const pxLogger = require('./pxlogger'); const p = require('agent-phin').unpromisified; -const pxConfig = require('./pxconfig'); const https = require('https'); const keepAliveAgent = new https.Agent({ keepAlive: true }); -const request = { - get: (options, cb) => { - options.method = 'GET'; - return makeRequest(options, cb); - }, - post: (options, cb) => { - options.method = 'POST'; - if (!options.headers['content-type'] && !options.headers['Content-Type']) { - options.headers['content-type'] = 'application/json'; - } - return makeRequest(options, cb); - } +exports.get = (options, config, cb) => { + options.method = 'GET'; + return makeRequest(options, config, cb); }; -function makeRequest(options, cb) { - if (pxConfig.conf.agent) { - options.agent = pxConfig.conf.agent; - } else { - options.agent = keepAliveAgent; - } - - try { - p(options, cb); - } catch (e) { - pxLogger.error(`Error making request: ${e.message}`); +exports.post = (options, config, cb) => { + options.method = 'POST'; + if (!options.headers['content-type'] && !options.headers['Content-Type']) { + options.headers['content-type'] = 'application/json'; } -} + return makeRequest(options, config, cb); +}; -module.exports = request; \ No newline at end of file +function makeRequest(options, config, cb) { + options.agent = config.agent || keepAliveAgent; + p(options, cb); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..23c596c1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1290 @@ +{ + "name": "perimeterx-node-core", + "version": "1.7.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "acorn": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", + "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true + }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agent-phin": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/agent-phin/-/agent-phin-1.0.3.tgz", + "integrity": "sha512-p9aDxReY6SxaVVXYmidCdPFtz+EsRfVodiL/rbIyht9c4kjkUe+RtdMJr5eEuJKCfh3iKKwB6Ntf2UP2E0zL6w==", + "requires": { + "centra": "^2.2.1" + } + }, + "ajv": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "dev": true + }, + "centra": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.2.2.tgz", + "integrity": "sha512-7M9pOa0afioJy+h3+RvaK9BuxtJdo4/cRXoBPoClUcX1YLlaFMIxQlN/Sp4GoDEp2w17qeQ74NDuMdXPaNLToA==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es6-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.13.0.tgz", + "integrity": "sha512-nqD5WQMisciZC5EHZowejLKQjWGuFS5c70fxqSKlnDME+oz9zmE8KTlX+lHSg+/5wsC/kf9Q9eMkC8qS3oM2fg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.1.0", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.0.tgz", + "integrity": "sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA==", + "dev": true, + "requires": { + "acorn": "^6.0.2", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", + "dev": true, + "requires": { + "samsam": "1.x" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.10.0.tgz", + "integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "inquirer": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", + "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "mu2": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/mu2/-/mu2-0.5.21.tgz", + "integrity": "sha1-iIqPD9kOsc/anbgUdvbhmcyeWNM=" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "parent-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", + "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "rewire": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-2.5.2.tgz", + "integrity": "sha1-ZCfee3/u+n02QBUH62SlOFvFjcc=", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "should": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/should/-/should-8.4.0.tgz", + "integrity": "sha1-XmCInT5kS73Tl6MM00+tKPz5C8A=", + "dev": true, + "requires": { + "should-equal": "0.8.0", + "should-format": "0.3.2", + "should-type": "0.2.0" + } + }, + "should-equal": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-0.8.0.tgz", + "integrity": "sha1-o/BXMv9FusG3ukEvhAiFaBlkEpk=", + "dev": true, + "requires": { + "should-type": "0.2.0" + } + }, + "should-format": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.2.tgz", + "integrity": "sha1-pZgx4Bot3uFJkRvHFIvlyAMZ4f8=", + "dev": true, + "requires": { + "should-type": "0.2.0" + } + }, + "should-type": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.2.0.tgz", + "integrity": "sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sinon": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", + "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", + "dev": true, + "requires": { + "diff": "^3.1.0", + "formatio": "1.2.0", + "lolex": "^1.6.0", + "native-promise-only": "^0.8.1", + "path-to-regexp": "^1.7.0", + "samsam": "^1.1.3", + "text-encoding": "0.6.4", + "type-detect": "^4.0.0" + } + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", + "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", + "dev": true, + "requires": { + "ajv": "^6.9.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "string-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.0.0.tgz", + "integrity": "sha512-rr8CUxBbvOZDUvc5lNIJ+OC1nPVpz+Siw9VBtUjB9b6jZehZLFt0JMCZzShFHIsI8cbhm0EsNIfWJMFV3cu3Ew==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + } + } + } + }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + } + } +} diff --git a/package.json b/package.json index c21260f5..6c10448f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "perimeterx-node-core", - "version": "1.8.0", + "version": "2.0.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/pxapi.test.js b/test/pxapi.test.js index 896482bd..695d8a6e 100644 --- a/test/pxapi.test.js +++ b/test/pxapi.test.js @@ -6,11 +6,14 @@ const rewire = require('rewire'); const pxhttpc = require('../lib/pxhttpc'); const pxapi = rewire('../lib/pxapi'); const originalTokenValidator = require('../lib/pxoriginaltoken'); -const PxClient = rewire('../lib/pxclient'); +const PxConfig = require('../lib/pxconfig'); +const PxLogger = require('../lib/pxlogger'); describe('PX API - pxapi.js', () => { let params; let config; + let pxConfig; + let logger; let stub; beforeEach(() => { @@ -27,10 +30,11 @@ describe('PX API - pxapi.js', () => { moduleMode: 1, }; - const pxconfig = require('../lib/pxconfig'); - pxconfig.init(params, new PxClient()); - config = pxconfig.mergeDefaults(params); - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + logger = new PxLogger(); + pxConfig = new PxConfig(params, logger); + logger.init(pxConfig); + config = pxConfig.conf; + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { return callback(data); }); }); @@ -45,8 +49,8 @@ describe('PX API - pxapi.js', () => { //Using rewire to get callServer function const pxApiCallServerFunc = pxapi.__get__('callServer'); - // Prepare pxCtx - const pxCtx = { + // Prepare ctx + const ctx = { ip: '1.2.3.4', fullUrl: 'stub', vid: 'stub', @@ -61,88 +65,88 @@ describe('PX API - pxapi.js', () => { } }; - pxApiCallServerFunc(pxCtx, data => { + pxApiCallServerFunc(ctx, config, data => { data.additional.px_orig_cookie.should.equal('abc'); done(); }); }); - it('token v3 - should add originalUuid, vid and decodedOriginalToken to pxCtx when original token decryption succeeds', (done) => { - const pxCtx = { + it('token v3 - should add originalUuid, vid and decodedOriginalToken to ctx when original token decryption succeeds', (done) => { + const ctx = { cookies: { _px3: 'aaaa' }, originalToken: '68a1bf96ab3af2e0683a377d332b125dda3e195ee56cf3ce4d61b99cd0860dc6:xTMRZvJnzxM=:1000:0pjajaPCjssb2HjG2436zyFXIvIEbE87nFBrHEQPDRT7fqiQ5RA05+njsLUVpOtdJjLvWNNAlSG70DW2wqWM5VmF9UR420/wxPkx6Ebyz/L9q7Mxk5fcdF8p+dGcMc3uD7Qh8y3WiPSN389cXhfKfMttUABQYvRpOxo7rMC+ngpHEVYg+lfBZCliHB1PZKLy' }; - originalTokenValidator.evalCookie(pxCtx, config); - pxCtx.originalUuid.should.equal('09ade30a-f08b-11e7-8c3f-9a214cf093ae'); - pxCtx.vid.should.equal('0290edec-f08b-11e7-8c3f-9a214cf093ae'); - JSON.stringify(pxCtx.decodedOriginalToken).should.equal('{"a":"c","s":0,"u":"09ade30a-f08b-11e7-8c3f-9a214cf093ae","t":1830515445000,"v":"0290edec-f08b-11e7-8c3f-9a214cf093ae"}'); + originalTokenValidator.evalCookie(ctx, config); + ctx.originalUuid.should.equal('09ade30a-f08b-11e7-8c3f-9a214cf093ae'); + ctx.vid.should.equal('0290edec-f08b-11e7-8c3f-9a214cf093ae'); + JSON.stringify(ctx.decodedOriginalToken).should.equal('{"a":"c","s":0,"u":"09ade30a-f08b-11e7-8c3f-9a214cf093ae","t":1830515445000,"v":"0290edec-f08b-11e7-8c3f-9a214cf093ae"}'); done(); }); it('token v3 - should set originalTokenError to decryption_failed on original token decryption fail', (done) => { - const pxCtx = { + const ctx = { cookies: { _px3: 'aaaa' }, originalToken: 'aaaaa:bbbbb:cccc:ddddd' }; - originalTokenValidator.evalCookie(pxCtx, config); - pxCtx.originalTokenError.should.equal('decryption_failed'); + originalTokenValidator.evalCookie(ctx, config); + ctx.originalTokenError.should.equal('decryption_failed'); done(); }); it('token v3 - should set originalTokenError to validation_failed on original token validation fail', (done) => { - const pxCtx = { + const ctx = { cookies: { _px3: 'aaaa' }, originalToken: '68a1bf96ab3af2e0683a377d332b125dda3e195ee56cf3ce4d61b99cd0860dc:xTMRZvJnzxM=:1000:0pjajaPCjssb2HjG2436zyFXIvIEbE87nFBrHEQPDRT7fqiQ5RA05+njsLUVpOtdJjLvWNNAlSG70DW2wqWM5VmF9UR420/wxPkx6Ebyz/L9q7Mxk5fcdF8p+dGcMc3uD7Qh8y3WiPSN389cXhfKfMttUABQYvRpOxo7rMC+ngpHEVYg+lfBZCliHB1PZKLy' }; - originalTokenValidator.evalCookie(pxCtx, config); - pxCtx.originalUuid.should.equal('09ade30a-f08b-11e7-8c3f-9a214cf093ae'); - pxCtx.vid.should.equal('0290edec-f08b-11e7-8c3f-9a214cf093ae'); - pxCtx.originalTokenError.should.equal('validation_failed'); + originalTokenValidator.evalCookie(ctx, config); + ctx.originalUuid.should.equal('09ade30a-f08b-11e7-8c3f-9a214cf093ae'); + ctx.vid.should.equal('0290edec-f08b-11e7-8c3f-9a214cf093ae'); + ctx.originalTokenError.should.equal('validation_failed'); done(); }); - it('token v1 - should add originalUuid, vid and decodedOriginalToken to pxCtx when original token decryption succeeds', (done) => { - const pxCtx = { + it('token v1 - should add originalUuid, vid and decodedOriginalToken to ctx when original token decryption succeeds', (done) => { + const ctx = { cookies: { _px: 'aaaa' }, originalToken: 'Gy9z3mQPYNE=:1000:I7A44BXmO5IlgqhXLM5Mmuq4/jESNgse51Zj/l4bpkAaymDQzcrUMHBofVQ8Q9IYfon3bVQn7gHA124xunjlSlPMlj133wuFBzt7r/yJKpcTEex5WBxynCQAXXx8tymeO1gWXLmPchrV93ysxPl/AeV2/ofVN3YzUR/0PQbXB2fzxkPc5bMPdxLMJCrgLtR4msoMGvg9qaiufMFDWWzah1kvUq1Kvrlk3UQm0y6UU1j6GoLHkTSnDBTg3GexETotOoUkM5FYMPZm8TxK0as+mg==' }; - originalTokenValidator.evalCookie(pxCtx, config); - pxCtx.originalUuid.should.equal('09ade30a-f08b-11e7-8c3f-9a214cf093ae'); - pxCtx.vid.should.equal('0290edec-f08b-11e7-8c3f-9a214cf093ae'); - JSON.stringify(pxCtx.decodedOriginalToken).should.equal('{"h":"aa2341380b7c67ee0ed5c2f7d4facf03847d7dcb4540aab021654361d3dcade4","s":{"a":0,"b":0},"u":"09ade30a-f08b-11e7-8c3f-9a214cf093ae","t":1830515445000,"v":"0290edec-f08b-11e7-8c3f-9a214cf093ae"}'); + originalTokenValidator.evalCookie(ctx, config); + ctx.originalUuid.should.equal('09ade30a-f08b-11e7-8c3f-9a214cf093ae'); + ctx.vid.should.equal('0290edec-f08b-11e7-8c3f-9a214cf093ae'); + JSON.stringify(ctx.decodedOriginalToken).should.equal('{"h":"aa2341380b7c67ee0ed5c2f7d4facf03847d7dcb4540aab021654361d3dcade4","s":{"a":0,"b":0},"u":"09ade30a-f08b-11e7-8c3f-9a214cf093ae","t":1830515445000,"v":"0290edec-f08b-11e7-8c3f-9a214cf093ae"}'); done(); }); it('token v1 - should set originalTokenError to decryption_failed on original token decryption fail', (done) => { - const pxCtx = { + const ctx = { cookies: { _px: 'aaaa' }, originalToken: 'aaaaa:bbbbb:cccc:ddddd' }; - originalTokenValidator.evalCookie(pxCtx, config); - pxCtx.originalTokenError.should.equal('decryption_failed'); + originalTokenValidator.evalCookie(ctx, config); + ctx.originalTokenError.should.equal('decryption_failed'); done(); }); it('should fail with exception and set originalTokenError to decryption_failed', (done) => { - const pxCtx = { + const ctx = { cookies: { _px: 'aaaaa' }, originalToken: '' }; - originalTokenValidator.evalCookie(pxCtx, config); - pxCtx.originalTokenError.should.equal('decryption_failed'); + originalTokenValidator.evalCookie(ctx, config); + ctx.originalTokenError.should.equal('decryption_failed'); done(); }); }); \ No newline at end of file diff --git a/test/pxconfig.test.js b/test/pxconfig.test.js index 9f46d538..361964e5 100644 --- a/test/pxconfig.test.js +++ b/test/pxconfig.test.js @@ -2,11 +2,12 @@ const should = require('should'); const rewire = require('rewire'); -const PxClient = rewire('../lib/pxclient'); +const PxConfig = require('../lib/pxconfig'); +const PxLogger = require('../lib/pxlogger'); describe('PX Configurations - pxconfig.js', () => { - let pxconfig; let params; + const logger = new PxLogger(); beforeEach(() => { params = { @@ -21,21 +22,20 @@ describe('PX Configurations - pxconfig.js', () => { customRequestHandler: null }; - pxconfig = require('../lib/pxconfig'); }); it('should set baseUrl to sapi-.perimeterx.net', (done) => { params.pxAppId = 'PXJWbMQarF'; - pxconfig.init(params, new PxClient()); - const conf = pxconfig.conf; + const pxConfig = new PxConfig(params, logger); + const conf = pxConfig.conf; conf.SERVER_HOST.should.be.exactly(`sapi-${params.pxAppId.toLowerCase()}.perimeterx.net`); done(); }); it('blocking score should be 80', (done) => { params.blockingScore = 80; - pxconfig.init(params, new PxClient()); - const conf = pxconfig.conf; + const pxConfig = new PxConfig(params, logger); + const conf = pxConfig.conf; conf.BLOCKING_SCORE.should.be.exactly(80); done(); }); @@ -45,8 +45,8 @@ describe('PX Configurations - pxconfig.js', () => { return '1.2.3.4'; }; - pxconfig.init(params, new PxClient()); - const conf = pxconfig.conf; + const pxConfig = new PxConfig(params, logger); + const conf = pxConfig.conf; conf.GET_USER_IP().should.be.exactly('1.2.3.4'); done(); }); @@ -56,51 +56,51 @@ describe('PX Configurations - pxconfig.js', () => { return 'Blocked'; }; - pxconfig.init(params, new PxClient()); - const conf = pxconfig.conf; + const pxConfig = new PxConfig(params, logger); + const conf = pxConfig.conf; conf.CUSTOM_REQUEST_HANDLER().should.be.exactly('Blocked'); done(); }); it('should set enableModule to false', () => { params.enableModule = false; - pxconfig.init(params, new PxClient()); - const conf = pxconfig.conf; + const pxConfig = new PxConfig(params, logger); + const conf = pxConfig.conf; conf.ENABLE_MODULE.should.be.exactly(false); }); it('should set sendPageActivities to false', () => { params.sendPageActivities = false; - pxconfig.init(params, new PxClient()); - const conf = pxconfig.conf; + const pxConfig = new PxConfig(params, logger); + const conf = pxConfig.conf; conf.SEND_PAGE_ACTIVITIES.should.be.exactly(false); }); it('should set debugMode to true', () => { params.sendPageActivities = true; - pxconfig.init(params, new PxClient()); - const conf = pxconfig.conf; + const pxConfig = new PxConfig(params, logger); + const conf = pxConfig.conf; conf.DEBUG_MODE.should.be.exactly(true); }); it('customLogo should be overridden', () => { params.customLogo = 'http://www.google.com/logo.jpg'; - pxconfig.init(params, new PxClient()); - const conf = pxconfig.conf; + const pxConfig = new PxConfig(params, logger); + const conf = pxConfig.conf; conf.CUSTOM_LOGO.should.be.exactly('http://www.google.com/logo.jpg'); }); it('jsRef should be overridden', () => { params.jsRef = ['http://www.google.com/script.js']; - pxconfig.init(params, new PxClient()); - const conf = pxconfig.conf; + const pxConfig = new PxConfig(params, logger); + const conf = pxConfig.conf; conf.JS_REF[0].should.equal('http://www.google.com/script.js'); }); it('cssRef should be overridden', () => { params.cssRef = ['http://www.google.com/stylesheet.css']; - pxconfig.init(params, new PxClient()); - const conf = pxconfig.conf; + const pxConfig = new PxConfig(params, logger); + const conf = pxConfig.conf; conf.CSS_REF[0].should.equal('http://www.google.com/stylesheet.css'); }); }); \ No newline at end of file diff --git a/test/pxenforcer.test.js b/test/pxenforcer.test.js index 0698159f..c0a057e7 100644 --- a/test/pxenforcer.test.js +++ b/test/pxenforcer.test.js @@ -9,7 +9,7 @@ const PxClient = rewire('../lib/pxclient'); const PxEnforcer = require('../lib/pxenforcer'); describe('PX Enforcer - pxenforcer.js', () => { - let params, enforcer, req, stub; + let params, enforcer, req, stub, pxClient; beforeEach(() => { params = { @@ -36,6 +36,8 @@ describe('PX Enforcer - pxenforcer.js', () => { req.get = (key) => { return req.headers[key] || ''; }; + + pxClient = new PxClient(); }); afterEach(() => { @@ -43,11 +45,11 @@ describe('PX Enforcer - pxenforcer.js', () => { }); it('enforces a call in a disabled module', (done) => { - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { return callback ? callback(null, data) : ''; }); params.enableModule = false; - enforcer = new PxEnforcer(params, new PxClient()); + enforcer = new PxEnforcer(params, pxClient); enforcer.enforce(req, null, (response) => { (response === undefined).should.equal(true); @@ -56,10 +58,10 @@ describe('PX Enforcer - pxenforcer.js', () => { }); it('enforces a call in an enabled module', (done) => { - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { return callback ? callback(null, data) : ''; }); - enforcer = new PxEnforcer(params, new PxClient()); + enforcer = new PxEnforcer(params, pxClient); enforcer.enforce(req, null, (response) => { (response === undefined).should.equal(true); @@ -68,14 +70,14 @@ describe('PX Enforcer - pxenforcer.js', () => { }); it('uses first party to get client', (done) => { - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { return callback ? callback(null, data) : ''; }); - const reqStub = sinon.stub(request, 'get').callsFake((data, callback) => { + const reqStub = sinon.stub(request, 'get').callsFake((data, config, callback) => { callback(null, {headers: {'x-px-johnny': '1'}, body: 'hello buddy', proxy:''}); }); req.originalUrl = '/_APP_ID/init.js'; - enforcer = new PxEnforcer(params, new PxClient()); + enforcer = new PxEnforcer(params, pxClient); enforcer.enforce(req, null, (error, response) => { (response === undefined).should.equal(false); response.body.should.equal('hello buddy'); @@ -86,16 +88,16 @@ describe('PX Enforcer - pxenforcer.js', () => { }); it('uses first party for xhr post request', (done) => { - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { return callback ? callback(null, data) : ''; }); - const reqStub = sinon.stub(request, 'post').callsFake((data, callback) => { + const reqStub = sinon.stub(request, 'post').callsFake((data, config, callback) => { callback(null, {headers: {'x-px-johnny': '1'}, body:'hello buddy'}); }); req.originalUrl = '/_APP_ID/xhr/something'; req.method = 'POST'; req.body = 'test'; - enforcer = new PxEnforcer(params, new PxClient()); + enforcer = new PxEnforcer(params, pxClient); enforcer.enforce(req, null, (error, response) => { (response === undefined).should.equal(false); response.body.should.equal('hello buddy'); @@ -106,16 +108,16 @@ describe('PX Enforcer - pxenforcer.js', () => { }); it('uses first party for xhr get request', (done) => { - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { return callback ? callback(null, data) : ''; }); - const reqStub = sinon.stub(request, 'get').callsFake((data, callback) => { + const reqStub = sinon.stub(request, 'get').callsFake((data, config, callback) => { callback(null, {headers: {'x-px-johnny': '1'}, body: 'hello buddy'}); }); req.originalUrl = '/_APP_ID/xhr/something'; req.method = 'GET'; req.body = 'test'; - enforcer = new PxEnforcer(params, new PxClient()); + enforcer = new PxEnforcer(params, pxClient); enforcer.enforce(req, null, (error, response) => { (response === undefined).should.equal(false); response.body.should.equal('hello buddy'); @@ -126,17 +128,17 @@ describe('PX Enforcer - pxenforcer.js', () => { }); it('uses first party with pxvid cookie', (done) => { - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { return callback ? callback(null, data) : ''; }); - const reqStub = sinon.stub(request, 'post').callsFake((data, callback) => { + const reqStub = sinon.stub(request, 'post').callsFake((data, config, callback) => { callback(null, {headers: {'x-px-johnny': '1'}, body:'hello buddy'}); }); req.originalUrl = '/_APP_ID/xhr/something'; req.method = 'POST'; req.cookies['_pxvid'] = 'abab-123'; req.body = 'test'; - enforcer = new PxEnforcer(params, new PxClient()); + enforcer = new PxEnforcer(params, pxClient); enforcer.enforce(req, null, (error, response) => { (response === undefined).should.equal(false); response.body.should.equal('hello buddy'); @@ -147,16 +149,16 @@ 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, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { return callback ? callback(null, data) : ''; }); - const reqStub = sinon.stub(request, 'post').callsFake((data, callback) => { + const reqStub = sinon.stub(request, 'post').callsFake((data, config, callback) => { callback(null, {headers: {'x-px-johnny': '1'}, body:'hello buddy'}); }); req.originalUrl = '/_APP_ID/xhr/something'; req.method = 'POST'; req.body = {key: 'value', anotherKey: 'anotherValue'}; - enforcer = new PxEnforcer(params, new PxClient()); + enforcer = new PxEnforcer(params, pxClient); enforcer.enforce(req, null, (error, response) => { (response === undefined).should.equal(false); response.body.should.equal('hello buddy'); @@ -167,7 +169,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, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { data.score = 100; data.action = 'b'; return callback ? callback(null, data) : ''; @@ -176,13 +178,13 @@ describe('PX Enforcer - pxenforcer.js', () => { moduleMode: 1, firstPartyEnabled: true }, params); - const reqStub = sinon.stub(request, 'post').callsFake((data, callback) => { + const reqStub = sinon.stub(request, 'post').callsFake((data, config, callback) => { callback(null, {headers: {'x-px-johnny': '1'}, body:'hello buddy'}); }); req.headers = {'x-px-authorization': '3:some-fake-cookie'}; req.method = 'POST'; req.body = {key: 'value', anotherKey: 'anotherValue'}; - enforcer = new PxEnforcer(curParams, new PxClient()); + enforcer = new PxEnforcer(curParams, pxClient); enforcer.enforce(req, null, (error, response) => { (response === undefined).should.equal(false); response.body.action.should.equal('block'); @@ -191,7 +193,7 @@ describe('PX Enforcer - pxenforcer.js', () => { }); }); it('should bypass monitor mode by header', (done) => { - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { data.score = 100; data.action = 'b'; return callback ? callback(null, data) : ''; @@ -208,7 +210,7 @@ describe('PX Enforcer - pxenforcer.js', () => { }); req.method = 'POST'; req.body = {key: 'value', anotherKey: 'anotherValue'}; - enforcer = new PxEnforcer(curParams, new PxClient()); + enforcer = new PxEnforcer(curParams, pxClient); enforcer.enforce(req, null, (error, response) => { (response === undefined).should.equal(false); (response.body.indexOf('Please verify you are a human') > -1).should.equal(true); @@ -217,7 +219,7 @@ describe('PX Enforcer - pxenforcer.js', () => { }); }); it('should ignore bypass monitor mode by header', (done) => { - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { data.score = 100; data.action = 'b'; return callback ? callback(null, data) : ''; @@ -234,7 +236,7 @@ describe('PX Enforcer - pxenforcer.js', () => { }); req.method = 'POST'; req.body = {key: 'value', anotherKey: 'anotherValue'}; - enforcer = new PxEnforcer(curParams, new PxClient()); + enforcer = new PxEnforcer(curParams, pxClient); enforcer.enforce(req, null, (error, response) => { (response === undefined).should.equal(true); reqStub.restore(); @@ -242,7 +244,7 @@ describe('PX Enforcer - pxenforcer.js', () => { }); }); it('should ignore bypass monitor header as its not present', (done) => { - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { data.score = 100; data.action = 'b'; return callback ? callback(null, data) : ''; @@ -256,7 +258,7 @@ describe('PX Enforcer - pxenforcer.js', () => { }); req.method = 'POST'; req.body = {key: 'value', anotherKey: 'anotherValue'}; - enforcer = new PxEnforcer(curParams, new PxClient()); + enforcer = new PxEnforcer(curParams, pxClient); enforcer.enforce(req, null, (error, response) => { (response === undefined).should.equal(true); reqStub.restore(); @@ -264,7 +266,7 @@ describe('PX Enforcer - pxenforcer.js', () => { }); }); it('should ignore bypass monitor header as cookie is valid', (done) => { - stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, callback) => { + stub = sinon.stub(pxhttpc, 'callServer').callsFake((data, headers, uri, callType, config, callback) => { data.score = 0; data.action = 'b'; return callback ? callback(null, data) : ''; @@ -281,7 +283,7 @@ describe('PX Enforcer - pxenforcer.js', () => { }); req.method = 'POST'; req.body = {key: 'value', anotherKey: 'anotherValue'}; - enforcer = new PxEnforcer(curParams, new PxClient()); + enforcer = new PxEnforcer(curParams, pxClient); enforcer.enforce(req, null, (error, response) => { (response === undefined).should.equal(true); reqStub.restore(); @@ -289,4 +291,3 @@ describe('PX Enforcer - pxenforcer.js', () => { }); }); }); - diff --git a/test/pxutils.test.js b/test/pxutils.test.js index 82205f9a..85b4d7b1 100644 --- a/test/pxutils.test.js +++ b/test/pxutils.test.js @@ -3,10 +3,11 @@ const should = require('should'); const rewire = require('rewire'); const pxutil = require('../lib/pxutil'); -const PxClient = rewire('../lib/pxclient'); +const PxConfig = require('../lib/pxconfig'); +const PxLogger = require('../lib/pxlogger'); describe('PX Utils - pxutils.js', () => { - let pxconfig; + let pxConfig; let params; beforeEach(() => { @@ -22,12 +23,12 @@ describe('PX Utils - pxutils.js', () => { enrichCustomParameters: enrichCustomParameters }; - pxconfig = require('../lib/pxconfig'); - pxconfig.init(params, new PxClient()); + const logger = new PxLogger(); + pxConfig = new PxConfig(params, logger); }); it('should generate headers array from headers object', (done) => { - const formattedHeaders = pxutil.formatHeaders({K: 'v'}); + const formattedHeaders = pxutil.formatHeaders({K: 'v'}, pxConfig.conf.SENSITIVE_HEADERS); (Object.prototype.toString.call(formattedHeaders)).should.be.exactly('[object Array]'); formattedHeaders[0]['name'].should.be.exactly('K'); formattedHeaders[0]['value'].should.be.exactly('v'); @@ -36,7 +37,7 @@ describe('PX Utils - pxutils.js', () => { it('should receive custom params function and custom params object and add only 2 of them', (done) => { const dict = {}; - pxutil.prepareCustomParams(pxconfig.conf, dict); + pxutil.prepareCustomParams(pxConfig.conf, dict); dict['custom_param1'].should.be.exactly('1'); dict['custom_param2'].should.be.exactly('2'); dict['custom_param10'].should.be.exactly('10');