perimeterx-js-core 0.23.3 → 0.24.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.
Files changed (50) hide show
  1. package/lib/cjs/activities/utils.js +4 -3
  2. package/lib/cjs/config/ConfigurationBase.js +14 -0
  3. package/lib/cjs/config/defaults/DefaultConfigurationParams.js +2 -0
  4. package/lib/cjs/enforcer/utils.js +6 -1
  5. package/lib/cjs/index.js +1 -0
  6. package/lib/cjs/phase/flow/PostEnforceFlow.js +2 -2
  7. package/lib/cjs/phase/impl/ModifyOutgoingResponsePhase.js +32 -1
  8. package/lib/cjs/snippet_injection/CustomSnippetFunction.js +2 -0
  9. package/lib/cjs/snippet_injection/index.js +20 -0
  10. package/lib/cjs/snippet_injection/snippet_injector/ISnippetInjector.js +2 -0
  11. package/lib/cjs/snippet_injection/snippet_injector/index.js +17 -0
  12. package/lib/cjs/snippet_injection/snippet_retriever/DefaultSnippetRetriever.js +95 -0
  13. package/lib/cjs/snippet_injection/snippet_retriever/ISnippetRetriever.js +2 -0
  14. package/lib/cjs/snippet_injection/snippet_retriever/index.js +18 -0
  15. package/lib/cjs/snippet_injection/utils.js +5 -0
  16. package/lib/cjs/utils/constants.js +1 -1
  17. package/lib/esm/activities/utils.js +4 -3
  18. package/lib/esm/config/ConfigurationBase.js +6 -0
  19. package/lib/esm/config/defaults/DefaultConfigurationParams.js +2 -0
  20. package/lib/esm/enforcer/utils.js +5 -0
  21. package/lib/esm/index.js +1 -0
  22. package/lib/esm/phase/flow/PostEnforceFlow.js +2 -2
  23. package/lib/esm/phase/impl/ModifyOutgoingResponsePhase.js +17 -1
  24. package/lib/esm/snippet_injection/CustomSnippetFunction.js +1 -0
  25. package/lib/esm/snippet_injection/index.js +4 -0
  26. package/lib/esm/snippet_injection/snippet_injector/ISnippetInjector.js +1 -0
  27. package/lib/esm/snippet_injection/snippet_injector/index.js +1 -0
  28. package/lib/esm/snippet_injection/snippet_retriever/DefaultSnippetRetriever.js +36 -0
  29. package/lib/esm/snippet_injection/snippet_retriever/ISnippetRetriever.js +1 -0
  30. package/lib/esm/snippet_injection/snippet_retriever/index.js +2 -0
  31. package/lib/esm/snippet_injection/utils.js +9 -0
  32. package/lib/esm/utils/constants.js +1 -1
  33. package/lib/types/activities/model/CommonActivityDetails.d.ts +1 -0
  34. package/lib/types/config/ConfigurationBase.d.ts +3 -0
  35. package/lib/types/config/IConfiguration.d.ts +9 -0
  36. package/lib/types/config/params/CoreConfigurationParams.d.ts +6 -1
  37. package/lib/types/enforcer/EnforcerOptions.d.ts +3 -0
  38. package/lib/types/index.d.ts +1 -0
  39. package/lib/types/phase/flow/PostEnforceFlow.d.ts +1 -1
  40. package/lib/types/phase/impl/ModifyOutgoingResponsePhase.d.ts +5 -1
  41. package/lib/types/snippet_injection/CustomSnippetFunction.d.ts +2 -0
  42. package/lib/types/snippet_injection/index.d.ts +4 -0
  43. package/lib/types/snippet_injection/snippet_injector/ISnippetInjector.d.ts +5 -0
  44. package/lib/types/snippet_injection/snippet_injector/index.d.ts +1 -0
  45. package/lib/types/snippet_injection/snippet_retriever/DefaultSnippetRetriever.d.ts +10 -0
  46. package/lib/types/snippet_injection/snippet_retriever/ISnippetRetriever.d.ts +5 -0
  47. package/lib/types/snippet_injection/snippet_retriever/index.d.ts +2 -0
  48. package/lib/types/snippet_injection/utils.d.ts +1 -0
  49. package/lib/types/utils/constants.d.ts +1 -1
  50. package/package.json +1 -1
@@ -86,9 +86,10 @@ function redactCookieSecret(secret) {
86
86
  return '***'.concat(secret.substring(secret.length - 3, secret.length));
87
87
  }
88
88
  var addConfigDataToDetails = function (details, config) {
89
- if (config.remoteConfigVersion) {
90
- details.remote_config_version = config.remoteConfigVersion;
91
- }
89
+ (0, utils_1.transferExistingProperties)(config, details, {
90
+ remoteConfigId: 'remote_config_id',
91
+ remoteConfigVersion: 'remote_config_version',
92
+ });
92
93
  };
