Skip to content

Commit

Permalink
Merge branch 'dev' into fix/remove-header-based-logger
Browse files Browse the repository at this point in the history
  • Loading branch information
chen-zimmer-px authored Jan 17, 2024
2 parents c36a2d0 + 69478df commit c96f1fe
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 29 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [3.14.0] - 2024-01-11

### Added
- Support for url decode reserved characters feature

## [3.13.0] - 2023-12-21

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[PerimeterX](http://www.perimeterx.com) Shared base for NodeJS enforcers
=============================================================

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

Expand Down
3 changes: 3 additions & 0 deletions lib/pxapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ function buildRequestData(ctx, config) {
if (ctx.serverInfoRegion) {
data.additional['server_info_region'] = ctx.serverInfoRegion;
}
if (ctx.isRawUrlDifferentFromNormalizedUrl) {
data.additional['raw_url'] = ctx.rawUrl;
}

if (ctx.additionalFields && ctx.additionalFields.loginCredentials) {
const { loginCredentials } = ctx.additionalFields;
Expand Down
6 changes: 4 additions & 2 deletions lib/pxconfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ class PxConfig {
['JWT_HEADER_USER_ID_FIELD_NAME', 'px_jwt_header_user_id_field_name'],
['JWT_HEADER_ADDITIONAL_FIELD_NAMES', 'px_jwt_header_additional_field_names'],
['CUSTOM_IS_SENSITIVE_REQUEST', 'px_custom_is_sensitive_request'],
['FIRST_PARTY_TIMEOUT_MS', 'px_first_party_timeout_ms']
['FIRST_PARTY_TIMEOUT_MS', 'px_first_party_timeout_ms'],
['URL_DECODE_RESERVED_CHARACTERS', 'px_url_decode_reserved_characters']
];

configKeyMapping.forEach(([targetKey, sourceKey]) => {
Expand Down Expand Up @@ -363,7 +364,8 @@ function pxDefaultConfig() {
JWT_HEADER_USER_ID_FIELD_NAME: '',
JWT_HEADER_ADDITIONAL_FIELD_NAMES: [],
CUSTOM_IS_SENSITIVE_REQUEST: '',
FIRST_PARTY_TIMEOUT_MS: 4000
FIRST_PARTY_TIMEOUT_MS: 4000,
URL_DECODE_RESERVED_CHARACTERS: false
};
}

Expand Down
4 changes: 3 additions & 1 deletion lib/pxcontext.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ class PxContext {
this.hostname = req.hostname || req.get('host');
this.userAgent = userAgent;
this.uri = req.originalUrl || '/';
this.fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
this.rawUrl = req.originalFullUrl;
this.isRawUrlDifferentFromNormalizedUrl = req.originalFullUrl !== req.requestNormalizedUrl;
this.fullUrl = req.requestNormalizedUrl;
this.originalRequest = req.originalRequest || req;
this.httpVersion = req.httpVersion || '';
this.httpMethod = req.method || '';
Expand Down
42 changes: 32 additions & 10 deletions lib/pxenforcer.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,36 @@ class PxEnforcer {
}

enforce(req, res, cb) {
const requestUrl = req.originalUrl;
if (!this._config.ENABLE_MODULE) {
this.logger.debug('Request will not be verified, module is disabled');
return cb();
}
const fullUrlFromRequest = pxUtil.getFullUrlFromRequest(req);
req.originalFullUrl = req.originalFullUrl || fullUrlFromRequest;
const normalizedUrl = this._normalizeUrl(fullUrlFromRequest);

req['requestNormalizedUrl'] = normalizedUrl.href;

const userAgent = req.get('user-agent') || '';
const ipAddress = pxUtil.extractIP(this._config, req);

this.logger.debug('Starting request verification');

if (requestUrl.startsWith(`/${this.reversePrefix}${this._config.FIRST_PARTY_VENDOR_PATH}`)) {
if (req.originalUrl.startsWith(`/${this.reversePrefix}${this._config.FIRST_PARTY_VENDOR_PATH}`)) {
// reverse proxy client
return pxProxy.getClient(req, this._config, ipAddress, cb);
}

if (requestUrl.startsWith(`/${this.reversePrefix}${this._config.FIRST_PARTY_XHR_PATH}`)) {
if (req.originalUrl.startsWith(`/${this.reversePrefix}${this._config.FIRST_PARTY_XHR_PATH}`)) {
//reverse proxy xhr
return pxProxy.sendXHR(req, this._config, ipAddress, this.reversePrefix, cb);
}

if (requestUrl.startsWith(`/${this.reversePrefix}${this._config.FIRST_PARTY_CAPTCHA_PATH}`)) {
if (req.originalUrl.startsWith(`/${this.reversePrefix}${this._config.FIRST_PARTY_CAPTCHA_PATH}`)) {
// reverse proxy captcha
return pxProxy.getCaptcha(req, this._config, ipAddress, this.reversePrefix, cb);
}

if (!this._config.ENABLE_MODULE) {
this.logger.debug('Request will not be verified, module is disabled');
return cb();
}

if (this._config.FILTER_BY_METHOD.includes(req.method.toUpperCase())) {
this.logger.debug(`Skipping verification for filtered method ${req.method}`);
return cb();
Expand Down Expand Up @@ -150,6 +154,19 @@ class PxEnforcer {
}
}

_normalizeUrl(originalUrl) {
let normalizedUrl = new URL(originalUrl);
if (this._config.URL_DECODE_RESERVED_CHARACTERS) {
try {
normalizedUrl = new URL(`${normalizedUrl.origin}${decodeURIComponent(normalizedUrl.pathname)}${normalizedUrl.search}`);
} catch (e) {
this.logger.debug(`unable to URL decode reserved characters: ${e}`);
}
}
normalizedUrl.pathname = normalizedUrl.pathname.replace(/\/+$/, '').replace(/\/+/g, '/');
return normalizedUrl;
}

_tryModifyContext(ctx, req) {
if (this._config.MODIFY_CONTEXT && typeof this._config.MODIFY_CONTEXT === 'function') {
try {
Expand Down Expand Up @@ -406,7 +423,7 @@ class PxEnforcer {
}

getActivityDetails(ctx) {
return {
const details = {
client_uuid: ctx.uuid,
http_version: ctx.httpVersion,
risk_rtt: ctx.riskRtt,
Expand All @@ -416,6 +433,11 @@ class PxEnforcer {
request_cookie_names: ctx.requestCookieNames,
enforcer_start_time: ctx.enforcerStartTime,
};

if (ctx.isRawUrlDifferentFromNormalizedUrl) {
details.raw_url = ctx.rawUrl;
}
return details;
}

/**
Expand Down
8 changes: 7 additions & 1 deletion lib/pxutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const { EMAIL_ADDRESS_REGEX, HASH_ALGORITHM } = require('./utils/constants');
* @param {Object} headers - request headers in key value format.
* @return {Array} request headers an array format.
*/

function getFullUrlFromRequest(req) {
return `${req.protocol}://${req.get('host')}${req.originalUrl}`;
}

function formatHeaders(headers, sensitiveHeaders) {
const retval = [];
try {
Expand Down Expand Up @@ -407,5 +412,6 @@ module.exports = {
isEmailAddress,
isGraphql,
tryOrNull,
appendContentType
appendContentType,
getFullUrlFromRequest
};
29 changes: 16 additions & 13 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "perimeterx-node-core",
"version": "3.13.0",
"version": "3.14.0",
"description": "PerimeterX NodeJS shared core for various applications to monitor and block traffic according to PerimeterX risk score",
"main": "index.js",
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions test/pxcors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ describe('PX Cors - pxCors.js', () => {
req.protocol = 'http';
req.ip = '1.2.3.4';
req.hostname = 'example.com';
req.host = 'example.com';
req.get = (key) => {
if (key === 'host') {
return req.host;
}
return req.headers[key] || '';
};

Expand Down
3 changes: 3 additions & 0 deletions test/pxenforcer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ describe('PX Enforcer - pxenforcer.js', () => {
req.ip = '1.2.3.4';
req.hostname = 'example.com';
req.get = (key) => {
if (key === 'host') {
return req.host;
}
return req.headers[key] || '';
};

Expand Down

0 comments on commit c96f1fe

Please sign in to comment.