Skip to content

Commit 60200c6

Browse files
Merge pull request #279 from PerimeterX/release/v3.10.0
Release/v3.10.0 ->master
2 parents 1f84b95 + 1e70847 commit 60200c6

File tree

10 files changed

+54
-15
lines changed

10 files changed

+54
-15
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [3.10.0] - 2023-03-28
9+
10+
### Added
11+
- Support for handling graphQL requests with empty query field
12+
- Support custom is sensitive request via function
13+
814
## [3.9.0] - 2023-01-29
915

1016
### Added

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[PerimeterX](http://www.perimeterx.com) Shared base for NodeJS enforcers
77
=============================================================
88

9-
> Latest stable version: [v3.9.0](https://www.npmjs.com/package/perimeterx-node-core)
9+
> Latest stable version: [v3.10.0](https://www.npmjs.com/package/perimeterx-node-core)
1010
1111
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.
1212

lib/pxconfig.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class PxConfig {
102102
['JWT_HEADER_NAME', 'px_jwt_header_name'],
103103
['JWT_HEADER_USER_ID_FIELD_NAME', 'px_jwt_header_user_id_field_name'],
104104
['JWT_HEADER_ADDITIONAL_FIELD_NAMES', 'px_jwt_header_additional_field_names'],
105+
['CUSTOM_IS_SENSITIVE_REQUEST', 'px_custom_is_sensitive_request']
105106
];
106107

107108
configKeyMapping.forEach(([targetKey, sourceKey]) => {
@@ -176,7 +177,8 @@ class PxConfig {
176177
userInput === 'px_login_successful_custom_callback' ||
177178
userInput === 'px_modify_context' ||
178179
userInput === 'px_cors_create_custom_block_response_headers' ||
179-
userInput === 'px_cors_custom_preflight_handler'
180+
userInput === 'px_cors_custom_preflight_handler' ||
181+
userInput === 'px_custom_is_sensitive_request'
180182
) {
181183
if (typeof params[userInput] === 'function') {
182184
return params[userInput];
@@ -359,6 +361,7 @@ function pxDefaultConfig() {
359361
JWT_HEADER_NAME: '',
360362
JWT_HEADER_USER_ID_FIELD_NAME: '',
361363
JWT_HEADER_ADDITIONAL_FIELD_NAMES: [],
364+
CUSTOM_IS_SENSITIVE_REQUEST: ''
362365
};
363366
}
364367

@@ -431,6 +434,7 @@ const allowedConfigKeys = [
431434
'px_jwt_header_name',
432435
'px_jwt_header_user_id_field_name',
433436
'px_jwt_header_additional_field_names',
437+
'px_custom_is_sensitive_request'
434438
];
435439

436440
module.exports = PxConfig;

lib/pxcontext.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class PxContext {
2323
this.originalRequest = req.originalRequest || req;
2424
this.httpVersion = req.httpVersion || '';
2525
this.httpMethod = req.method || '';
26-
this.sensitiveRoute = this.isSpecialRoute(config.SENSITIVE_ROUTES, this.uri);
26+
this.sensitiveRequest = () => this.isSensitiveRequest(req, config);
2727
this.enforcedRoute = this.isSpecialRoute(config.ENFORCED_ROUTES, this.uri);
2828
this.whitelistRoute = this.isSpecialRoute(config.WHITELIST_ROUTES, this.uri);
2929
this.monitoredRoute = !this.enforcedRoute && this.isSpecialRoute(config.MONITORED_ROUTES, this.uri);
@@ -85,6 +85,25 @@ class PxContext {
8585
}
8686
}
8787

88+
isSensitiveRequest(request, config) {
89+
return this.isSpecialRoute(config.SENSITIVE_ROUTES, this.uri) ||
90+
this.isCustomSensitiveRequest(request, config);
91+
}
92+
93+
isCustomSensitiveRequest(request, config) {
94+
const customIsSensitiveRequest = config.CUSTOM_IS_SENSITIVE_REQUEST;
95+
try {
96+
if (customIsSensitiveRequest && customIsSensitiveRequest(request)) {
97+
config.logger.debug('Custom sensitive request matched');
98+
return true;
99+
}
100+
} catch (err) {
101+
config.logger.debug(`Caught exception on custom sensitive request function: ${err}`);
102+
}
103+
104+
return false;
105+
}
106+
88107
getGraphqlDataFromBody(body) {
89108
let jsonBody = null;
90109
if (typeof body === 'string') {

lib/pxcookie.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function evalCookie(ctx, config) {
8181
return ScoreEvaluateAction.COOKIE_INVALID;
8282
}
8383

84-
if (ctx.sensitiveRoute) {
84+
if (ctx.sensitiveRoute()) {
8585
config.logger.debug(`Sensitive route match, sending Risk API. path: ${ctx.uri}`);
8686
ctx.s2sCallReason = 'sensitive_route';
8787
return ScoreEvaluateAction.SENSITIVE_ROUTE;

lib/pxutil.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,10 @@ function isGraphql(req, config) {
288288
// query: string (not null)
289289
// output: Record [ OperationName -> OperationType ]
290290
function parseGraphqlBody(query) {
291+
if (!query) {
292+
return null;
293+
}
294+
291295
const pattern = /\s*(query|mutation|subscription)\s+(\w+)/gm;
292296
let match;
293297
const ret = {};
@@ -322,25 +326,22 @@ function isSensitiveGraphqlOperation(graphqlData, config) {
322326
// graphqlBodyObject: {query: string?, operationName: string?, variables: any[]?}
323327
// output: GraphqlData?
324328
function getGraphqlData(graphqlBodyObject) {
325-
if (!graphqlBodyObject || !graphqlBodyObject.query) {
329+
if (!graphqlBodyObject) {
326330
return null;
327331
}
328332

329333
const parsedData = parseGraphqlBody(graphqlBodyObject.query);
330-
if (!parsedData) {
331-
return null;
332-
}
333334

334335
const selectedOperationName =
335336
graphqlBodyObject['operationName'] || (Object.keys(parsedData).length === 1 && Object.keys(parsedData)[0]);
336337

337-
if (!selectedOperationName || !parsedData[selectedOperationName]) {
338+
if (!selectedOperationName || (parsedData && !parsedData[selectedOperationName])) {
338339
return null;
339340
}
340341

341342
const variables = extractVariables(graphqlBodyObject.variables);
342343

343-
return new GraphqlData(parsedData[selectedOperationName], selectedOperationName, variables);
344+
return new GraphqlData(parsedData && parsedData[selectedOperationName], selectedOperationName, variables);
344345
}
345346

346347
// input: object representing variables

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "perimeterx-node-core",
3-
"version": "3.9.0",
3+
"version": "3.10.0",
44
"description": "PerimeterX NodeJS shared core for various applications to monitor and block traffic according to PerimeterX risk score",
55
"main": "index.js",
66
"scripts": {

test/graphql.test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ describe('Graphql Testing', () => {
3030
graphqlData.type.should.be.exactly('query');
3131
});
3232

33+
it('should extract operation name if !query', () => {
34+
const gqlObj = {
35+
operationName: 'q1',
36+
};
37+
38+
const graphqlData = pxutil.getGraphqlData(gqlObj);
39+
graphqlData.name.should.be.exactly('q1');
40+
});
41+
3342
it('extract with many queries', () => {
3443
const gqlObj = {
3544
query: 'query q1 { \n abc \n }\nmutation q2 {\n def\n }',

test/pxenforcer.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ describe('PX Enforcer - pxenforcer.js', () => {
847847
return callback ? callback(null, data) : '';
848848
});
849849

850-
const modifyCtx = sinon.stub().callsFake((ctx) => ctx.sensitiveRoute = true);
850+
const modifyCtx = sinon.stub().callsFake((ctx) => ctx.sensitiveRequest = true);
851851
const curParams = {
852852
...params,
853853
px_modify_context: modifyCtx,
@@ -857,7 +857,7 @@ describe('PX Enforcer - pxenforcer.js', () => {
857857
enforcer = new pxenforcer(curParams, pxClient);
858858
enforcer.enforce(req, null, () => {
859859
(modifyCtx.calledOnce).should.equal(true);
860-
(req.locals.pxCtx.sensitiveRoute).should.equal(true);
860+
(req.locals.pxCtx.sensitiveRequest).should.equal(true);
861861
done();
862862
});
863863
});

0 commit comments

Comments
 (0)