From 5f8358bd2ced22203007b4ebd272e8d5af72783f Mon Sep 17 00:00:00 2001 From: Johnny Tordgeman Date: Tue, 24 Jan 2023 14:02:25 +0200 Subject: [PATCH 1/7] Added support for AD --- lib/pxapi.js | 29 +++++++++++++++++++++-- lib/pxclient.js | 32 ++++++++++++++++++-------- lib/pxconfig.js | 18 +++++++++++++++ lib/pxcontext.js | 13 +++++++++-- lib/pxjwt.js | 52 ++++++++++++++++++++++++++++++++++++++++++ lib/pxutil.js | 1 + lib/utils/constants.js | 14 +++++++++--- 7 files changed, 143 insertions(+), 16 deletions(-) create mode 100644 lib/pxjwt.js diff --git a/lib/pxapi.js b/lib/pxapi.js index cf959292..5e9d7c44 100644 --- a/lib/pxapi.js +++ b/lib/pxapi.js @@ -7,7 +7,16 @@ const { ModuleMode } = require('./enums/ModuleMode'); const PassReason = require('./enums/PassReason'); const ScoreEvaluateAction = require('./enums/ScoreEvaluateAction'); const S2SErrorReason = require('./enums/S2SErrorReason'); -const { CI_USERNAME_FIELD, CI_PASSWORD_FIELD, CI_VERSION_FIELD, CI_SSO_STEP_FIELD, GQL_OPERATION_TYPE_FIELD, GQL_OPERATION_NAME_FIELD } = require('./utils/constants'); +const { + CI_USERNAME_FIELD, + CI_PASSWORD_FIELD, + CI_VERSION_FIELD, + CI_SSO_STEP_FIELD, + GQL_OPERATION_TYPE_FIELD, + GQL_OPERATION_NAME_FIELD, + JWT_ADDITIONAL_FIELDS_FIELD_NAME, + APP_USER_ID_FIELD_NAME, +} = require('./utils/constants'); const { CIVersion } = require('./enums/CIVersion'); exports.evalByServerCall = evalByServerCall; @@ -77,11 +86,27 @@ function buildRequestData(ctx, config) { } } + if (ctx.jwt) { + const { userID, additionalFields } = ctx.jwt; + + if (userID) { + data.additional[APP_USER_ID_FIELD_NAME] = userID; + } + + if (additionalFields) { + data.additional[JWT_ADDITIONAL_FIELDS_FIELD_NAME] = additionalFields; + } + } + if (ctx.s2sCallReason === 'cookie_decryption_failed') { data.additional.px_orig_cookie = ctx.getCookie(); //No need strigify, already a string } - if (ctx.s2sCallReason === 'cookie_expired' || ctx.s2sCallReason === 'cookie_validation_failed' || ctx.s2sCallReason === 'sensitive_route') { + if ( + ctx.s2sCallReason === 'cookie_expired' || + ctx.s2sCallReason === 'cookie_validation_failed' || + ctx.s2sCallReason === 'sensitive_route' + ) { data.additional.px_cookie = JSON.stringify(ctx.decodedCookie); } diff --git a/lib/pxclient.js b/lib/pxclient.js index 47548ac0..0b56161d 100644 --- a/lib/pxclient.js +++ b/lib/pxclient.js @@ -10,7 +10,9 @@ const { CI_RAW_USERNAME_FIELD, CI_CREDENTIALS_COMPROMISED_FIELD, GQL_OPERATION_TYPE_FIELD, - GQL_OPERATION_NAME_FIELD + GQL_OPERATION_NAME_FIELD, + APP_USER_ID_FIELD_NAME, + JWT_ADDITIONAL_FIELDS_FIELD_NAME, } = require('./utils/constants'); class PxClient { @@ -66,6 +68,18 @@ class PxClient { details[GQL_OPERATION_TYPE_FIELD] = ctx.graphqlData.operationType; details[GQL_OPERATION_NAME_FIELD] = ctx.graphqlData.operationName; } + + if (ctx.jwt) { + const { userID, additionalFields } = ctx.jwt; + + if (userID) { + details[APP_USER_ID_FIELD_NAME] = userID; + } + + if (additionalFields) { + details[JWT_ADDITIONAL_FIELDS_FIELD_NAME] = additionalFields; + } + } } /** @@ -87,11 +101,11 @@ class PxClient { sendEnforcerTelemetry(updateReason, config) { const details = { - 'enforcer_configs': pxUtil.filterConfig(config), - 'node_name': os.hostname(), - 'os_name': os.platform(), - 'update_reason': updateReason, - 'module_version': config.MODULE_VERSION + enforcer_configs: pxUtil.filterConfig(config), + node_name: os.hostname(), + os_name: os.platform(), + update_reason: updateReason, + module_version: config.MODULE_VERSION, }; const pxData = {}; @@ -121,9 +135,9 @@ class PxClient { createHeaders(config, additionalHeaders = {}) { return { - 'Authorization': 'Bearer ' + config.AUTH_TOKEN, + Authorization: 'Bearer ' + config.AUTH_TOKEN, 'Content-Type': 'application/json', - ...additionalHeaders + ...additionalHeaders, }; } @@ -137,7 +151,7 @@ class PxClient { [CI_VERSION_FIELD]: loginCredentials && loginCredentials.version, [CI_RAW_USERNAME_FIELD]: loginCredentials && loginCredentials.rawUsername, [CI_SSO_STEP_FIELD]: loginCredentials && loginCredentials.ssoStep, - ...additionalDetails + ...additionalDetails, }; if (!config.SEND_RAW_USERNAME_ON_ADDITIONAL_S2S_ACTIVITY || !details.credentials_compromised) { diff --git a/lib/pxconfig.js b/lib/pxconfig.js index 45c3da04..11efc8dd 100644 --- a/lib/pxconfig.js +++ b/lib/pxconfig.js @@ -91,6 +91,12 @@ class PxConfig { ['LOGIN_SUCCESSFUL_BODY_REGEX', 'px_login_successful_body_regex'], ['LOGIN_SUCCESSFUL_CUSTOM_CALLBACK', 'px_login_successful_custom_callback'], ['MODIFY_CONTEXT', 'px_modify_context'], + ['JWT_COOKIE_NAME', 'px_jwt_cookie_name'], + ['JWT_COOKIE_USER_ID_FIELD_NAME', 'px_jwt_cookie_user_id_field_name'], + ['JWT_COOKIE_ADDITIONAL_FIELD_NAMES', 'px_jwt_cookie_additional_field_names'], + ['JWT_HEADER_NAME', 'px_jwt_header_name'], + ['JWT_HEADER_USER_ID_FIELD_NAME', 'px_jwt_header_user_id_field_name'], + ['JWT_HEADER_ADDITIONAL_FIELD_NAMES', 'px_jwt_header_additional_field_names'], ]; configKeyMapping.forEach(([targetKey, sourceKey]) => { @@ -335,6 +341,12 @@ function pxDefaultConfig() { LOGIN_SUCCESSFUL_BODY_REGEX: '', LOGIN_SUCCESSFUL_CUSTOM_CALLBACK: null, MODIFY_CONTEXT: null, + JWT_COOKIE_NAME: '', + JWT_COOKIE_USER_ID_FIELD_NAME: '', + JWT_COOKIE_ADDITIONAL_FIELD_NAMES: [], + JWT_HEADER_NAME: '', + JWT_HEADER_USER_ID_FIELD_NAME: '', + JWT_HEADER_ADDITIONAL_FIELD_NAMES: [], }; } @@ -396,6 +408,12 @@ const allowedConfigKeys = [ 'px_login_successful_body_regex', 'px_login_successful_custom_callback', 'px_modify_context', + 'px_jwt_cookie_name', + 'px_jwt_cookie_user_id_field_name', + 'px_jwt_cookie_additional_field_names', + 'px_jwt_header_name', + 'px_jwt_header_user_id_field_name', + 'px_jwt_header_additional_field_names', ]; module.exports = PxConfig; diff --git a/lib/pxcontext.js b/lib/pxcontext.js index 3f0381bb..7f0801ce 100644 --- a/lib/pxcontext.js +++ b/lib/pxcontext.js @@ -2,6 +2,7 @@ const { v4: uuidv4 } = require('uuid'); const { CookieOrigin } = require('./enums/CookieOrigin'); const pxUtil = require('./pxutil'); +const pxJWT = require('./pxjwt'); class PxContext { constructor(config, req, additionalFields) { @@ -30,6 +31,7 @@ class PxContext { this.cookieOrigin = CookieOrigin.COOKIE; this.additionalFields = additionalFields || {}; this.signedFields = [this.userAgent]; + const mobileHeader = this.headers[mobileSdkHeader]; if (mobileHeader !== undefined) { this.signedFields = null; @@ -64,6 +66,11 @@ class PxContext { if (process.env.AWS_REGION) { this.serverInfoRegion = process.env.AWS_REGION; } + + if (config.JWT_COOKIE_NAME || config.JWT_HEADER_NAME) { + const token = req.cookies[config.JWT_COOKIE_NAME] || req.headers[config.JWT_HEADER_NAME]; + this.jwt = pxJWT.extractJWTData(config, token); + } } getCookie() { @@ -86,8 +93,10 @@ class PxContext { return false; } const { operationType, operationName } = this.graphqlData; - return config.SENSITIVE_GRAPHQL_OPERATION_TYPES.includes(operationType) - || config.SENSITIVE_GRAPHQL_OPERATION_NAMES.includes(operationName); + return ( + config.SENSITIVE_GRAPHQL_OPERATION_TYPES.includes(operationType) || + config.SENSITIVE_GRAPHQL_OPERATION_NAMES.includes(operationName) + ); } verifyRoute(pattern, uri) { diff --git a/lib/pxjwt.js b/lib/pxjwt.js new file mode 100644 index 00000000..ddf75820 --- /dev/null +++ b/lib/pxjwt.js @@ -0,0 +1,52 @@ +const { TOKEN_SEPARATOR } = require('./utils/constants'); + +function getJWTDecodedData(pxConfig, token) { + try { + const encodedPayload = token.split(TOKEN_SEPARATOR)[1]; + if (encodedPayload) { + const base64 = encodedPayload.replace('-', '+').replace('_', '/'); + const data = Buffer.from(base64, 'base64').toString(); + return JSON.parse(data); + } + } catch (e) { + pxConfig.logger.debug(`Failed to parse JWT token ${token}: ${e.message} `); + } + + return null; +} + +function extractJWTData(pxConfig, token) { + let additionalFields = null; + const data = getJWTDecodedData(pxConfig, token); + + try { + if (data) { + const userFieldName = pxConfig.JWT_COOKIE_USER_ID_FIELD_NAME || pxConfig.JWT_HEADER_USER_ID_FIELD_NAME; + const userID = data[userFieldName]; + + const additionalFieldsConfig = + pxConfig.JWT_COOKIE_ADDITIONAL_FIELD_NAMES.length > 0 + ? pxConfig.JWT_COOKIE_ADDITIONAL_FIELD_NAMES + : pxConfig.JWT_HEADER_ADDITIONAL_FIELD_NAMES; + + if (additionalFieldsConfig && additionalFieldsConfig.length > 0) { + additionalFields = additionalFieldsConfig.reduce((matchedFields, fieldName) => { + if (data[fieldName]) { + matchedFields[fieldName] = data[fieldName]; + } + return matchedFields; + }, {}); + } + + return { userID, additionalFields }; + } + } catch (e) { + pxConfig.logger.debug(`Failed to extract JWT token ${token}: ${e.message} `); + } + + return null; +} + +module.exports = { + extractJWTData, +}; diff --git a/lib/pxutil.js b/lib/pxutil.js index 31cecca0..f32c40e1 100644 --- a/lib/pxutil.js +++ b/lib/pxutil.js @@ -324,6 +324,7 @@ function isEmailAddress(str) { return EMAIL_ADDRESS_REGEX.test(str); } + module.exports = { formatHeaders, filterSensitiveHeaders, diff --git a/lib/utils/constants.js b/lib/utils/constants.js index 584d5ece..a63c8116 100644 --- a/lib/utils/constants.js +++ b/lib/utils/constants.js @@ -25,9 +25,14 @@ const CI_CREDENTIALS_COMPROMISED_FIELD = 'credentials_compromised'; const GQL_OPERATION_TYPE_FIELD = 'graphql_operation_type'; const GQL_OPERATION_NAME_FIELD = 'graphql_operation_name'; -const EMAIL_ADDRESS_REGEX = /^([a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)$/; +const EMAIL_ADDRESS_REGEX = + /^([a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)$/; const HASH_ALGORITHM = { SHA256: 'sha256' }; +const TOKEN_SEPARATOR = '.'; +const APP_USER_ID_FIELD_NAME = 'app_user_id'; +const JWT_ADDITIONAL_FIELDS_FIELD_NAME = 'jwt_additional_fields'; + module.exports = { MILLISECONDS_IN_SECOND, SECONDS_IN_MINUTE, @@ -51,5 +56,8 @@ module.exports = { GQL_OPERATION_TYPE_FIELD, GQL_OPERATION_NAME_FIELD, EMAIL_ADDRESS_REGEX, - HASH_ALGORITHM -}; \ No newline at end of file + HASH_ALGORITHM, + TOKEN_SEPARATOR, + APP_USER_ID_FIELD_NAME, + JWT_ADDITIONAL_FIELDS_FIELD_NAME, +}; From bfc3ebf6901e9e04f8d778817c44f72b0d7f5f23 Mon Sep 17 00:00:00 2001 From: Johnny Tordgeman Date: Wed, 25 Jan 2023 13:36:24 +0200 Subject: [PATCH 2/7] Added CTS and various fixes --- CHANGELOG.md | 5 ++++ lib/pxapi.js | 5 ++++ lib/pxclient.js | 5 ++++ lib/pxcontext.js | 2 ++ lib/pxjwt.js | 57 ++++++++++++++++++++++++------------------ lib/utils/constants.js | 2 ++ 6 files changed, 51 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21efbfd8..a587b018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.8.0] - 2023-01-25 + +### Added +- support for Account Defender. + ## [3.7.0] - 2023-01-15 ### Added diff --git a/lib/pxapi.js b/lib/pxapi.js index 570cd887..0c71d7f7 100644 --- a/lib/pxapi.js +++ b/lib/pxapi.js @@ -16,6 +16,7 @@ const { GQL_OPERATIONS_FIELD, JWT_ADDITIONAL_FIELDS_FIELD_NAME, APP_USER_ID_FIELD_NAME, + CROSS_TAB_SESSION, } = require('./utils/constants'); const { CIVersion } = require('./enums/CIVersion'); @@ -97,6 +98,10 @@ function buildRequestData(ctx, config) { } } + if (ctx.cts) { + data.additional[CROSS_TAB_SESSION] = ctx.cts; + } + if (ctx.s2sCallReason === 'cookie_decryption_failed') { data.additional.px_orig_cookie = ctx.getCookie(); //No need strigify, already a string } diff --git a/lib/pxclient.js b/lib/pxclient.js index 3a08583e..36ff995f 100644 --- a/lib/pxclient.js +++ b/lib/pxclient.js @@ -12,6 +12,7 @@ const { GQL_OPERATIONS_FIELD, APP_USER_ID_FIELD_NAME, JWT_ADDITIONAL_FIELDS_FIELD_NAME, + CROSS_TAB_SESSION, } = require('./utils/constants'); class PxClient { @@ -78,6 +79,10 @@ class PxClient { details[JWT_ADDITIONAL_FIELDS_FIELD_NAME] = additionalFields; } } + + if (ctx.cts) { + details[CROSS_TAB_SESSION] = ctx.cts; + } } /** diff --git a/lib/pxcontext.js b/lib/pxcontext.js index 7e08b51e..065ee741 100644 --- a/lib/pxcontext.js +++ b/lib/pxcontext.js @@ -53,6 +53,8 @@ class PxContext { } else if ((key === '_pxvid' || key === 'pxvid') && vidRegex.test(cookies[key])) { this.vid = cookies[key]; this.vidSource = 'vid_cookie'; + } else if (key === 'pxcts') { + this.cts = cookies[key]; } else if (key.match(/^_px.+$/)) { this.cookies[key] = cookies[key]; } diff --git a/lib/pxjwt.js b/lib/pxjwt.js index ddf75820..4d9cd9fe 100644 --- a/lib/pxjwt.js +++ b/lib/pxjwt.js @@ -1,12 +1,12 @@ const { TOKEN_SEPARATOR } = require('./utils/constants'); -function getJWTDecodedData(pxConfig, token) { +function getJWTPayload(pxConfig, token) { try { const encodedPayload = token.split(TOKEN_SEPARATOR)[1]; if (encodedPayload) { - const base64 = encodedPayload.replace('-', '+').replace('_', '/'); - const data = Buffer.from(base64, 'base64').toString(); - return JSON.parse(data); + const base64Payload = encodedPayload.replace('-', '+').replace('_', '/'); + const payload = Buffer.from(base64Payload, 'base64').toString(); + return JSON.parse(payload); } } catch (e) { pxConfig.logger.debug(`Failed to parse JWT token ${token}: ${e.message} `); @@ -15,31 +15,28 @@ function getJWTDecodedData(pxConfig, token) { return null; } -function extractJWTData(pxConfig, token) { +function getJWTData(pxConfig, payload) { let additionalFields = null; - const data = getJWTDecodedData(pxConfig, token); try { - if (data) { - const userFieldName = pxConfig.JWT_COOKIE_USER_ID_FIELD_NAME || pxConfig.JWT_HEADER_USER_ID_FIELD_NAME; - const userID = data[userFieldName]; - - const additionalFieldsConfig = - pxConfig.JWT_COOKIE_ADDITIONAL_FIELD_NAMES.length > 0 - ? pxConfig.JWT_COOKIE_ADDITIONAL_FIELD_NAMES - : pxConfig.JWT_HEADER_ADDITIONAL_FIELD_NAMES; - - if (additionalFieldsConfig && additionalFieldsConfig.length > 0) { - additionalFields = additionalFieldsConfig.reduce((matchedFields, fieldName) => { - if (data[fieldName]) { - matchedFields[fieldName] = data[fieldName]; - } - return matchedFields; - }, {}); - } - - return { userID, additionalFields }; + const userFieldName = pxConfig.JWT_COOKIE_USER_ID_FIELD_NAME || pxConfig.JWT_HEADER_USER_ID_FIELD_NAME; + const userID = payload[userFieldName]; + + const additionalFieldsConfig = + pxConfig.JWT_COOKIE_ADDITIONAL_FIELD_NAMES.length > 0 + ? pxConfig.JWT_COOKIE_ADDITIONAL_FIELD_NAMES + : pxConfig.JWT_HEADER_ADDITIONAL_FIELD_NAMES; + + if (additionalFieldsConfig && additionalFieldsConfig.length > 0) { + additionalFields = additionalFieldsConfig.reduce((matchedFields, fieldName) => { + if (payload[fieldName]) { + matchedFields[fieldName] = payload[fieldName]; + } + return matchedFields; + }, {}); } + + return { userID, additionalFields }; } catch (e) { pxConfig.logger.debug(`Failed to extract JWT token ${token}: ${e.message} `); } @@ -47,6 +44,16 @@ function extractJWTData(pxConfig, token) { return null; } +function extractJWTData(pxConfig, token) { + const payload = getJWTPayload(pxConfig, token); + + if (!payload) { + return null; + } + + return getJWTData(pxConfig, payload); +} + module.exports = { extractJWTData, }; diff --git a/lib/utils/constants.js b/lib/utils/constants.js index 4987c483..16f851df 100644 --- a/lib/utils/constants.js +++ b/lib/utils/constants.js @@ -31,6 +31,7 @@ const HASH_ALGORITHM = { SHA256: 'sha256' }; const TOKEN_SEPARATOR = '.'; const APP_USER_ID_FIELD_NAME = 'app_user_id'; const JWT_ADDITIONAL_FIELDS_FIELD_NAME = 'jwt_additional_fields'; +const CROSS_TAB_SESSION = 'cross_tab_session'; module.exports = { MILLISECONDS_IN_SECOND, @@ -58,4 +59,5 @@ module.exports = { TOKEN_SEPARATOR, APP_USER_ID_FIELD_NAME, JWT_ADDITIONAL_FIELDS_FIELD_NAME, + CROSS_TAB_SESSION, }; From 89318daa610c6ff20cf310dd2ebef2ce722eee73 Mon Sep 17 00:00:00 2001 From: Johnny Tordgeman Date: Wed, 25 Jan 2023 13:38:02 +0200 Subject: [PATCH 3/7] fixed wrong var in error message --- lib/pxjwt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pxjwt.js b/lib/pxjwt.js index 4d9cd9fe..b9b675ec 100644 --- a/lib/pxjwt.js +++ b/lib/pxjwt.js @@ -38,7 +38,7 @@ function getJWTData(pxConfig, payload) { return { userID, additionalFields }; } catch (e) { - pxConfig.logger.debug(`Failed to extract JWT token ${token}: ${e.message} `); + pxConfig.logger.debug(`Failed to extract JWT token ${payload}: ${e.message} `); } return null; From f375620edd270a7857c45699f7cc040a1a30500c Mon Sep 17 00:00:00 2001 From: Johnny Tordgeman Date: Thu, 26 Jan 2023 10:08:16 +0200 Subject: [PATCH 4/7] verifying token exists before calling extract --- lib/pxcontext.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pxcontext.js b/lib/pxcontext.js index 065ee741..672e866d 100644 --- a/lib/pxcontext.js +++ b/lib/pxcontext.js @@ -79,7 +79,9 @@ class PxContext { if (config.JWT_COOKIE_NAME || config.JWT_HEADER_NAME) { const token = req.cookies[config.JWT_COOKIE_NAME] || req.headers[config.JWT_HEADER_NAME]; - this.jwt = pxJWT.extractJWTData(config, token); + if (token) { + this.jwt = pxJWT.extractJWTData(config, token); + } } } From 7b82178c52ed3ced40347b3a376cf99940d3c4e9 Mon Sep 17 00:00:00 2001 From: Johnny Tordgeman Date: Thu, 26 Jan 2023 11:01:34 +0200 Subject: [PATCH 5/7] updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a587b018..8257463a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [3.8.0] - 2023-01-25 ### Added -- support for Account Defender. +- Support User Identifiers: CTS and JWT. ## [3.7.0] - 2023-01-15 From e6f8638ca63d060364010ee13f485c88ffe82b9f Mon Sep 17 00:00:00 2001 From: chenzimmer2 Date: Thu, 26 Jan 2023 13:03:51 +0200 Subject: [PATCH 6/7] release version 3.8.0 --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 087b2ad8..b13f83db 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [PerimeterX](http://www.perimeterx.com) Shared base for NodeJS enforcers ============================================================= -> Latest stable version: [v3.7.0](https://www.npmjs.com/package/perimeterx-node-core) +> Latest stable version: [v3.8.0](https://www.npmjs.com/package/perimeterx-node-core) This is a shared base implementation for PerimeterX Express enforcer and future NodeJS enforcers. For a fully functioning implementation example, see the [Node-Express enforcer](https://github.com/PerimeterX/perimeterx-node-express/) implementation. diff --git a/package.json b/package.json index ca034281..16f89758 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "perimeterx-node-core", - "version": "3.7.0", + "version": "3.8.0", "description": "PerimeterX NodeJS shared core for various applications to monitor and block traffic according to PerimeterX risk score", "main": "index.js", "scripts": { From d43a5710c52c3ae03291c392bf1789f41600f9b0 Mon Sep 17 00:00:00 2001 From: chenzimmer2 Date: Thu, 26 Jan 2023 14:08:57 +0200 Subject: [PATCH 7/7] Added package node --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3af4044d..a72cc692 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "perimeterx-node-core", - "version": "3.7.0", + "version": "3.8.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "perimeterx-node-core", - "version": "3.7.0", + "version": "3.8.0", "license": "ISC", "dependencies": { "agent-phin": "^1.0.4",