93
94
  exports.addConfigDataToDetails = addConfigDataToDetails;
94
95
  var addCustomParametersToDetails = function (details, customParameters) {
@@ -717,6 +717,20 @@ var ConfigurationBase = /** @class */ (function () {
717
717
  enumerable: false,
718
718
  configurable: true
719
719
  });
720
+ Object.defineProperty(ConfigurationBase.prototype, "snippetInjectionEnabled", {
721
+ get: function () {
722
+ return this.configParams.px_snippet_injection_enabled;
723
+ },
724
+ enumerable: false,
725
+ configurable: true
726
+ });
727
+ Object.defineProperty(ConfigurationBase.prototype, "createCustomSnippet", {
728
+ get: function () {
729
+ return this.configParams.px_create_custom_snippet;
730
+ },
731
+ enumerable: false,
732
+ configurable: true
733
+ });
720
734
  Object.defineProperty(ConfigurationBase.prototype, "enableBlockedUrlOnCaptchaBlockPage", {
721
735
  get: function () {
722
736
  return true;
@@ -130,6 +130,8 @@ var defaultConfigurationParams = function () { return ({
130
130
  px_remote_config_retry_interval_ms: 1000,
131
131
  px_url_decode_reserved_characters: false,
132
132
  px_secured_pxhd_enabled: false,
133
+ px_snippet_injection_enabled: false,
134
+ px_create_custom_snippet: null,
133
135
  px_custom_is_sensitive_request: null,
134
136
  px_custom_is_monitored_request: null,
135
137
  px_custom_is_enforced_request: null,
@@ -23,8 +23,9 @@ var activities_1 = require("../activities");
23
23
  var logger_1 = require("../logger");
24
24
  var products_1 = require("../products");
25
25
  var utils_1 = require("../utils");
26
+ var snippet_injection_1 = require("../snippet_injection");
26
27
  var createEnforcerInitializationBlock = function (config, options) {
27
- var _a, _b;
28
+ var _a, _b, _c;
28
29
  var tokenVersion = config.tokenVersion;
29
30
  if (!(0, utils_1.isValidTokenVersion)(tokenVersion)) {
30
31
  throw new utils_1.EnforcerError("error initializing enforcer: token version ".concat(tokenVersion, " is invalid (must be one of ").concat(Object.values(risk_token_1.TokenVersion)
@@ -65,6 +66,8 @@ var createEnforcerInitializationBlock = function (config, options) {
65
66
  hmacUtils: hmacUtils,
66
67
  })
67
68
  : null);
69
+ var snippetInjector = (_c = options.snippetInjector) !== null && _c !== void 0 ? _c : null;
70
+ var snippetRetriever = options.snippetRetriever || (snippetInjector ? new snippet_injection_1.DefaultSnippetRetriever(config) : null);
68
71
  var allOptions = {
69
72
  httpClient: httpClient,
70
73
  base64Utils: base64Utils,
@@ -84,6 +87,8 @@ var createEnforcerInitializationBlock = function (config, options) {
84
87
  remoteConfigStorageClient: remoteConfigStorageClient,
85
88
  remoteConfigServiceClient: remoteConfigServiceClient,
86
89
  remoteConfigUpdater: remoteConfigUpdater,
90
+ snippetInjector: snippetInjector,
91
+ snippetRetriever: snippetRetriever,
87
92
  };
88
93
  var products = (0, exports.createEnforcerProducts)(config, options.products, base64Utils, hashUtils, urlUtils, ipRangeChecker);
89
94
  return __assign({ products: products }, allOptions);
package/lib/cjs/index.js CHANGED
@@ -23,6 +23,7 @@ __exportStar(require("./context"), exports);
23
23
  __exportStar(require("./custom_parameters"), exports);
24
24
  __exportStar(require("./sensitive_request"), exports);
25
25
  __exportStar(require("./monitored_request"), exports);
26
+ __exportStar(require("./snippet_injection"), exports);
26
27
  __exportStar(require("./cors"), exports);
27
28
  __exportStar(require("./enforcer"), exports);
28
29
  __exportStar(require("./filter"), exports);
@@ -20,10 +20,10 @@ var impl_1 = require("../impl");
20
20
  var PostEnforceFlow = /** @class */ (function (_super) {
21
21
  __extends(PostEnforceFlow, _super);
22
22
  function PostEnforceFlow(config, _a) {
23
- var products = _a.products, activityClient = _a.activityClient;
23
+ var products = _a.products, activityClient = _a.activityClient, snippetRetriever = _a.snippetRetriever, snippetInjector = _a.snippetInjector;
24
24
  return _super.call(this, [
25
25
  new impl_1.EnrichContextFromResponsePhase(config, products),
26
- new impl_1.ModifyOutgoingResponsePhase(config, Object.values(products)),
26
+ new impl_1.ModifyOutgoingResponsePhase(config, Object.values(products), snippetRetriever, snippetInjector),
27
27
  new impl_1.SendAsyncActivitiesOnResponsePhase(activityClient),
28
28
  ]) || this;
29
29
  }
@@ -38,10 +38,13 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.ModifyOutgoingResponsePhase = void 0;
40
40
  var pxhd_1 = require("../../pxhd");
41
+ var http_1 = require("../../http");
41
42
  var ModifyOutgoingResponsePhase = /** @class */ (function () {
42
- function ModifyOutgoingResponsePhase(config, products) {
43
+ function ModifyOutgoingResponsePhase(config, products, snippetRetriever, snippetInjector) {
43
44
  this.config = config;
44
45
  this.products = products;
46
+ this.snippetRetriever = snippetRetriever;
47
+ this.snippetInjector = snippetInjector;
45
48
  }
46
49
  ModifyOutgoingResponsePhase.prototype.execute = function (context) {
47
50
  return __awaiter(this, void 0, void 0, function () {
@@ -54,11 +57,39 @@ var ModifyOutgoingResponsePhase = /** @class */ (function () {
54
57
  if (((_a = context.pxhd) === null || _a === void 0 ? void 0 : _a.source) === pxhd_1.PXHDSource.RISK && context.response) {
55
58
  pxhd_1.PXHDUtils.addPxhdToOutgoingResponse(this.config, context, context.response);
56
59
  }
60
+ return [4 /*yield*/, this.handleSnippetInjection(context)];
61
+ case 2:
62
+ _b.sent();
57
63
  return [2 /*return*/, { done: false }];
58
64
  }
59
65
  });
60
66
  });
61
67
  };
68
+ ModifyOutgoingResponsePhase.prototype.handleSnippetInjection = function (context) {
69
+ return __awaiter(this, void 0, void 0, function () {
70
+ var snippet, _a;
71
+ var _b, _c, _d;
72
+ return __generator(this, function (_e) {
73
+ switch (_e.label) {
74
+ case 0:
75
+ if (!(this.snippetRetriever &&
76
+ this.snippetInjector &&
77
+ this.config.snippetInjectionEnabled &&
78
+ !!((_d = (_c = (_b = context.response) === null || _b === void 0 ? void 0 : _b.headers) === null || _c === void 0 ? void 0 : _c.get(http_1.CONTENT_TYPE_HEADER_NAME)) === null || _d === void 0 ? void 0 : _d.includes(http_1.ContentType.TEXT_HTML)))) return [3 /*break*/, 3];
79
+ context.logger.debug('snippet injection enabled');
80
+ return [4 /*yield*/, this.snippetRetriever.retrieveSnippet(context)];
81
+ case 1:
82
+ snippet = _e.sent();
83
+ _a = context;
84
+ return [4 /*yield*/, this.snippetInjector.injectSnippet(context.response, snippet)];
85
+ case 2:
86
+ _a.response = _e.sent();
87
+ _e.label = 3;
88
+ case 3: return [2 /*return*/];
89
+ }
90
+ });
91
+ });
92
+ };
62
93
  return ModifyOutgoingResponsePhase;
63
94
  }());
64
95
  exports.ModifyOutgoingResponsePhase = ModifyOutgoingResponsePhase;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./snippet_injector"), exports);
18
+ __exportStar(require("./snippet_retriever"), exports);
19
+ __exportStar(require("./utils"), exports);
20
+ __exportStar(require("./CustomSnippetFunction"), exports);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./ISnippetInjector"), exports);
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.DefaultSnippetRetriever = void 0;
40
+ var products_1 = require("../../products");
41
+ var utils_1 = require("../utils");
42
+ var DefaultSnippetRetriever = /** @class */ (function () {
43
+ function DefaultSnippetRetriever(config) {
44
+ this.config = config;
45
+ }
46
+ DefaultSnippetRetriever.prototype.retrieveSnippet = function (context) {
47
+ return __awaiter(this, void 0, void 0, function () {
48
+ return __generator(this, function (_a) {
49
+ switch (_a.label) {
50
+ case 0: return [4 /*yield*/, this.retrieveCustomSnippet(context)];
51
+ case 1: return [2 /*return*/, (_a.sent()) || this.retrieveDefaultSnippet(context)];
52
+ }
53
+ });
54
+ });
55
+ };
56
+ DefaultSnippetRetriever.prototype.retrieveCustomSnippet = function (context) {
57
+ return __awaiter(this, void 0, void 0, function () {
58
+ var snippet, e_1;
59
+ return __generator(this, function (_a) {
60
+ switch (_a.label) {
61
+ case 0:
62
+ if (!this.config.createCustomSnippet) {
63
+ return [2 /*return*/, null];
64
+ }
65
+ _a.label = 1;
66
+ case 1:
67
+ _a.trys.push([1, 3, , 4]);
68
+ return [4 /*yield*/, this.config.createCustomSnippet(this.config.getActiveConfig(), context.requestData.request.getUnderlyingRequest(), context.response.getUnderlyingResponse())];
69
+ case 2:
70
+ snippet = _a.sent();
71
+ if (!snippet || typeof snippet !== 'string') {
72
+ context.logger.debug("invalid custom snippet of type ".concat(typeof snippet));
73
+ return [2 /*return*/, null];
74
+ }
75
+ context.logger.debug('retrieving custom snippet');
76
+ return [2 /*return*/, snippet];
77
+ case 3:
78
+ e_1 = _a.sent();
79
+ context.logger.debug("failed retrieving custom snippet: ".concat(e_1));
80
+ return [3 /*break*/, 4];
81
+ case 4: return [2 /*return*/, null];
82
+ }
83
+ });
84
+ });
85
+ };
86
+ DefaultSnippetRetriever.prototype.retrieveDefaultSnippet = function (context) {
87
+ var sensorSrc = this.config.firstPartyEnabled
88
+ ? (0, products_1.getMostCustomizedFirstPartyPath)(this.config, products_1.FirstPartySuffix.SENSOR)
89
+ : "".concat(this.config.backendClientUrl, "/").concat(this.config.appId, "/main.min.js");
90
+ context.logger.debug("retrieving default snippet with src ".concat(sensorSrc));
91
+ return (0, utils_1.createDefaultSnippet)(this.config.appId, sensorSrc);
92
+ };
93
+ return DefaultSnippetRetriever;
94
+ }());
95
+ exports.DefaultSnippetRetriever = DefaultSnippetRetriever;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./ISnippetRetriever"), exports);
18
+ __exportStar(require("./DefaultSnippetRetriever"), exports);
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDefaultSnippet = void 0;
4
+ var createDefaultSnippet = function (appId, sensorSrc) { return "<script type=\"text/javascript\">\n (function(){\n window._pxAppId = '".concat(appId, "';\n var p = document.getElementsByTagName('script')[0], s = document.createElement('script');\n s.async = 1;\n s.src = '").concat(sensorSrc, "';\n p.parentNode.insertBefore(s,p);\n }());\n</script>"); };
5
+ exports.createDefaultSnippet = createDefaultSnippet;
@@ -14,4 +14,4 @@ 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
15
  exports.URL_REGEX = /^(https?:)\/\/(([^@\s:\/]+):?([^@\s\/]*)@)?(([^:\/?#]*)(?:\:([0-9]+))?)(\/?[^?#]*)(\?[^#]*|)(#.*|)$/;
16
16
  exports.REGEX_STRUCTURE = /^\/(.+?)\/([gimsuyvd]*)$/;
17
- exports.CORE_MODULE_VERSION = 'JS Core 0.23.3';
17
+ exports.CORE_MODULE_VERSION = 'JS Core 0.24.0';
@@ -75,9 +75,10 @@ export function redactCookieSecret(secret) {
75
75
  return '***'.concat(secret.substring(secret.length - 3, secret.length));
76
76
  }
77
77
  export const addConfigDataToDetails = (details, config) => {
78
- if (config.remoteConfigVersion) {
79
- details.remote_config_version = config.remoteConfigVersion;
80
- }
78
+ transferExistingProperties(config, details, {
79
+ remoteConfigId: 'remote_config_id',
80
+ remoteConfigVersion: 'remote_config_version',
81
+ });
81
82
  };
82
83
  export const addCustomParametersToDetails = (details, customParameters) => {
83
84
  if (customParameters) {
@@ -363,6 +363,12 @@ export class ConfigurationBase {
363
363
  get tokenVersion() {
364
364
  return this.configParams.px_token_version;
365
365
  }
366
+ get snippetInjectionEnabled() {
367
+ return this.configParams.px_snippet_injection_enabled;
368
+ }
369
+ get createCustomSnippet() {
370
+ return this.configParams.px_create_custom_snippet;
371
+ }
366
372
  get enableBlockedUrlOnCaptchaBlockPage() {
367
373
  return true;
368
374
  }
@@ -127,6 +127,8 @@ export const defaultConfigurationParams = () => ({
127
127
  px_remote_config_retry_interval_ms: 1000,
128
128
  px_url_decode_reserved_characters: false,
129
129
  px_secured_pxhd_enabled: false,
130
+ px_snippet_injection_enabled: false,
131
+ px_create_custom_snippet: null,
130
132
  px_custom_is_sensitive_request: null,
131
133
  px_custom_is_monitored_request: null,
132
134
  px_custom_is_enforced_request: null,
@@ -9,6 +9,7 @@ import { HttpActivityClient, HttpBatchedActivityClient } from '../activities';
9
9
  import { HttpLogServiceClient } from '../logger';
10
10
  import { AccountDefender, BotDefender, CredentialIntelligence, HypeSaleChallenge, ProductName, } from '../products';
11
11
  import { EnforcerError, isValidTokenVersion } from '../utils';
12
+ import { DefaultSnippetRetriever } from '../snippet_injection';
12
13
  export const createEnforcerInitializationBlock = (config, options) => {
13
14
  const tokenVersion = config.tokenVersion;
14
15
  if (!isValidTokenVersion(tokenVersion)) {
@@ -50,6 +51,8 @@ export const createEnforcerInitializationBlock = (config, options) => {
50
51
  hmacUtils,
51
52
  })
52
53
  : null);
54
+ const snippetInjector = options.snippetInjector ?? null;
55
+ const snippetRetriever = options.snippetRetriever || (snippetInjector ? new DefaultSnippetRetriever(config) : null);
53
56
  const allOptions = {
54
57
  httpClient,
55
58
  base64Utils,
@@ -69,6 +72,8 @@ export const createEnforcerInitializationBlock = (config, options) => {
69
72
  remoteConfigStorageClient,
70
73
  remoteConfigServiceClient,
71
74
  remoteConfigUpdater,
75
+ snippetInjector,
76
+ snippetRetriever,
72
77
  };
73
78
  const products = createEnforcerProducts(config, options.products, base64Utils, hashUtils, urlUtils, ipRangeChecker);
74
79
  return { products, ...allOptions };
package/lib/esm/index.js CHANGED
@@ -7,6 +7,7 @@ export * from './context';
7
7
  export * from './custom_parameters';
8
8
  export * from './sensitive_request';
9
9
  export * from './monitored_request';
10
+ export * from './snippet_injection';
10
11
  export * from './cors';
11
12
  export * from './enforcer';
12
13
  export * from './filter';
@@ -1,9 +1,9 @@
1
1
  import { CompositePhase, EnrichContextFromResponsePhase, ModifyOutgoingResponsePhase, SendAsyncActivitiesOnResponsePhase, } from '../impl';
2
2
  export class PostEnforceFlow extends CompositePhase {
3
- constructor(config, { products, activityClient, }) {
3
+ constructor(config, { products, activityClient, snippetRetriever, snippetInjector, }) {
4
4
  super([
5
5
  new EnrichContextFromResponsePhase(config, products),
6
- new ModifyOutgoingResponsePhase(config, Object.values(products)),
6
+ new ModifyOutgoingResponsePhase(config, Object.values(products), snippetRetriever, snippetInjector),
7
7
  new SendAsyncActivitiesOnResponsePhase(activityClient),
8
8
  ]);
9
9
  }
@@ -1,16 +1,32 @@
1
1
  import { PXHDSource, PXHDUtils } from '../../pxhd';
2
+ import { CONTENT_TYPE_HEADER_NAME, ContentType } from '../../http';
2
3
  export class ModifyOutgoingResponsePhase {
3
4
  config;
4
5
  products;
5
- constructor(config, products) {
6
+ snippetRetriever;
7
+ snippetInjector;
8
+ constructor(config, products, snippetRetriever, snippetInjector) {
6
9
  this.config = config;
7
10
  this.products = products;
11
+ this.snippetRetriever = snippetRetriever;
12
+ this.snippetInjector = snippetInjector;
8
13
  }
9
14
  async execute(context) {
10
15
  await Promise.all(this.products.map((product) => product?.modifyOutgoingResponse(context)));
11
16
  if (context.pxhd?.source === PXHDSource.RISK && context.response) {
12
17
  PXHDUtils.addPxhdToOutgoingResponse(this.config, context, context.response);
13
18
  }
19
+ await this.handleSnippetInjection(context);
14
20
  return { done: false };
15
21
  }
22
+ async handleSnippetInjection(context) {
23
+ if (this.snippetRetriever &&
24
+ this.snippetInjector &&
25
+ this.config.snippetInjectionEnabled &&
26
+ !!context.response?.headers?.get(CONTENT_TYPE_HEADER_NAME)?.includes(ContentType.TEXT_HTML)) {
27
+ context.logger.debug('snippet injection enabled');
28
+ const snippet = await this.snippetRetriever.retrieveSnippet(context);
29
+ context.response = await this.snippetInjector.injectSnippet(context.response, snippet);
30
+ }
31
+ }
16
32
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export * from './snippet_injector';
2
+ export * from './snippet_retriever';
3
+ export * from './utils';
4
+ export * from './CustomSnippetFunction';
@@ -0,0 +1 @@
1
+ export * from './ISnippetInjector';
@@ -0,0 +1,36 @@
1
+ import { FirstPartySuffix, getMostCustomizedFirstPartyPath } from '../../products';
2
+ import { createDefaultSnippet } from '../utils';
3
+ export class DefaultSnippetRetriever {
4
+ config;
5
+ constructor(config) {
6
+ this.config = config;
7
+ }
8
+ async retrieveSnippet(context) {
9
+ return (await this.retrieveCustomSnippet(context)) || this.retrieveDefaultSnippet(context);
10
+ }
11
+ async retrieveCustomSnippet(context) {
12
+ if (!this.config.createCustomSnippet) {
13
+ return null;
14
+ }
15
+ try {
16
+ const snippet = await this.config.createCustomSnippet(this.config.getActiveConfig(), context.requestData.request.getUnderlyingRequest(), context.response.getUnderlyingResponse());
17
+ if (!snippet || typeof snippet !== 'string') {
18
+ context.logger.debug(`invalid custom snippet of type ${typeof snippet}`);
19
+ return null;
20
+ }
21
+ context.logger.debug('retrieving custom snippet');
22
+ return snippet;
23
+ }
24
+ catch (e) {
25
+ context.logger.debug(`failed retrieving custom snippet: ${e}`);
26
+ }
27
+ return null;
28
+ }
29
+ retrieveDefaultSnippet(context) {
30
+ const sensorSrc = this.config.firstPartyEnabled
31
+ ? getMostCustomizedFirstPartyPath(this.config, FirstPartySuffix.SENSOR)
32
+ : `${this.config.backendClientUrl}/${this.config.appId}/main.min.js`;
33
+ context.logger.debug(`retrieving default snippet with src ${sensorSrc}`);
34
+ return createDefaultSnippet(this.config.appId, sensorSrc);
35
+ }
36
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ISnippetRetriever';
2
+ export * from './DefaultSnippetRetriever';
@@ -0,0 +1,9 @@
1
+ export const createDefaultSnippet = (appId, sensorSrc) => `<script type="text/javascript">
2
+ (function(){
3
+ window._pxAppId = '${appId}';
4
+ var p = document.getElementsByTagName('script')[0], s = document.createElement('script');
5
+ s.async = 1;
6
+ s.src = '${sensorSrc}';
7
+ p.parentNode.insertBefore(s,p);
8
+ }());
9
+ </script>`;
@@ -11,4 +11,4 @@ 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
12
  export const URL_REGEX = /^(https?:)\/\/(([^@\s:\/]+):?([^@\s\/]*)@)?(([^:\/?#]*)(?:\:([0-9]+))?)(\/?[^?#]*)(\?[^#]*|)(#.*|)$/;
13
13
  export const REGEX_STRUCTURE = /^\/(.+?)\/([gimsuyvd]*)$/;
14
- export const CORE_MODULE_VERSION = 'JS Core 0.23.3';
14
+ export const CORE_MODULE_VERSION = 'JS Core 0.24.0';
@@ -39,6 +39,7 @@ export type CommonActivityDetails = {
39
39
  ci_version?: CredentialIntelligenceVersion;
40
40
  sso_step?: SsoStep;
41
41
  remote_config_version?: number;
42
+ remote_config_id?: string;
42
43
  enforcer_start_time: number;
43
44
  raw_url?: string;
44
45
  used_cookie_secret?: string;
@@ -9,6 +9,7 @@ import { CredentialEndpointConfiguration, CredentialIntelligenceVersion, CustomL
9
9
  import { CustomRequestFunction } from './CustomRequestFunction';
10
10
  import { ExtractGraphQLKeywordsFunction } from '../graphql';
11
11
  import { TokenVersion } from '../risk_token';
12
+ import { CustomSnippetFunction } from '../snippet_injection';
12
13
  export declare abstract class ConfigurationBase<Req, Res, Added, Removed extends string> implements IConfiguration<Req, Res, Added, Removed> {
13
14
  protected configParams: RequiredAllConfigurationParams<Req, Res, Added, Removed>;
14
15
  protected readonly staticConfigParams: StaticConfigurationParams<Req, Res, Added, Removed>;
@@ -115,5 +116,7 @@ export declare abstract class ConfigurationBase<Req, Res, Added, Removed extends
115
116
  get urlDecodeReservedCharacters(): boolean;
116
117
  get securedPxhdEnabled(): boolean;
117
118
  get tokenVersion(): `${TokenVersion}`;
119
+ get snippetInjectionEnabled(): boolean;
120
+ get createCustomSnippet(): CustomSnippetFunction<Req, Res, Added, Removed> | null;
118
121
  get enableBlockedUrlOnCaptchaBlockPage(): boolean;
119
122
  }
@@ -8,6 +8,7 @@ import { CredentialEndpointConfiguration, CredentialIntelligenceVersion, CustomL
8
8
  import { CustomRequestFunction } from './CustomRequestFunction';
9
9
  import { ExtractGraphQLKeywordsFunction } from '../graphql';
10
10
  import { TokenVersion } from '../risk_token';
11
+ import { CustomSnippetFunction } from '../snippet_injection';
11
12
  export interface IConfiguration<Req, Res, Added, Removed> {
12
13
  /**
13
14
  * The application ID.
@@ -370,6 +371,14 @@ export interface IConfiguration<Req, Res, Added, Removed> {
370
371
  * Whether to add the Secure attribute when setting the PXHD cookie.
371
372
  */
372
373
  readonly securedPxhdEnabled: boolean;
374
+ /**
375
+ * Whether snippet injection on response bodies is enabled.
376
+ */
377
+ readonly snippetInjectionEnabled: boolean;
378
+ /**
379
+ * A custom function that returns a string representing the snippet to be injected.
380
+ */
381
+ readonly createCustomSnippet: CustomSnippetFunction<Req, Res, Added, Removed> | null;
373
382
  /**
374
383
  * Whether to include the request url in captcha block page
375
384
  */
@@ -7,6 +7,7 @@ import { CustomParametersFunction } from '../../custom_parameters';
7
7
  import { CustomBlockResponseHeadersHandler, CustomPreflightHandler } from '../../cors';
8
8
  import { CustomRequestFunction } from '../CustomRequestFunction';
9
9
  import { TokenVersion } from '../../risk_token';
10
+ import { CustomSnippetFunction } from '../../snippet_injection';
10
11
  export type StaticConfigurationParamsOnly = {
11
12
  px_app_id: string;
12
13
  px_cookie_secret: string | string[];
@@ -34,7 +35,11 @@ export type TokenV3ConfigurationParamsOnly = {
34
35
  px_risk_cookie_min_iterations?: number;
35
36
  px_risk_cookie_max_iterations?: number;
36
37
  };
37
- export type CommonConfigurationParams<Req, Res, Added, Removed> = TokenV3ConfigurationParamsOnly & BatchedActivitiesConfigParamsOnly & {
38
+ export type SnippetInjectionParamsOnly<Req, Res, Added, Removed> = {
39
+ px_snippet_injection_enabled?: boolean;
40
+ px_create_custom_snippet?: CustomSnippetFunction<Req, Res, Added, Removed> | null;
41
+ };
42
+ export type CommonConfigurationParams<Req, Res, Added, Removed> = TokenV3ConfigurationParamsOnly & BatchedActivitiesConfigParamsOnly & SnippetInjectionParamsOnly<Req, Res, Added, Removed> & {
38
43
  px_s2s_timeout?: number;
39
44
  px_backend_url?: string;
40
45
  px_user_agent_max_length?: number;
@@ -10,6 +10,7 @@ import { Products } from '../products';
10
10
  import { IGraphQLParser } from '../graphql';
11
11
  import { ILogServiceClient } from '../logger';
12
12
  import { IRemoteConfigStorageClient, IRemoteConfigServiceClient, IRemoteConfigUpdater } from '../config';
13
+ import { ISnippetInjector, ISnippetRetriever } from '../snippet_injection';
13
14
  export type EnforcerOptions<Req, Res, Added, Removed> = {
14
15
  httpClient: IHttpClient;
15
16
  base64Utils: IBase64Utils;
@@ -30,4 +31,6 @@ export type EnforcerOptions<Req, Res, Added, Removed> = {
30
31
  remoteConfigUpdater?: IRemoteConfigUpdater<Req, Res> | null;
31
32
  remoteConfigStorageClient?: IRemoteConfigStorageClient<Req, Res, Added, Removed> | null;
32
33
  remoteConfigServiceClient?: IRemoteConfigServiceClient<Req, Res, Added, Removed> | null;
34
+ snippetRetriever?: ISnippetRetriever<Req, Res> | null;
35
+ snippetInjector?: ISnippetInjector<Res> | null;
33
36
  };
@@ -7,6 +7,7 @@ export * from './context';
7
7
  export * from './custom_parameters';
8
8
  export * from './sensitive_request';
9
9
  export * from './monitored_request';
10
+ export * from './snippet_injection';
10
11
  export * from './cors';
11
12
  export * from './enforcer';
12
13
  export * from './filter';
@@ -2,5 +2,5 @@ import { IConfiguration } from '../../config';
2
2
  import { EnforcerOptions } from '../../enforcer';
3
3
  import { CompositePhase } from '../impl';
4
4
  export declare class PostEnforceFlow<Req, Res, Added, Removed> extends CompositePhase<Req, Res> {
5
- constructor(config: IConfiguration<Req, Res, Added, Removed>, { products, activityClient, }: Pick<Required<EnforcerOptions<Req, Res, Added, Removed>>, 'products' | 'activityClient'>);
5
+ constructor(config: IConfiguration<Req, Res, Added, Removed>, { products, activityClient, snippetRetriever, snippetInjector, }: Pick<Required<EnforcerOptions<Req, Res, Added, Removed>>, 'products' | 'activityClient' | 'snippetRetriever' | 'snippetInjector'>);
6
6
  }
@@ -3,9 +3,13 @@ import { IProduct } from '../../products';
3
3
  import { IPhase } from '../IPhase';
4
4
  import { PhaseResult } from '../PhaseResult';
5
5
  import { IConfiguration } from '../../config';
6
+ import { ISnippetInjector, ISnippetRetriever } from '../../snippet_injection';
6
7
  export declare class ModifyOutgoingResponsePhase<Req, Res, Added, Removed> implements IPhase<Req, Res> {
7
8
  protected readonly config: IConfiguration<Req, Res, Added, Removed>;
8
9
  protected readonly products: IProduct<any, Req, Res>[];
9
- constructor(config: IConfiguration<Req, Res, Added, Removed>, products: IProduct<any, Req, Res>[]);
10
+ protected readonly snippetRetriever?: ISnippetRetriever<Req, Res> | null;
11
+ protected readonly snippetInjector?: ISnippetInjector<Res> | null;
12
+ constructor(config: IConfiguration<Req, Res, Added, Removed>, products: IProduct<any, Req, Res>[], snippetRetriever?: ISnippetRetriever<Req, Res> | null, snippetInjector?: ISnippetInjector<Res> | null);
10
13
  execute(context: IContext<Req, Res>): Promise<PhaseResult>;
14
+ protected handleSnippetInjection(context: IContext<Req, Res>): Promise<void>;
11
15
  }
@@ -0,0 +1,2 @@
1
+ import { ActiveConfigurationParams } from '../config';
2
+ export type CustomSnippetFunction<Req, Res, Added, Removed> = (config: ActiveConfigurationParams<Req, Res, Added, Removed>, request: Req, response: Res) => string | Promise<string>;
@@ -0,0 +1,4 @@
1
+ export * from './snippet_injector';
2
+ export * from './snippet_retriever';
3
+ export * from './utils';
4
+ export * from './CustomSnippetFunction';
@@ -0,0 +1,5 @@
1
+ import { AsyncOrSync } from 'ts-essentials';
2
+ import { IOutgoingResponse } from '../../http';
3
+ export interface ISnippetInjector<Res> {
4
+ injectSnippet(response: IOutgoingResponse<Res>, snippet: string): AsyncOrSync<IOutgoingResponse<Res>>;
5
+ }
@@ -0,0 +1 @@
1
+ export * from './ISnippetInjector';
@@ -0,0 +1,10 @@
1
+ import { IConfiguration } from '../../config';
2
+ import { ReadonlyContext } from '../../context';
3
+ import { ISnippetRetriever } from './ISnippetRetriever';
4
+ export declare class DefaultSnippetRetriever<Req, Res, Added, Removed> implements ISnippetRetriever<Req, Res> {
5
+ protected readonly config: IConfiguration<Req, Res, Added, Removed>;
6
+ constructor(config: IConfiguration<Req, Res, Added, Removed>);
7
+ retrieveSnippet(context: ReadonlyContext<Req, Res>): Promise<string>;
8
+ protected retrieveCustomSnippet(context: ReadonlyContext<Req, Res>): Promise<string | null>;
9
+ protected retrieveDefaultSnippet(context: ReadonlyContext<Req, Res>): string;
10
+ }
@@ -0,0 +1,5 @@
1
+ import { AsyncOrSync } from 'ts-essentials';
2
+ import { ReadonlyContext } from '../../context';
3
+ export interface ISnippetRetriever<Req, Res> {
4
+ retrieveSnippet(context: ReadonlyContext<Req, Res>): AsyncOrSync<string>;
5
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ISnippetRetriever';
2
+ export * from './DefaultSnippetRetriever';
@@ -0,0 +1 @@
1
+ export declare const createDefaultSnippet: (appId: string, sensorSrc: string) => string;
@@ -11,4 +11,4 @@ export declare const PUSH_DATA_FEATURE_HEADER_NAME = "x-px-feature";
11
11
  export declare const EMAIL_ADDRESS_REGEX: RegExp;
12
12
  export declare const URL_REGEX: RegExp;
13
13
  export declare const REGEX_STRUCTURE: RegExp;
14
- export declare const CORE_MODULE_VERSION = "JS Core 0.23.3";
14
+ export declare const CORE_MODULE_VERSION = "JS Core 0.24.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perimeterx-js-core",
3
- "version": "0.23.3",
3
+ "version": "0.24.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "typesVersions": {