Skip to content
This repository was archived by the owner on Oct 29, 2025. It is now read-only.

Commit c2f74eb

Browse files
Merge pull request #241 from PerimeterX/release/v3.4.0
Release/v3.4.0 to master
2 parents 4d82033 + 538f006 commit c2f74eb

File tree

11 files changed

+114
-7
lines changed

11 files changed

+114
-7
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ 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+
9+
## [3.4.0] - 2022-05-1
10+
11+
### Added
12+
13+
- Credentials intelligence v2 hashing protocol added and set as default
14+
815
## [3.3.2] - 2022-04-11
916

1017
### Fixed

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.3.2](https://www.npmjs.com/package/perimeterx-node-core)
9+
> Latest stable version: [v3.4.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/enums/CIVersion.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const CIVersion = {
22
V1: 'v1',
3+
V2: 'v2',
34
MULTISTEP_SSO: 'multistep_sso'
45
};
56

lib/extract_field/credentials_intelligence/CredentialsIntelligenceProtocolFactory.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
const { CIVersion } = require('../../enums/CIVersion');
22
const { V1CredentialsIntelligenceProtocol } = require('./V1CredentialsIntelligenceProtocol');
33
const { MultistepSSOCredentialsIntelligenceProtocol } = require('./MultistepSSOCredentialsIntelligenceProtocol');
4+
const { V2CredentialsIntelligenceProtocol } = require('./V2CredentialsIntelligenceProtocol');
45

56
class CredentialsIntelligenceProtocolFactory {
67
static Create(protocolVersion) {
78
switch (protocolVersion) {
89
case CIVersion.V1:
910
return new V1CredentialsIntelligenceProtocol();
11+
case CIVersion.V2:
12+
return new V2CredentialsIntelligenceProtocol();
1013
case CIVersion.MULTISTEP_SSO:
1114
return new MultistepSSOCredentialsIntelligenceProtocol();
1215
default:
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const { ICredentialsIntelligenceProtocol } = require('./ICredentialsIntelligenceProtocol');
2+
const { LoginCredentialsFields } = require('../../models/LoginCredentialsFields');
3+
const pxUtil = require('../../pxutil');
4+
const { CIVersion } = require('../../enums/CIVersion');
5+
6+
class V2CredentialsIntelligenceProtocol extends ICredentialsIntelligenceProtocol {
7+
ProcessCredentials(username, password) {
8+
const normalizedUsername = pxUtil.isEmailAddress(username) ? this.normalizeEmailAddress(username) : username;
9+
const hashedUsername = pxUtil.sha256(normalizedUsername);
10+
const hashedPassword = this.hashPassword(hashedUsername, password);
11+
12+
return new LoginCredentialsFields(
13+
hashedUsername,
14+
hashedPassword,
15+
username,
16+
CIVersion.V2
17+
);
18+
}
19+
20+
normalizeEmailAddress(emailAddress) {
21+
const lowercaseEmail = emailAddress.trim().toLowerCase();
22+
const atIndex = lowercaseEmail.indexOf('@');
23+
let normalizedUsername = lowercaseEmail.substring(0, atIndex);
24+
const plusIndex = normalizedUsername.indexOf('+');
25+
26+
if (plusIndex > -1) {
27+
normalizedUsername = normalizedUsername.substring(0, plusIndex);
28+
}
29+
30+
const domain = lowercaseEmail.substring(atIndex);
31+
const GMAIL_DOMAIN = '@gmail.com';
32+
33+
if (domain === GMAIL_DOMAIN) {
34+
normalizedUsername = normalizedUsername.replace('.', '');
35+
}
36+
37+
return `${normalizedUsername}${domain}`;
38+
}
39+
40+
hashPassword(salt, password) {
41+
const hashedPassword = pxUtil.sha256(password);
42+
return pxUtil.sha256(salt + hashedPassword);
43+
}
44+
}
45+
46+
47+
module.exports = {
48+
V2CredentialsIntelligenceProtocol
49+
};

lib/pxconfig.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ function pxDefaultConfig() {
317317
CUSTOM_COOKIE_HEADER: '',
318318
ENABLE_LOGIN_CREDS_EXTRACTION: false,
319319
LOGIN_CREDS_EXTRACTION: [],
320-
CREDENTIALS_INTELLIGENCE_VERSION: CIVersion.V1,
320+
CREDENTIALS_INTELLIGENCE_VERSION: CIVersion.V2,
321321
COMPROMISED_CREDENTIALS_HEADER: DEFAULT_COMPROMISED_CREDENTIALS_HEADER_NAME,
322322
ENABLE_ADDITIONAL_S2S_ACTIVITY_HEADER: false,
323323
SENSITIVE_GRAPHQL_OPERATION_TYPES: [],

lib/pxutil.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const crypto = require('crypto');
77
const { ModuleMode } = require('./enums/ModuleMode');
88
const { GraphqlOperationType } = require('./enums/GraphqlOperationType');
99
const { GraphqlData } = require('./models/GraphqlData');
10+
const {EMAIL_ADDRESS_REGEX, HASH_ALGORITHM} = require('./utils/constants')
1011

1112
/**
1213
* PerimeterX (http://www.perimeterx.com) NodeJS-Express SDK
@@ -224,7 +225,7 @@ function _hashString(string, hashType) {
224225
}
225226

226227
function sha256(string) {
227-
return _hashString(string, 'sha256');
228+
return _hashString(string, HASH_ALGORITHM.SHA256);
228229
}
229230

230231
function isStringMatchWith(string, match) {
@@ -305,6 +306,10 @@ function isValidGraphqlOperationType(operationType) {
305306
return Object.values(GraphqlOperationType).includes(operationType);
306307
}
307308

309+
function isEmailAddress(str) {
310+
return EMAIL_ADDRESS_REGEX.test(str);
311+
}
312+
308313
module.exports = {
309314
formatHeaders,
310315
filterSensitiveHeaders,
@@ -323,5 +328,6 @@ module.exports = {
323328
generateHMAC,
324329
isReqInMonitorMode,
325330
getTokenObject,
326-
getGraphqlData
331+
getGraphqlData,
332+
isEmailAddress
327333
};

lib/utils/constants.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const CI_CREDENTIALS_COMPROMISED_FIELD = 'credentials_compromised';
2525

2626
const GQL_OPERATION_TYPE_FIELD = 'graphql_operation_type';
2727
const GQL_OPERATION_NAME_FIELD = 'graphql_operation_name';
28+
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])?)$/
29+
const HASH_ALGORITHM = { SHA256: 'sha256' };
2830

2931
module.exports = {
3032
MILLISECONDS_IN_SECOND,
@@ -47,5 +49,7 @@ module.exports = {
4749
CI_SSO_STEP_FIELD,
4850
CI_CREDENTIALS_COMPROMISED_FIELD,
4951
GQL_OPERATION_TYPE_FIELD,
50-
GQL_OPERATION_NAME_FIELD
52+
GQL_OPERATION_NAME_FIELD,
53+
EMAIL_ADDRESS_REGEX,
54+
HASH_ALGORITHM
5155
};

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.3.2",
3+
"version": "3.4.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/pxci.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const should = require('should');
2+
const { V2CredentialsIntelligenceProtocol } = require('../lib/extract_field/credentials_intelligence/V2CredentialsIntelligenceProtocol');
3+
4+
describe('Check credentials intelligence v2 hashing', function() {
5+
it('Should hash username', function() {
6+
const username = 'pxUser';
7+
const password = '1234';
8+
9+
const protocol = new V2CredentialsIntelligenceProtocol();
10+
const hashedCredentials = protocol.ProcessCredentials(username, password);
11+
12+
(hashedCredentials.username).should.equal('9620f4cab3b3a50b9cbcb9a8d01328874ec33eb6882ae31c022f6986fc516851');
13+
(hashedCredentials.password).should.equal('c958c33151f273c620ec658ac4de9abd33ad7627df5d8c468224b0bae7173eb4');
14+
});
15+
16+
it('Should normalize and hash gmail', function() {
17+
const username = '[email protected]';
18+
const password = '1234';
19+
20+
const protocol = new V2CredentialsIntelligenceProtocol();
21+
const hashedCredentials = protocol.ProcessCredentials(username, password);
22+
23+
(hashedCredentials.username).should.equal('2bd9bd06f3440c682044a3f1b1fa7a97bd8b568a6e9e7d2cb0c6e858d9c78069');
24+
(hashedCredentials.password).should.equal('5246d99e5d2506d70db44e8216aecb7be42bf5bf7bc1766a680cbdad2ce046ab');
25+
});
26+
27+
it('Should normalize and hash mail', function() {
28+
const username = '[email protected]';
29+
const password = '1234';
30+
31+
const protocol = new V2CredentialsIntelligenceProtocol();
32+
const hashedCredentials = protocol.ProcessCredentials(username, password);
33+
34+
(hashedCredentials.username).should.equal('53225d1fa939355031fa2208a44ada1bf9953f0d0daf894baa984a4310df6b48');
35+
(hashedCredentials.password).should.equal('ddf40c1584801828bda92cc493373d045de593f73c4fb40aab5de7f19aa8df94');
36+
});
37+
});

0 commit comments

Comments
 (0)