perimeterx-js-core 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/config/ConfigurationBase.js +7 -0
- package/lib/cjs/config/defaults/DefaultCommonConfigurationParams.js +1 -5
- package/lib/cjs/impl/url/UrlImpl.js +27 -7
- package/lib/cjs/logger/LoggerBase.js +3 -0
- package/lib/cjs/phase/flow/PostEnforceFlow.js +1 -1
- package/lib/cjs/phase/impl/CreateBlockResponsePhase.js +1 -1
- package/lib/cjs/phase/impl/ModifyOutgoingResponsePhase.js +3 -2
- package/lib/cjs/phase/impl/SendLogsPhase.js +3 -1
- package/lib/cjs/products/bot_defender/first_party/DefaultBotDefenderFirstParty.js +45 -10
- package/lib/cjs/pxhd/PXHDUtils.js +7 -6
- package/lib/cjs/utils/constants.js +2 -2
- package/lib/esm/config/ConfigurationBase.js +3 -0
- package/lib/esm/config/defaults/DefaultCommonConfigurationParams.js +1 -5
- package/lib/esm/impl/url/UrlImpl.js +26 -7
- package/lib/esm/logger/LoggerBase.js +3 -0
- package/lib/esm/phase/flow/PostEnforceFlow.js +1 -1
- package/lib/esm/phase/impl/CreateBlockResponsePhase.js +1 -1
- package/lib/esm/phase/impl/ModifyOutgoingResponsePhase.js +4 -2
- package/lib/esm/phase/impl/SendLogsPhase.js +1 -0
- package/lib/esm/products/bot_defender/first_party/DefaultBotDefenderFirstParty.js +45 -10
- package/lib/esm/pxhd/PXHDUtils.js +7 -6
- package/lib/esm/utils/constants.js +2 -2
- package/lib/types/config/ConfigurationBase.d.ts +1 -0
- package/lib/types/config/IConfiguration.d.ts +4 -0
- package/lib/types/config/params/CommonConfigurationParams.d.ts +1 -5
- package/lib/types/impl/url/UrlImpl.d.ts +5 -1
- package/lib/types/logger/ILogger.d.ts +4 -0
- package/lib/types/logger/LoggerBase.d.ts +1 -0
- package/lib/types/phase/impl/ModifyOutgoingResponsePhase.d.ts +3 -1
- package/lib/types/products/bot_defender/first_party/DefaultBotDefenderFirstParty.d.ts +1 -0
- package/lib/types/pxhd/PXHDUtils.d.ts +1274 -3
- package/lib/types/utils/constants.d.ts +1 -1
- package/package.json +1 -1
|
@@ -634,6 +634,13 @@ var ConfigurationBase = /** @class */ (function () {
|
|
|
634
634
|
enumerable: false,
|
|
635
635
|
configurable: true
|
|
636
636
|
});
|
|
637
|
+
Object.defineProperty(ConfigurationBase.prototype, "securedPxhdEnabled", {
|
|
638
|
+
get: function () {
|
|
639
|
+
return this.configParams.px_secured_pxhd_enabled;
|
|
640
|
+
},
|
|
641
|
+
enumerable: false,
|
|
642
|
+
configurable: true
|
|
643
|
+
});
|
|
637
644
|
return ConfigurationBase;
|
|
638
645
|
}());
|
|
639
646
|
exports.ConfigurationBase = ConfigurationBase;
|
|
@@ -13,7 +13,6 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
|
|
|
13
13
|
px_risk_cookie_max_iterations: 5000,
|
|
14
14
|
px_logger_severity: logger_1.LoggerSeverity.ERROR,
|
|
15
15
|
px_ip_headers: [],
|
|
16
|
-
px_extract_ip: null,
|
|
17
16
|
px_module_enabled: true,
|
|
18
17
|
px_module_mode: utils_1.ModuleMode.MONITOR,
|
|
19
18
|
px_additional_activity_handler: null,
|
|
@@ -21,9 +20,6 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
|
|
|
21
20
|
px_max_activity_batch_size: 0,
|
|
22
21
|
px_batch_activities_timeout_ms: 1000,
|
|
23
22
|
px_bypass_monitor_header: '',
|
|
24
|
-
px_csp_enabled: false,
|
|
25
|
-
px_csp_no_updates_max_interval_minutes: 60,
|
|
26
|
-
px_csp_policy_refresh_interval_minutes: 5,
|
|
27
23
|
px_enforced_routes: [],
|
|
28
24
|
px_first_party_enabled: true,
|
|
29
25
|
px_custom_first_party_prefix: '',
|
|
@@ -104,7 +100,6 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
|
|
|
104
100
|
px_sensitive_graphql_operation_names: [],
|
|
105
101
|
px_sensitive_graphql_operation_types: [],
|
|
106
102
|
px_enrich_custom_parameters: null,
|
|
107
|
-
px_proxy_url: '',
|
|
108
103
|
px_jwt_cookie_name: '',
|
|
109
104
|
px_jwt_cookie_user_id_field_name: '',
|
|
110
105
|
px_jwt_cookie_additional_field_names: [],
|
|
@@ -118,4 +113,5 @@ exports.DEFAULT_COMMON_CONFIGURATION_PARAMS = {
|
|
|
118
113
|
px_remote_config_max_fetch_attempts: 5,
|
|
119
114
|
px_remote_config_retry_interval_ms: 1000,
|
|
120
115
|
px_url_decode_reserved_characters: false,
|
|
116
|
+
px_secured_pxhd_enabled: false,
|
|
121
117
|
};
|
|
@@ -11,15 +11,18 @@ var UrlImpl = /** @class */ (function () {
|
|
|
11
11
|
throw new Error("Invalid UrlImpl: ".concat(rawUrl));
|
|
12
12
|
}
|
|
13
13
|
this.protocol = match[1];
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
14
|
+
this.username = match[3] || '';
|
|
15
|
+
this.password = match[4] || '';
|
|
16
|
+
this.hostname = match[6];
|
|
17
|
+
this.port = match[7] || '';
|
|
18
|
+
this.pathname = match[8] || '/';
|
|
19
|
+
this.search = match[9] || '';
|
|
20
|
+
this.hash = match[10] || '';
|
|
21
|
+
this.urlUtils = new CustomImplUrlUtils_1.CustomImplUrlUtils();
|
|
19
22
|
}
|
|
20
23
|
Object.defineProperty(UrlImpl.prototype, "href", {
|
|
21
24
|
get: function () {
|
|
22
|
-
return "".concat(this.
|
|
25
|
+
return "".concat(this.protocol, "//").concat(this.credentials).concat(this.host).concat(this.pathname).concat(this.search).concat(this.hash);
|
|
23
26
|
},
|
|
24
27
|
enumerable: false,
|
|
25
28
|
configurable: true
|
|
@@ -57,7 +60,7 @@ var UrlImpl = /** @class */ (function () {
|
|
|
57
60
|
});
|
|
58
61
|
Object.defineProperty(UrlImpl.prototype, "searchParams", {
|
|
59
62
|
get: function () {
|
|
60
|
-
return new UrlSearchParamsImpl_1.UrlSearchParamsImpl(
|
|
63
|
+
return new UrlSearchParamsImpl_1.UrlSearchParamsImpl(this.urlUtils, this.search);
|
|
61
64
|
},
|
|
62
65
|
enumerable: false,
|
|
63
66
|
configurable: true
|
|
@@ -69,6 +72,23 @@ var UrlImpl = /** @class */ (function () {
|
|
|
69
72
|
};
|
|
70
73
|
return PROTOCOL_TO_DEFAULT_PORT[this.protocol] === port;
|
|
71
74
|
};
|
|
75
|
+
Object.defineProperty(UrlImpl.prototype, "credentials", {
|
|
76
|
+
get: function () {
|
|
77
|
+
if (!this.username && !this.password) {
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
var credentials = '';
|
|
81
|
+
if (this.username) {
|
|
82
|
+
credentials += this.urlUtils.encodeUriComponent(this.username);
|
|
83
|
+
}
|
|
84
|
+
if (this.password) {
|
|
85
|
+
credentials += ":".concat(this.urlUtils.encodeUriComponent(this.password));
|
|
86
|
+
}
|
|
87
|
+
return "".concat(credentials, "@");
|
|
88
|
+
},
|
|
89
|
+
enumerable: false,
|
|
90
|
+
configurable: true
|
|
91
|
+
});
|
|
72
92
|
return UrlImpl;
|
|
73
93
|
}());
|
|
74
94
|
exports.UrlImpl = UrlImpl;
|
|
@@ -54,6 +54,9 @@ var LoggerBase = /** @class */ (function () {
|
|
|
54
54
|
var logRecord = __assign(__assign({}, metadata), { message: message, severity: loggerSeverity, messageTimestamp: Date.now() });
|
|
55
55
|
this.logs.push(logRecord);
|
|
56
56
|
};
|
|
57
|
+
LoggerBase.prototype.clearLogs = function () {
|
|
58
|
+
this.logs = [];
|
|
59
|
+
};
|
|
57
60
|
return LoggerBase;
|
|
58
61
|
}());
|
|
59
62
|
exports.LoggerBase = LoggerBase;
|
|
@@ -23,7 +23,7 @@ var PostEnforceFlow = /** @class */ (function (_super) {
|
|
|
23
23
|
var products = _a.products, activityClient = _a.activityClient;
|
|
24
24
|
return _super.call(this, [
|
|
25
25
|
new impl_1.EnrichContextFromResponsePhase(config, products),
|
|
26
|
-
new impl_1.ModifyOutgoingResponsePhase(Object.values(products)),
|
|
26
|
+
new impl_1.ModifyOutgoingResponsePhase(config, Object.values(products)),
|
|
27
27
|
new impl_1.SendAsyncActivitiesOnResponsePhase(activityClient),
|
|
28
28
|
]) || this;
|
|
29
29
|
}
|
|
@@ -92,7 +92,7 @@ var CreateBlockResponsePhase = /** @class */ (function () {
|
|
|
92
92
|
switch (_c.label) {
|
|
93
93
|
case 0:
|
|
94
94
|
if (!context.isMobile && ((_a = context.pxhd) === null || _a === void 0 ? void 0 : _a.source) === pxhd_1.PXHDSource.RISK) {
|
|
95
|
-
response = pxhd_1.PXHDUtils.addPxhdToMinimalResponse(context, response);
|
|
95
|
+
response = pxhd_1.PXHDUtils.addPxhdToMinimalResponse(this.config, context, response);
|
|
96
96
|
}
|
|
97
97
|
if (!(this.config.corsSupportEnabled && ((_b = this.cors) === null || _b === void 0 ? void 0 : _b.isCorsRequest(context)))) return [3 /*break*/, 2];
|
|
98
98
|
return [4 /*yield*/, this.cors.getCorsBlockHeaders(context)];
|
|
@@ -39,7 +39,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.ModifyOutgoingResponsePhase = void 0;
|
|
40
40
|
var pxhd_1 = require("../../pxhd");
|
|
41
41
|
var ModifyOutgoingResponsePhase = /** @class */ (function () {
|
|
42
|
-
function ModifyOutgoingResponsePhase(products) {
|
|
42
|
+
function ModifyOutgoingResponsePhase(config, products) {
|
|
43
|
+
this.config = config;
|
|
43
44
|
this.products = products;
|
|
44
45
|
}
|
|
45
46
|
ModifyOutgoingResponsePhase.prototype.execute = function (context) {
|
|
@@ -51,7 +52,7 @@ var ModifyOutgoingResponsePhase = /** @class */ (function () {
|
|
|
51
52
|
case 1:
|
|
52
53
|
_b.sent();
|
|
53
54
|
if (((_a = context.pxhd) === null || _a === void 0 ? void 0 : _a.source) === pxhd_1.PXHDSource.RISK) {
|
|
54
|
-
pxhd_1.PXHDUtils.addPxhdToOutgoingResponse(context, context.response);
|
|
55
|
+
pxhd_1.PXHDUtils.addPxhdToOutgoingResponse(this.config, context, context.response);
|
|
55
56
|
}
|
|
56
57
|
return [2 /*return*/, { done: false }];
|
|
57
58
|
}
|
|
@@ -85,6 +85,9 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
|
|
|
85
85
|
return [2 /*return*/, { defaultResponse: defaultResponse }];
|
|
86
86
|
}
|
|
87
87
|
url = this.getThirdPartySensorScriptUrl();
|
|
88
|
+
if (!url) {
|
|
89
|
+
return [2 /*return*/, { defaultResponse: defaultResponse }];
|
|
90
|
+
}
|
|
88
91
|
return [4 /*yield*/, this.getOutgoingRequest(url, context)];
|
|
89
92
|
case 1:
|
|
90
93
|
request = _a.sent();
|
|
@@ -105,6 +108,9 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
|
|
|
105
108
|
return [2 /*return*/, { defaultResponse: defaultResponse }];
|
|
106
109
|
}
|
|
107
110
|
url = this.getThirdPartyXhrUrl(context, prefix);
|
|
111
|
+
if (!url) {
|
|
112
|
+
return [2 /*return*/, { defaultResponse: defaultResponse }];
|
|
113
|
+
}
|
|
108
114
|
return [4 /*yield*/, this.getOutgoingRequest(url, context)];
|
|
109
115
|
case 1:
|
|
110
116
|
request = _a.sent();
|
|
@@ -133,6 +139,9 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
|
|
|
133
139
|
return [2 /*return*/, { defaultResponse: defaultResponse }];
|
|
134
140
|
}
|
|
135
141
|
url = this.getThirdPartyCaptchaScriptUrl(context);
|
|
142
|
+
if (!url) {
|
|
143
|
+
return [2 /*return*/, { defaultResponse: defaultResponse }];
|
|
144
|
+
}
|
|
136
145
|
return [4 /*yield*/, this.getOutgoingRequest(url, context)];
|
|
137
146
|
case 1:
|
|
138
147
|
request = _a.sent();
|
|
@@ -147,7 +156,7 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
|
|
|
147
156
|
return __awaiter(this, void 0, void 0, function () {
|
|
148
157
|
return __generator(this, function (_b) {
|
|
149
158
|
return [2 /*return*/, new http_1.OutgoingRequestImpl({
|
|
150
|
-
url: url,
|
|
159
|
+
url: url.href,
|
|
151
160
|
method: requestData.method,
|
|
152
161
|
headers: this.prepareFirstPartyHeaders(url, requestData, vid),
|
|
153
162
|
body: requestData.request.body,
|
|
@@ -173,8 +182,7 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
|
|
|
173
182
|
return headers;
|
|
174
183
|
};
|
|
175
184
|
DefaultBotDefenderFirstParty.prototype.setHostHeader = function (headers, url) {
|
|
176
|
-
|
|
177
|
-
headers[http_1.HOST_HEADER_NAME] = [host];
|
|
185
|
+
headers[http_1.HOST_HEADER_NAME] = [url.host];
|
|
178
186
|
};
|
|
179
187
|
DefaultBotDefenderFirstParty.prototype.setXffHeader = function (headers, ip) {
|
|
180
188
|
var xffValue = headers[http_1.X_FORWARDED_FOR_HEADER_NAME] || [];
|
|
@@ -185,17 +193,44 @@ var DefaultBotDefenderFirstParty = /** @class */ (function () {
|
|
|
185
193
|
headers[constants_1.X_PX_ENFORCER_TRUE_IP_HEADER_NAME] = [ip];
|
|
186
194
|
};
|
|
187
195
|
DefaultBotDefenderFirstParty.prototype.getThirdPartySensorScriptUrl = function () {
|
|
188
|
-
|
|
196
|
+
try {
|
|
197
|
+
return this.urlUtils.createUrl("".concat(this.config.backendClientUrl, "/").concat(this.config.appId, "/main.min.js"));
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
this.config.logger.debug("unable to create third party sensor URL: ".concat(e));
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
189
203
|
};
|
|
190
204
|
DefaultBotDefenderFirstParty.prototype.getThirdPartyCaptchaScriptUrl = function (context) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
205
|
+
try {
|
|
206
|
+
var originalUrl = context.requestData.url;
|
|
207
|
+
var _a = this.config, appId = _a.appId, backendCaptchaUrl = _a.backendCaptchaUrl;
|
|
208
|
+
return this.urlUtils.createUrl("".concat(backendCaptchaUrl, "/").concat(appId).concat(FirstPartySuffix_1.FirstPartySuffix.CAPTCHA, ".js").concat(originalUrl.search));
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
this.config.logger.debug("unable to create third party captcha URL: ".concat(e));
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
194
214
|
};
|
|
195
215
|
DefaultBotDefenderFirstParty.prototype.getThirdPartyXhrUrl = function (context, prefix) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
216
|
+
try {
|
|
217
|
+
var originalUrl = context.requestData.url;
|
|
218
|
+
var pathname = originalUrl.pathname.replace(prefix, '');
|
|
219
|
+
var thirdPartyUrl = this.urlUtils.createUrl("".concat(this.config.backendCollectorUrl).concat(pathname).concat(originalUrl.search));
|
|
220
|
+
var host = this.urlUtils.createUrl(this.config.backendCollectorUrl).host;
|
|
221
|
+
if (!this.isValidThirdPartyUrl(thirdPartyUrl, host, pathname)) {
|
|
222
|
+
this.config.logger.debug("invalid third party url: ".concat(thirdPartyUrl));
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return thirdPartyUrl;
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
this.config.logger.debug("unable to create third party XHR URL: ".concat(e));
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
DefaultBotDefenderFirstParty.prototype.isValidThirdPartyUrl = function (url, expectedHost, expectedPath) {
|
|
233
|
+
return url.host.toLowerCase() === expectedHost.toLowerCase() && url.pathname.startsWith(expectedPath);
|
|
199
234
|
};
|
|
200
235
|
return DefaultBotDefenderFirstParty;
|
|
201
236
|
}());
|
|
@@ -7,25 +7,26 @@ var PXHDUtils;
|
|
|
7
7
|
(function (PXHDUtils) {
|
|
8
8
|
PXHDUtils.PXHD_SAMESITE_VALUE = 'Lax';
|
|
9
9
|
PXHDUtils.PXHD_PATH_VALUE = '/';
|
|
10
|
-
PXHDUtils.addPxhdToOutgoingResponse = function (context, response) {
|
|
10
|
+
PXHDUtils.addPxhdToOutgoingResponse = function (config, context, response) {
|
|
11
11
|
if (!(context === null || context === void 0 ? void 0 : context.pxhd)) {
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
|
-
var setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd);
|
|
14
|
+
var setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd, config.securedPxhdEnabled);
|
|
15
15
|
response.headers.append(http_1.SET_COOKIE_HEADER_NAME, setPxhdCookie);
|
|
16
16
|
};
|
|
17
|
-
PXHDUtils.addPxhdToMinimalResponse = function (context, response) {
|
|
17
|
+
PXHDUtils.addPxhdToMinimalResponse = function (config, context, response) {
|
|
18
18
|
if (context === null || context === void 0 ? void 0 : context.pxhd) {
|
|
19
|
-
var setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd);
|
|
19
|
+
var setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd, config.securedPxhdEnabled);
|
|
20
20
|
return http_1.MinimalResponseUtils.appendHeader(response, http_1.SET_COOKIE_HEADER_NAME, setPxhdCookie);
|
|
21
21
|
}
|
|
22
22
|
return response;
|
|
23
23
|
};
|
|
24
|
-
PXHDUtils.getPxhdCookieValue = function (pxhd) {
|
|
24
|
+
PXHDUtils.getPxhdCookieValue = function (pxhd, isSecure) {
|
|
25
25
|
var value = "".concat(utils_1.PXHD_COOKIE_NAME, "=").concat(pxhd.value);
|
|
26
|
+
var secure = isSecure ? 'Secure' : '';
|
|
26
27
|
var domain = pxhd.domain && "domain=".concat(pxhd.domain);
|
|
27
28
|
var path = "path=".concat(PXHDUtils.PXHD_PATH_VALUE);
|
|
28
29
|
var sameSite = "SameSite=".concat(PXHDUtils.PXHD_SAMESITE_VALUE);
|
|
29
|
-
return [value, domain, path, sameSite].filter(Boolean).join('; ');
|
|
30
|
+
return [value, secure, domain, path, sameSite].filter(Boolean).join('; ');
|
|
30
31
|
};
|
|
31
32
|
})(PXHDUtils || (exports.PXHDUtils = PXHDUtils = {}));
|
|
@@ -12,5 +12,5 @@ exports.X_PX_BYPASS_REASON_HEADER_NAME = 'x-px-bypass-reason';
|
|
|
12
12
|
exports.PUSH_DATA_HMAC_HEADER_NAME = 'x-px-pushdata';
|
|
13
13
|
exports.PUSH_DATA_FEATURE_HEADER_NAME = 'x-px-feature';
|
|
14
14
|
exports.EMAIL_ADDRESS_REGEX = /^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,7}$/;
|
|
15
|
-
exports.URL_REGEX = /^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/;
|
|
16
|
-
exports.CORE_MODULE_VERSION = 'JS Core 0.
|
|
15
|
+
exports.URL_REGEX = /^(https?\:)\/\/(([^@\s:]+):?([^@\s]*)@)?(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/;
|
|
16
|
+
exports.CORE_MODULE_VERSION = 'JS Core 0.14.0';
|
|
@@ -10,7 +10,6 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
|
|
|
10
10
|
px_risk_cookie_max_iterations: 5000,
|
|
11
11
|
px_logger_severity: LoggerSeverity.ERROR,
|
|
12
12
|
px_ip_headers: [],
|
|
13
|
-
px_extract_ip: null,
|
|
14
13
|
px_module_enabled: true,
|
|
15
14
|
px_module_mode: ModuleMode.MONITOR,
|
|
16
15
|
px_additional_activity_handler: null,
|
|
@@ -18,9 +17,6 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
|
|
|
18
17
|
px_max_activity_batch_size: 0,
|
|
19
18
|
px_batch_activities_timeout_ms: 1000,
|
|
20
19
|
px_bypass_monitor_header: '',
|
|
21
|
-
px_csp_enabled: false,
|
|
22
|
-
px_csp_no_updates_max_interval_minutes: 60,
|
|
23
|
-
px_csp_policy_refresh_interval_minutes: 5,
|
|
24
20
|
px_enforced_routes: [],
|
|
25
21
|
px_first_party_enabled: true,
|
|
26
22
|
px_custom_first_party_prefix: '',
|
|
@@ -101,7 +97,6 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
|
|
|
101
97
|
px_sensitive_graphql_operation_names: [],
|
|
102
98
|
px_sensitive_graphql_operation_types: [],
|
|
103
99
|
px_enrich_custom_parameters: null,
|
|
104
|
-
px_proxy_url: '',
|
|
105
100
|
px_jwt_cookie_name: '',
|
|
106
101
|
px_jwt_cookie_user_id_field_name: '',
|
|
107
102
|
px_jwt_cookie_additional_field_names: [],
|
|
@@ -115,4 +110,5 @@ export const DEFAULT_COMMON_CONFIGURATION_PARAMS = {
|
|
|
115
110
|
px_remote_config_max_fetch_attempts: 5,
|
|
116
111
|
px_remote_config_retry_interval_ms: 1000,
|
|
117
112
|
px_url_decode_reserved_characters: false,
|
|
113
|
+
px_secured_pxhd_enabled: false,
|
|
118
114
|
};
|
|
@@ -7,21 +7,27 @@ export class UrlImpl {
|
|
|
7
7
|
pathname;
|
|
8
8
|
search;
|
|
9
9
|
hash;
|
|
10
|
+
username;
|
|
11
|
+
password;
|
|
10
12
|
_port;
|
|
13
|
+
urlUtils;
|
|
11
14
|
constructor(rawUrl) {
|
|
12
15
|
const match = rawUrl.match(URL_REGEX);
|
|
13
16
|
if (!match) {
|
|
14
17
|
throw new Error(`Invalid UrlImpl: ${rawUrl}`);
|
|
15
18
|
}
|
|
16
19
|
this.protocol = match[1];
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
20
|
+
this.username = match[3] || '';
|
|
21
|
+
this.password = match[4] || '';
|
|
22
|
+
this.hostname = match[6];
|
|
23
|
+
this.port = match[7] || '';
|
|
24
|
+
this.pathname = match[8] || '/';
|
|
25
|
+
this.search = match[9] || '';
|
|
26
|
+
this.hash = match[10] || '';
|
|
27
|
+
this.urlUtils = new CustomImplUrlUtils();
|
|
22
28
|
}
|
|
23
29
|
get href() {
|
|
24
|
-
return `${this.
|
|
30
|
+
return `${this.protocol}//${this.credentials}${this.host}${this.pathname}${this.search}${this.hash}`;
|
|
25
31
|
}
|
|
26
32
|
get origin() {
|
|
27
33
|
return `${this.protocol}//${this.host}`;
|
|
@@ -43,7 +49,7 @@ export class UrlImpl {
|
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
get searchParams() {
|
|
46
|
-
return new UrlSearchParamsImpl(
|
|
52
|
+
return new UrlSearchParamsImpl(this.urlUtils, this.search);
|
|
47
53
|
}
|
|
48
54
|
isDefaultPort(port) {
|
|
49
55
|
const PROTOCOL_TO_DEFAULT_PORT = {
|
|
@@ -52,4 +58,17 @@ export class UrlImpl {
|
|
|
52
58
|
};
|
|
53
59
|
return PROTOCOL_TO_DEFAULT_PORT[this.protocol] === port;
|
|
54
60
|
}
|
|
61
|
+
get credentials() {
|
|
62
|
+
if (!this.username && !this.password) {
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
let credentials = '';
|
|
66
|
+
if (this.username) {
|
|
67
|
+
credentials += this.urlUtils.encodeUriComponent(this.username);
|
|
68
|
+
}
|
|
69
|
+
if (this.password) {
|
|
70
|
+
credentials += `:${this.urlUtils.encodeUriComponent(this.password)}`;
|
|
71
|
+
}
|
|
72
|
+
return `${credentials}@`;
|
|
73
|
+
}
|
|
55
74
|
}
|
|
@@ -3,7 +3,7 @@ export class PostEnforceFlow extends CompositePhase {
|
|
|
3
3
|
constructor(config, { products, activityClient }) {
|
|
4
4
|
super([
|
|
5
5
|
new EnrichContextFromResponsePhase(config, products),
|
|
6
|
-
new ModifyOutgoingResponsePhase(Object.values(products)),
|
|
6
|
+
new ModifyOutgoingResponsePhase(config, Object.values(products)),
|
|
7
7
|
new SendAsyncActivitiesOnResponsePhase(activityClient),
|
|
8
8
|
]);
|
|
9
9
|
}
|
|
@@ -39,7 +39,7 @@ export class CreateBlockResponsePhase {
|
|
|
39
39
|
}
|
|
40
40
|
async addHeadersToResponse(response, context) {
|
|
41
41
|
if (!context.isMobile && context.pxhd?.source === PXHDSource.RISK) {
|
|
42
|
-
response = PXHDUtils.addPxhdToMinimalResponse(context, response);
|
|
42
|
+
response = PXHDUtils.addPxhdToMinimalResponse(this.config, context, response);
|
|
43
43
|
}
|
|
44
44
|
if (this.config.corsSupportEnabled && this.cors?.isCorsRequest(context)) {
|
|
45
45
|
const corsHeaders = await this.cors.getCorsBlockHeaders(context);
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { PXHDSource, PXHDUtils } from '../../pxhd';
|
|
2
2
|
export class ModifyOutgoingResponsePhase {
|
|
3
|
+
config;
|
|
3
4
|
products;
|
|
4
|
-
constructor(products) {
|
|
5
|
+
constructor(config, products) {
|
|
6
|
+
this.config = config;
|
|
5
7
|
this.products = products;
|
|
6
8
|
}
|
|
7
9
|
async execute(context) {
|
|
8
10
|
await Promise.all(this.products.map((product) => product?.modifyOutgoingResponse(context)));
|
|
9
11
|
if (context.pxhd?.source === PXHDSource.RISK) {
|
|
10
|
-
PXHDUtils.addPxhdToOutgoingResponse(context, context.response);
|
|
12
|
+
PXHDUtils.addPxhdToOutgoingResponse(this.config, context, context.response);
|
|
11
13
|
}
|
|
12
14
|
return { done: false };
|
|
13
15
|
}
|
|
@@ -31,6 +31,9 @@ export class DefaultBotDefenderFirstParty {
|
|
|
31
31
|
return { defaultResponse };
|
|
32
32
|
}
|
|
33
33
|
const url = this.getThirdPartySensorScriptUrl();
|
|
34
|
+
if (!url) {
|
|
35
|
+
return { defaultResponse };
|
|
36
|
+
}
|
|
34
37
|
const request = await this.getOutgoingRequest(url, context);
|
|
35
38
|
this.config.logger.debug(`proxying first party sensor script ${context.requestData.url.pathname} to ${url}`);
|
|
36
39
|
return { request, defaultResponse };
|
|
@@ -41,6 +44,9 @@ export class DefaultBotDefenderFirstParty {
|
|
|
41
44
|
return { defaultResponse };
|
|
42
45
|
}
|
|
43
46
|
const url = this.getThirdPartyXhrUrl(context, prefix);
|
|
47
|
+
if (!url) {
|
|
48
|
+
return { defaultResponse };
|
|
49
|
+
}
|
|
44
50
|
const request = await this.getOutgoingRequest(url, context);
|
|
45
51
|
this.config.logger.debug(`proxying first party XHR request ${context.requestData.url.pathname} to ${url}`);
|
|
46
52
|
return { request, defaultResponse };
|
|
@@ -59,13 +65,16 @@ export class DefaultBotDefenderFirstParty {
|
|
|
59
65
|
return { defaultResponse };
|
|
60
66
|
}
|
|
61
67
|
const url = this.getThirdPartyCaptchaScriptUrl(context);
|
|
68
|
+
if (!url) {
|
|
69
|
+
return { defaultResponse };
|
|
70
|
+
}
|
|
62
71
|
const request = await this.getOutgoingRequest(url, context);
|
|
63
72
|
this.config.logger.debug(`proxying first party captcha script ${context.requestData.url.pathname} to ${url}`);
|
|
64
73
|
return { request, defaultResponse };
|
|
65
74
|
}
|
|
66
75
|
async getOutgoingRequest(url, { requestData, vid }) {
|
|
67
76
|
return new OutgoingRequestImpl({
|
|
68
|
-
url: url,
|
|
77
|
+
url: url.href,
|
|
69
78
|
method: requestData.method,
|
|
70
79
|
headers: this.prepareFirstPartyHeaders(url, requestData, vid),
|
|
71
80
|
body: requestData.request.body,
|
|
@@ -89,8 +98,7 @@ export class DefaultBotDefenderFirstParty {
|
|
|
89
98
|
return headers;
|
|
90
99
|
}
|
|
91
100
|
setHostHeader(headers, url) {
|
|
92
|
-
|
|
93
|
-
headers[HOST_HEADER_NAME] = [host];
|
|
101
|
+
headers[HOST_HEADER_NAME] = [url.host];
|
|
94
102
|
}
|
|
95
103
|
setXffHeader(headers, ip) {
|
|
96
104
|
const xffValue = headers[X_FORWARDED_FOR_HEADER_NAME] || [];
|
|
@@ -101,16 +109,43 @@ export class DefaultBotDefenderFirstParty {
|
|
|
101
109
|
headers[X_PX_ENFORCER_TRUE_IP_HEADER_NAME] = [ip];
|
|
102
110
|
}
|
|
103
111
|
getThirdPartySensorScriptUrl() {
|
|
104
|
-
|
|
112
|
+
try {
|
|
113
|
+
return this.urlUtils.createUrl(`${this.config.backendClientUrl}/${this.config.appId}/main.min.js`);
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
this.config.logger.debug(`unable to create third party sensor URL: ${e}`);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
105
119
|
}
|
|
106
120
|
getThirdPartyCaptchaScriptUrl(context) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
121
|
+
try {
|
|
122
|
+
const originalUrl = context.requestData.url;
|
|
123
|
+
const { appId, backendCaptchaUrl } = this.config;
|
|
124
|
+
return this.urlUtils.createUrl(`${backendCaptchaUrl}/${appId}${FirstPartySuffix.CAPTCHA}.js${originalUrl.search}`);
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
this.config.logger.debug(`unable to create third party captcha URL: ${e}`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
110
130
|
}
|
|
111
131
|
getThirdPartyXhrUrl(context, prefix) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
132
|
+
try {
|
|
133
|
+
const originalUrl = context.requestData.url;
|
|
134
|
+
const pathname = originalUrl.pathname.replace(prefix, '');
|
|
135
|
+
const thirdPartyUrl = this.urlUtils.createUrl(`${this.config.backendCollectorUrl}${pathname}${originalUrl.search}`);
|
|
136
|
+
const { host } = this.urlUtils.createUrl(this.config.backendCollectorUrl);
|
|
137
|
+
if (!this.isValidThirdPartyUrl(thirdPartyUrl, host, pathname)) {
|
|
138
|
+
this.config.logger.debug(`invalid third party url: ${thirdPartyUrl}`);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return thirdPartyUrl;
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
this.config.logger.debug(`unable to create third party XHR URL: ${e}`);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
isValidThirdPartyUrl(url, expectedHost, expectedPath) {
|
|
149
|
+
return url.host.toLowerCase() === expectedHost.toLowerCase() && url.pathname.startsWith(expectedPath);
|
|
115
150
|
}
|
|
116
151
|
}
|
|
@@ -4,25 +4,26 @@ export var PXHDUtils;
|
|
|
4
4
|
(function (PXHDUtils) {
|
|
5
5
|
PXHDUtils.PXHD_SAMESITE_VALUE = 'Lax';
|
|
6
6
|
PXHDUtils.PXHD_PATH_VALUE = '/';
|
|
7
|
-
PXHDUtils.addPxhdToOutgoingResponse = (context, response) => {
|
|
7
|
+
PXHDUtils.addPxhdToOutgoingResponse = (config, context, response) => {
|
|
8
8
|
if (!context?.pxhd) {
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
|
-
const setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd);
|
|
11
|
+
const setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd, config.securedPxhdEnabled);
|
|
12
12
|
response.headers.append(SET_COOKIE_HEADER_NAME, setPxhdCookie);
|
|
13
13
|
};
|
|
14
|
-
PXHDUtils.addPxhdToMinimalResponse = (context, response) => {
|
|
14
|
+
PXHDUtils.addPxhdToMinimalResponse = (config, context, response) => {
|
|
15
15
|
if (context?.pxhd) {
|
|
16
|
-
const setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd);
|
|
16
|
+
const setPxhdCookie = PXHDUtils.getPxhdCookieValue(context.pxhd, config.securedPxhdEnabled);
|
|
17
17
|
return MinimalResponseUtils.appendHeader(response, SET_COOKIE_HEADER_NAME, setPxhdCookie);
|
|
18
18
|
}
|
|
19
19
|
return response;
|
|
20
20
|
};
|
|
21
|
-
PXHDUtils.getPxhdCookieValue = (pxhd) => {
|
|
21
|
+
PXHDUtils.getPxhdCookieValue = (pxhd, isSecure) => {
|
|
22
22
|
const value = `${PXHD_COOKIE_NAME}=${pxhd.value}`;
|
|
23
|
+
const secure = isSecure ? 'Secure' : '';
|
|
23
24
|
const domain = pxhd.domain && `domain=${pxhd.domain}`;
|
|
24
25
|
const path = `path=${PXHDUtils.PXHD_PATH_VALUE}`;
|
|
25
26
|
const sameSite = `SameSite=${PXHDUtils.PXHD_SAMESITE_VALUE}`;
|
|
26
|
-
return [value, domain, path, sameSite].filter(Boolean).join('; ');
|
|
27
|
+
return [value, secure, domain, path, sameSite].filter(Boolean).join('; ');
|
|
27
28
|
};
|
|
28
29
|
})(PXHDUtils || (PXHDUtils = {}));
|
|
@@ -9,5 +9,5 @@ export const X_PX_BYPASS_REASON_HEADER_NAME = 'x-px-bypass-reason';
|
|
|
9
9
|
export const PUSH_DATA_HMAC_HEADER_NAME = 'x-px-pushdata';
|
|
10
10
|
export const PUSH_DATA_FEATURE_HEADER_NAME = 'x-px-feature';
|
|
11
11
|
export const EMAIL_ADDRESS_REGEX = /^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,7}$/;
|
|
12
|
-
export const URL_REGEX = /^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/;
|
|
13
|
-
export const CORE_MODULE_VERSION = 'JS Core 0.
|
|
12
|
+
export const URL_REGEX = /^(https?\:)\/\/(([^@\s:]+):?([^@\s]*)@)?(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/;
|
|
13
|
+
export const CORE_MODULE_VERSION = 'JS Core 0.14.0';
|
|
@@ -337,6 +337,10 @@ export interface IConfiguration<Req, Res, ParamsType extends ConfigurationParams
|
|
|
337
337
|
* Whether reserved URL characters should be percent-decoded when parsing the incoming request URL.
|
|
338
338
|
*/
|
|
339
339
|
readonly urlDecodeReservedCharacters: boolean;
|
|
340
|
+
/**
|
|
341
|
+
* Whether to add the Secure attribute when setting the PXHD cookie.
|
|
342
|
+
*/
|
|
343
|
+
readonly securedPxhdEnabled: boolean;
|
|
340
344
|
/**
|
|
341
345
|
* Returns an object representation of the current configuration.
|
|
342
346
|
*/
|