perimeterx-js-core 1.0.1 → 1.1.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 (60) hide show
  1. package/lib/cjs/action/Action.js +8 -0
  2. package/lib/cjs/action/ActionPriorityOrder.js +2 -0
  3. package/lib/cjs/activities/ActivityType.js +1 -0
  4. package/lib/cjs/activities/HttpActivityClient.js +28 -7
  5. package/lib/cjs/activities/utils.js +20 -1
  6. package/lib/cjs/blocker/model/BlockAction.js +1 -0
  7. package/lib/cjs/flow/EnforceFlow.js +1 -0
  8. package/lib/cjs/phase/impl/CreateRedirectResponsePhase.js +100 -0
  9. package/lib/cjs/phase/impl/RiskApiPhase.js +4 -0
  10. package/lib/cjs/phase/impl/SendAsyncActivitiesPhase.js +7 -1
  11. package/lib/cjs/phase/impl/index.js +1 -0
  12. package/lib/cjs/products/bot_defender/BotDefender.js +16 -3
  13. package/lib/cjs/products/bot_defender/reasons/BotDefenderRedirectReason.js +7 -0
  14. package/lib/cjs/products/bot_defender/reasons/index.js +1 -0
  15. package/lib/cjs/risk_api/model/GetRiskResponseHeaders.js +3 -0
  16. package/lib/cjs/risk_api/risk_response/RiskResponseBase.js +27 -0
  17. package/lib/cjs/risk_api/risk_response/serialize/SerializedRiskResponse.js +3 -0
  18. package/lib/cjs/risk_api/risk_response/v2/GetRiskResponseV2.js +6 -0
  19. package/lib/cjs/utils/constants.js +1 -1
  20. package/lib/esm/action/Action.js +8 -0
  21. package/lib/esm/action/ActionPriorityOrder.js +2 -0
  22. package/lib/esm/activities/ActivityType.js +1 -0
  23. package/lib/esm/activities/HttpActivityClient.js +28 -7
  24. package/lib/esm/activities/utils.js +18 -0
  25. package/lib/esm/blocker/model/BlockAction.js +1 -0
  26. package/lib/esm/context/DefaultContext.js +1 -0
  27. package/lib/esm/flow/EnforceFlow.js +2 -1
  28. package/lib/esm/phase/impl/CreateRedirectResponsePhase.js +40 -0
  29. package/lib/esm/phase/impl/RiskApiPhase.js +4 -0
  30. package/lib/esm/phase/impl/SendAsyncActivitiesPhase.js +7 -1
  31. package/lib/esm/phase/impl/index.js +1 -0
  32. package/lib/esm/products/bot_defender/BotDefender.js +16 -4
  33. package/lib/esm/products/bot_defender/reasons/BotDefenderRedirectReason.js +4 -0
  34. package/lib/esm/products/bot_defender/reasons/index.js +1 -0
  35. package/lib/esm/risk_api/model/GetRiskResponseHeaders.js +3 -0
  36. package/lib/esm/risk_api/risk_response/RiskResponseBase.js +12 -0
  37. package/lib/esm/risk_api/risk_response/serialize/SerializedRiskResponse.js +6 -0
  38. package/lib/esm/risk_api/risk_response/v2/GetRiskResponseV2.js +6 -0
  39. package/lib/esm/utils/constants.js +1 -1
  40. package/lib/types/action/Action.d.ts +9 -1
  41. package/lib/types/activities/ActivityType.d.ts +2 -1
  42. package/lib/types/activities/HttpActivityClient.d.ts +12 -0
  43. package/lib/types/activities/model/AsyncActivityDetails.d.ts +11 -1
  44. package/lib/types/activities/utils.d.ts +2 -1
  45. package/lib/types/blocker/model/BlockAction.d.ts +2 -1
  46. package/lib/types/context/DefaultContext.d.ts +1 -0
  47. package/lib/types/context/interfaces/IContext.d.ts +4 -0
  48. package/lib/types/phase/impl/CreateRedirectResponsePhase.d.ts +17 -0
  49. package/lib/types/phase/impl/index.d.ts +1 -0
  50. package/lib/types/products/bot_defender/BotDefender.d.ts +1 -1
  51. package/lib/types/products/bot_defender/reasons/BotDefenderReasonType.d.ts +2 -1
  52. package/lib/types/products/bot_defender/reasons/BotDefenderRedirectReason.d.ts +3 -0
  53. package/lib/types/products/bot_defender/reasons/index.d.ts +1 -0
  54. package/lib/types/risk_api/risk_response/CommonRiskResponsePayload.d.ts +3 -0
  55. package/lib/types/risk_api/risk_response/IRiskResponse.d.ts +3 -0
  56. package/lib/types/risk_api/risk_response/RiskResponseBase.d.ts +3 -0
  57. package/lib/types/risk_api/risk_response/serialize/RiskResponseJson.d.ts +3 -0
  58. package/lib/types/risk_api/risk_response/serialize/SerializedRiskResponse.d.ts +3 -0
  59. package/lib/types/utils/constants.d.ts +1 -1
  60. package/package.json +1 -1
@@ -22,4 +22,12 @@ var Action;
22
22
  * Indicates that the request should be blocked and a block response should be returned.
23
23
  */
24
24
  Action["BLOCK"] = "block";
25
+ /**
26
+ * Indicates that the client should be redirected to a specified URL with a 302 response.
27
+ */
28
+ Action["REDIRECT"] = "redirect";
29
+ /**
30
+ * Indicates that the client would be redirected, but no redirect response will be returned (monitor mode).
31
+ */
32
+ Action["SIMULATED_REDIRECT"] = "simulated_redirect";
25
33
  })(Action || (exports.Action = Action = {}));
@@ -7,6 +7,8 @@ var Action_1 = require("./Action.js");
7
7
  * and the first action that matches a product recommendation will be taken.
8
8
  */
9
9
  exports.ACTION_PRIORITY_ORDER = [
10
+ Action_1.Action.REDIRECT,
11
+ Action_1.Action.SIMULATED_REDIRECT,
10
12
  Action_1.Action.BLOCK,
11
13
  Action_1.Action.SIMULATED_BLOCK,
12
14
  Action_1.Action.TRIGGER_RISK_API,
@@ -7,4 +7,5 @@ var ActivityType;
7
7
  ActivityType["BLOCK"] = "block";
8
8
  ActivityType["ADDITIONAL_S2S"] = "additional_s2s";
9
9
  ActivityType["ENFORCER_TELEMETRY"] = "enforcer_telemetry";
10
+ ActivityType["PAGE_REDIRECTED"] = "page_redirected";
10
11
  })(ActivityType || (exports.ActivityType = ActivityType = {}));
@@ -89,17 +89,30 @@ var HttpActivityClient = /** @class */ (function () {
89
89
  */
90
90
  HttpActivityClient.prototype.createActivities = function (context) {
91
91
  var activities = [];
92
- if (this.shouldCreateBlockActivity(context)) {
93
- activities.push(this.createBlockActivity(context));
92
+ if (this.shouldCreatePageRedirectedActivity(context)) {
93
+ activities.push(this.createPageRedirectedActivity(context));
94
94
  }
95
- if (this.shouldCreatePageRequestedActivity(context)) {
96
- activities.push(this.createPageRequestedActivity(context));
97
- }
98
- if (this.shouldCreateAdditionalS2SActivity(context)) {
99
- activities.push(this.createAdditionalS2SActivity(context));
95
+ else {
96
+ if (this.shouldCreateBlockActivity(context)) {
97
+ activities.push(this.createBlockActivity(context));
98
+ }
99
+ if (this.shouldCreatePageRequestedActivity(context)) {
100
+ activities.push(this.createPageRequestedActivity(context));
101
+ }
102
+ if (this.shouldCreateAdditionalS2SActivity(context)) {
103
+ activities.push(this.createAdditionalS2SActivity(context));
104
+ }
100
105
  }
101
106
  return activities.map(this.finalizeActivity);
102
107
  };
108
+ /**
109
+ * Whether to create a page_redirected activity based on the context.
110
+ * @param context
111
+ * @protected
112
+ */
113
+ HttpActivityClient.prototype.shouldCreatePageRedirectedActivity = function (context) {
114
+ return context.action === action_1.Action.REDIRECT || context.action === action_1.Action.SIMULATED_REDIRECT;
115
+ };
103
116
  /**
104
117
  * Whether to create a block activity based on the context.
105
118
  * @param context
@@ -126,6 +139,14 @@ var HttpActivityClient = /** @class */ (function () {
126
139
  !!context.productData.ci &&
127
140
  this.config.ciAutomaticAdditionalS2SEnabled);
128
141
  };
142
+ /**
143
+ * Creates a page_redirected activity based on the context.
144
+ * @param context
145
+ * @protected
146
+ */
147
+ HttpActivityClient.prototype.createPageRedirectedActivity = function (context) {
148
+ return (0, utils_2.createAsyncActivity)(ActivityType_1.ActivityType.PAGE_REDIRECTED, this.config, context);
149
+ };
129
150
  /**
130
151
  * Creates a block activity based on the context.
131
152
  * @param context
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toHeaderEntryArray = exports.createAdditionalS2SActivityDetails = exports.createBlockActivityDetails = exports.createPageRequestedActivityDetails = exports.addResponseDataToAsyncActivityCommonDetails = exports.addRiskApiDataToAsyncActivityCommonDetails = exports.addTokenDataToDetails = exports.addRequestDataToDetails = exports.addServerDataToDetails = exports.addTlsDataToDetails = exports.addProductDataToDetails = exports.addCustomParametersToDetails = exports.addConfigDataToDetails = exports.addRootContextDataToDetails = exports.createCommonActivityDetails = exports.createAsyncActivityCommonDetails = exports.createActivityDetails = exports.createAsyncActivity = void 0;
3
+ exports.toHeaderEntryArray = exports.createPageRedirectedActivityDetails = exports.createAdditionalS2SActivityDetails = exports.createBlockActivityDetails = exports.createPageRequestedActivityDetails = exports.addResponseDataToAsyncActivityCommonDetails = exports.addRiskApiDataToAsyncActivityCommonDetails = exports.addTokenDataToDetails = exports.addRequestDataToDetails = exports.addServerDataToDetails = exports.addTlsDataToDetails = exports.addProductDataToDetails = exports.addCustomParametersToDetails = exports.addConfigDataToDetails = exports.addRootContextDataToDetails = exports.createCommonActivityDetails = exports.createAsyncActivityCommonDetails = exports.createActivityDetails = exports.createAsyncActivity = void 0;
4
4
  exports.redactCookieSecret = redactCookieSecret;
5
5
  var utils_1 = require("../utils/index.js");
6
6
  var products_1 = require("../products/index.js");
@@ -52,6 +52,9 @@ var createActivityDetails = function (activityType, config, context) {
52
52
  case ActivityType_1.ActivityType.ADDITIONAL_S2S:
53
53
  specificActivityDetails = (0, exports.createAdditionalS2SActivityDetails)(config, context);
54
54
  break;
55
+ case ActivityType_1.ActivityType.PAGE_REDIRECTED:
56
+ specificActivityDetails = (0, exports.createPageRedirectedActivityDetails)(context);
57
+ break;
55
58
  }
56
59
  return Object.assign(commonActivityDetails, commonAsyncActivityDetails, specificActivityDetails);
57
60
  };
@@ -325,6 +328,22 @@ var createAdditionalS2SActivityDetails = function (_a, _b) {
325
328
  return details;
326
329
  };
327
330
  exports.createAdditionalS2SActivityDetails = createAdditionalS2SActivityDetails;
331
+ var createPageRedirectedActivityDetails = function (context) {
332
+ var details = {
333
+ redirect_url: context.redirectUrl,
334
+ redirect_reason: 's2s',
335
+ simulated_redirect: context.action === action_1.Action.SIMULATED_REDIRECT,
336
+ };
337
+ var riskResponse = context.riskApiData.riskResponse;
338
+ if (riskResponse === null || riskResponse === void 0 ? void 0 : riskResponse.redirectRuleId) {
339
+ details.redirect_rule_id = riskResponse.redirectRuleId;
340
+ }
341
+ if (riskResponse === null || riskResponse === void 0 ? void 0 : riskResponse.redirectRuleName) {
342
+ details.redirect_rule_name = riskResponse.redirectRuleName;
343
+ }
344
+ return details;
345
+ };
346
+ exports.createPageRedirectedActivityDetails = createPageRedirectedActivityDetails;
328
347
  var toHeaderEntryArray = function (headers) {
329
348
  return Object.entries(headers).flatMap(function (_a) {
330
349
  var key = _a[0], values = _a[1];
@@ -9,4 +9,5 @@ var BlockAction;
9
9
  BlockAction["CHALLENGE"] = "j";
10
10
  BlockAction["HYPE_SALE_CHALLENGE"] = "hsc";
11
11
  BlockAction["HYPE_SALE_CHALLENGE_LEGACY"] = "cp";
12
+ BlockAction["REDIRECT"] = "re";
12
13
  })(BlockAction || (exports.BlockAction = BlockAction = {}));
@@ -27,6 +27,7 @@ var EnforceFlow = /** @class */ (function (_super) {
27
27
  new phase_1.EnrichContextFromRequestPhase(config, products, dataEnrichment, graphQLParser),
28
28
  new phase_1.RiskApiPhase(products, riskApiClient),
29
29
  new phase_1.AdditionalActivityHandlerPhase(config),
30
+ new phase_1.CreateRedirectResponsePhase(config, cors),
30
31
  new phase_1.CreateBlockResponsePhase(config, phase_1.CreateBlockResponsePhase.getBlockers(products), cors),
31
32
  new phase_1.ModifyIncomingRequestPhase(Object.values(products)),
32
33
  ]) || this;
@@ -0,0 +1,100 @@
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 = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["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.CreateRedirectResponsePhase = void 0;
40
+ var http_1 = require("../../http/index.js");
41
+ var action_1 = require("../../action/index.js");
42
+ var pxhd_1 = require("../../pxhd/index.js");
43
+ /**
44
+ * `CreateRedirectResponsePhase` is a phase that creates a 302 redirect response when the enforcer
45
+ * action is REDIRECT. It mirrors the structure of `CreateBlockResponsePhase`.
46
+ */
47
+ var CreateRedirectResponsePhase = /** @class */ (function () {
48
+ function CreateRedirectResponsePhase(config, cors) {
49
+ this.config = config;
50
+ if (cors) {
51
+ this.cors = cors;
52
+ }
53
+ }
54
+ CreateRedirectResponsePhase.prototype.execute = function (context) {
55
+ return __awaiter(this, void 0, void 0, function () {
56
+ var response;
57
+ return __generator(this, function (_a) {
58
+ switch (_a.label) {
59
+ case 0:
60
+ if (context.action !== action_1.Action.REDIRECT) {
61
+ return [2 /*return*/, { done: false }];
62
+ }
63
+ context.logger.debug('create redirect response phase');
64
+ response = new http_1.MinimalResponseImpl({
65
+ status: 302,
66
+ headers: { location: [context.redirectUrl] },
67
+ body: '',
68
+ });
69
+ return [4 /*yield*/, this.addHeadersToResponse(response, context)];
70
+ case 1:
71
+ response = _a.sent();
72
+ return [2 /*return*/, { done: true, response: response }];
73
+ }
74
+ });
75
+ });
76
+ };
77
+ CreateRedirectResponsePhase.prototype.addHeadersToResponse = function (response, context) {
78
+ return __awaiter(this, void 0, void 0, function () {
79
+ var corsHeaders;
80
+ var _a;
81
+ return __generator(this, function (_b) {
82
+ switch (_b.label) {
83
+ case 0:
84
+ if (!context.isMobile && ((_a = context.pxhd) === null || _a === void 0 ? void 0 : _a.source) === pxhd_1.PXHDSource.RISK) {
85
+ response = pxhd_1.PXHDUtils.addPxhdToMinimalResponse(this.config, context, response);
86
+ }
87
+ if (!(this.config.corsSupportEnabled && this.cors && this.cors.isCorsRequest(context))) return [3 /*break*/, 2];
88
+ return [4 /*yield*/, this.cors.getCorsBlockHeaders(context)];
89
+ case 1:
90
+ corsHeaders = _b.sent();
91
+ response = http_1.MinimalResponseUtils.appendHeaders(response, corsHeaders);
92
+ _b.label = 2;
93
+ case 2: return [2 /*return*/, response];
94
+ }
95
+ });
96
+ });
97
+ };
98
+ return CreateRedirectResponsePhase;
99
+ }());
100
+ exports.CreateRedirectResponsePhase = CreateRedirectResponsePhase;
@@ -55,6 +55,7 @@ exports.RiskApiPhase = void 0;
55
55
  var action_1 = require("../../action/index.js");
56
56
  var utils_1 = require("../../utils/index.js");
57
57
  var pxhd_1 = require("../../pxhd/index.js");
58
+ var blocker_1 = require("../../blocker/index.js");
58
59
  var DecideActionPhase_1 = require("./DecideActionPhase.js");
59
60
  /**
60
61
  * The `RiskApiPhase` class is responsible for making the synchronous Risk API request when necessary. If a Risk API call
@@ -152,6 +153,9 @@ var RiskApiPhase = /** @class */ (function (_super) {
152
153
  source: pxhd_1.PXHDSource.RISK,
153
154
  };
154
155
  }
156
+ if (riskResponse.action === blocker_1.BlockAction.REDIRECT && riskResponse.redirectUrl) {
157
+ context.redirectUrl = riskResponse.redirectUrl;
158
+ }
155
159
  };
156
160
  RiskApiPhase.prototype.enrichContextWithProductDataFromRiskApi = function (context) {
157
161
  return __awaiter(this, void 0, void 0, function () {
@@ -52,7 +52,13 @@ var SendAsyncActivitiesPhase = /** @class */ (function () {
52
52
  return __generator(this, function (_a) {
53
53
  switch (_a.label) {
54
54
  case 0:
55
- if (![action_1.Action.PASS_REQUEST, action_1.Action.BLOCK, action_1.Action.SIMULATED_BLOCK].includes(context.action)) return [3 /*break*/, 2];
55
+ if (![
56
+ action_1.Action.PASS_REQUEST,
57
+ action_1.Action.BLOCK,
58
+ action_1.Action.SIMULATED_BLOCK,
59
+ action_1.Action.REDIRECT,
60
+ action_1.Action.SIMULATED_REDIRECT,
61
+ ].includes(context.action)) return [3 /*break*/, 2];
56
62
  promise = this.activityClient.sendActivities(context);
57
63
  if (!this.config.awaitAsyncHttpRequests) return [3 /*break*/, 2];
58
64
  return [4 /*yield*/, promise];
@@ -22,6 +22,7 @@ __exportStar(require("./ParseTokenPhase.js"), exports);
22
22
  __exportStar(require("./DecideActionPhase.js"), exports);
23
23
  __exportStar(require("./RiskApiPhase.js"), exports);
24
24
  __exportStar(require("./CreateBlockResponsePhase.js"), exports);
25
+ __exportStar(require("./CreateRedirectResponsePhase.js"), exports);
25
26
  __exportStar(require("./ModifyIncomingRequestPhase.js"), exports);
26
27
  __exportStar(require("./EnrichContextFromResponsePhase.js"), exports);
27
28
  __exportStar(require("./ModifyOutgoingResponsePhase.js"), exports);
@@ -58,6 +58,7 @@ var block_1 = require("./block/index.js");
58
58
  var filter_1 = require("./filter/index.js");
59
59
  var first_party_1 = require("./first_party/index.js");
60
60
  var reasons_1 = require("./reasons/index.js");
61
+ var blocker_1 = require("../../blocker/index.js");
61
62
  var BotDefenderActionData_1 = require("./BotDefenderActionData.js");
62
63
  var BotDefenderUtils_1 = require("./BotDefenderUtils.js");
63
64
  var BotDefender = /** @class */ (function () {
@@ -161,7 +162,8 @@ var BotDefender = /** @class */ (function () {
161
162
  };
162
163
  BotDefender.prototype.getRiskApiActionData = function (_a) {
163
164
  return __awaiter(this, arguments, void 0, function (_b) {
164
- var riskApiData = _b.riskApiData, bd = _b.productData.bd;
165
+ var riskResponse, isMonitored, actionData;
166
+ var riskApiData = _b.riskApiData, bd = _b.productData.bd, logger = _b.logger;
165
167
  return __generator(this, function (_c) {
166
168
  switch (riskApiData.riskApiCallResult) {
167
169
  case risk_api_1.RiskApiCallResult.NONE:
@@ -170,14 +172,25 @@ var BotDefender = /** @class */ (function () {
170
172
  return [2 /*return*/, new BotDefenderActionData_1.BotDefenderActionData(action_1.Action.PASS_REQUEST, reasons_1.BotDefenderPassReason.S2S_ERROR)];
171
173
  case risk_api_1.RiskApiCallResult.TIMEOUT:
172
174
  return [2 /*return*/, new BotDefenderActionData_1.BotDefenderActionData(action_1.Action.PASS_REQUEST, reasons_1.BotDefenderPassReason.S2S_TIMEOUT)];
173
- case risk_api_1.RiskApiCallResult.SUCCESSFUL:
174
- return [2 /*return*/, this.getSuccessfulRiskApiAction(riskApiData.riskResponse, !!(bd === null || bd === void 0 ? void 0 : bd.isMonitoredRequest))];
175
+ case risk_api_1.RiskApiCallResult.SUCCESSFUL: {
176
+ riskResponse = riskApiData.riskResponse;
177
+ isMonitored = !!(bd === null || bd === void 0 ? void 0 : bd.isMonitoredRequest);
178
+ actionData = this.getSuccessfulRiskApiAction(riskResponse, isMonitored);
179
+ logger.debug("bot defender action: ".concat(actionData.action, ", risk action: ").concat(riskResponse.action, ", redirect url: ").concat(riskResponse.redirectUrl, ", score: ").concat(riskResponse.score, ", monitored: ").concat(isMonitored));
180
+ return [2 /*return*/, actionData];
181
+ }
175
182
  }
176
183
  return [2 /*return*/];
177
184
  });
178
185
  });
179
186
  };
180
187
  BotDefender.prototype.getSuccessfulRiskApiAction = function (riskResponse, isMonitored) {
188
+ if (riskResponse.action === blocker_1.BlockAction.REDIRECT) {
189
+ if (!riskResponse.redirectUrl) {
190
+ return new BotDefenderActionData_1.BotDefenderActionData(action_1.Action.PASS_REQUEST, reasons_1.BotDefenderPassReason.S2S_ERROR);
191
+ }
192
+ return new BotDefenderActionData_1.BotDefenderActionData(isMonitored ? action_1.Action.SIMULATED_REDIRECT : action_1.Action.REDIRECT, reasons_1.BotDefenderRedirectReason.S2S);
193
+ }
181
194
  if (riskResponse.score >= this.config.blockingScore) {
182
195
  return new BotDefenderActionData_1.BotDefenderActionData(isMonitored ? action_1.Action.SIMULATED_BLOCK : action_1.Action.BLOCK, reasons_1.BotDefenderBlockReason.S2S_HIGH_SCORE);
183
196
  }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BotDefenderRedirectReason = void 0;
4
+ var BotDefenderRedirectReason;
5
+ (function (BotDefenderRedirectReason) {
6
+ BotDefenderRedirectReason["S2S"] = "s2s";
7
+ })(BotDefenderRedirectReason || (exports.BotDefenderRedirectReason = BotDefenderRedirectReason = {}));
@@ -16,5 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./BotDefenderBlockReason.js"), exports);
18
18
  __exportStar(require("./BotDefenderPassReason.js"), exports);
19
+ __exportStar(require("./BotDefenderRedirectReason.js"), exports);
19
20
  __exportStar(require("./BotDefenderS2SCallReason.js"), exports);
20
21
  __exportStar(require("./BotDefenderReasonType.js"), exports);
@@ -13,4 +13,7 @@ exports.GetRiskResponseHeaders = {
13
13
  Status: 'x-px-status',
14
14
  TelemetryRequested: 'x-px-telemetry-requested',
15
15
  Uuid: 'x-px-uuid',
16
+ RedirectUrl: 'x-px-redirect-url',
17
+ RedirectRuleId: 'x-px-redirect-rule-id',
18
+ RedirectRuleName: 'x-px-redirect-rule-name',
16
19
  };
@@ -145,6 +145,30 @@ var RiskResponseBase = /** @class */ (function () {
145
145
  enumerable: false,
146
146
  configurable: true
147
147
  });
148
+ Object.defineProperty(RiskResponseBase.prototype, "redirectUrl", {
149
+ get: function () {
150
+ var _a;
151
+ return (_a = this.riskResponse) === null || _a === void 0 ? void 0 : _a.redirect_url;
152
+ },
153
+ enumerable: false,
154
+ configurable: true
155
+ });
156
+ Object.defineProperty(RiskResponseBase.prototype, "redirectRuleId", {
157
+ get: function () {
158
+ var _a;
159
+ return (_a = this.riskResponse) === null || _a === void 0 ? void 0 : _a.redirect_rule_id;
160
+ },
161
+ enumerable: false,
162
+ configurable: true
163
+ });
164
+ Object.defineProperty(RiskResponseBase.prototype, "redirectRuleName", {
165
+ get: function () {
166
+ var _a;
167
+ return (_a = this.riskResponse) === null || _a === void 0 ? void 0 : _a.redirect_rule_name;
168
+ },
169
+ enumerable: false,
170
+ configurable: true
171
+ });
148
172
  RiskResponseBase.prototype.toJSON = function () {
149
173
  return {
150
174
  status: this.status,
@@ -158,6 +182,9 @@ var RiskResponseBase = /** @class */ (function () {
158
182
  additionalRiskInfo: this.additionalRiskInfo,
159
183
  telemetryRequested: this.telemetryRequested,
160
184
  uuid: this.uuid,
185
+ redirectUrl: this.redirectUrl,
186
+ redirectRuleId: this.redirectRuleId,
187
+ redirectRuleName: this.redirectRuleName,
161
188
  };
162
189
  };
163
190
  return RiskResponseBase;
@@ -15,6 +15,9 @@ var SerializedRiskResponse = /** @class */ (function () {
15
15
  this.score = riskResponseJson.score;
16
16
  this.status = riskResponseJson.status;
17
17
  this.uuid = riskResponseJson.uuid;
18
+ this.redirectUrl = riskResponseJson.redirectUrl;
19
+ this.redirectRuleId = riskResponseJson.redirectRuleId;
20
+ this.redirectRuleName = riskResponseJson.redirectRuleName;
18
21
  }
19
22
  SerializedRiskResponse.prototype.validate = function () {
20
23
  throw new utils_1.EnforcerError('SerializedRiskResponse.validate() should not be invoked');
@@ -39,6 +39,9 @@ var GetRiskResponseV2 = /** @class */ (function (_super) {
39
39
  var telemetryRequested = this.getHeader(response, model_1.GetRiskResponseHeaders.TelemetryRequested);
40
40
  var uuid = this.getHeader(response, model_1.GetRiskResponseHeaders.Uuid);
41
41
  var action = this.getHeader(response, model_1.GetRiskResponseHeaders.Action);
42
+ var redirectUrl = this.getHeader(response, model_1.GetRiskResponseHeaders.RedirectUrl);
43
+ var redirectRuleId = this.getHeader(response, model_1.GetRiskResponseHeaders.RedirectRuleId);
44
+ var redirectRuleName = this.getHeader(response, model_1.GetRiskResponseHeaders.RedirectRuleName);
42
45
  return {
43
46
  status: status,
44
47
  action: action,
@@ -51,6 +54,9 @@ var GetRiskResponseV2 = /** @class */ (function (_super) {
51
54
  telemetry_requested: telemetryRequested === 'true',
52
55
  drc: drc ? parseInt(drc) : undefined,
53
56
  cookie_cfg_block_result: blockResult,
57
+ redirect_url: redirectUrl,
58
+ redirect_rule_id: redirectRuleId,
59
+ redirect_rule_name: redirectRuleName,
54
60
  };
55
61
  };
56
62
  GetRiskResponseV2.prototype.getHeader = function (response, header) {
@@ -15,4 +15,4 @@ exports.PUSH_DATA_FEATURE_HEADER_NAME = 'x-px-feature';
15
15
  exports.EMAIL_ADDRESS_REGEX = /^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,7}$/;
16
16
  exports.URL_REGEX = /^(https?:)\/\/(([^@\s:\/]+):?([^@\s\/]*)@)?(([^:\/?#]*)(?:\:([0-9]+))?)(\/?[^?#]*)(\?[^#]*|)(#.*|)$/;
17
17
  exports.REGEX_STRUCTURE = /^\/(.+?)\/([gimsuyvd]*)$/;
18
- exports.CORE_MODULE_VERSION = 'JS Core 1.0.1';
18
+ exports.CORE_MODULE_VERSION = 'JS Core 1.1.0';
@@ -19,4 +19,12 @@ export var Action;
19
19
  * Indicates that the request should be blocked and a block response should be returned.
20
20
  */
21
21
  Action["BLOCK"] = "block";
22
+ /**
23
+ * Indicates that the client should be redirected to a specified URL with a 302 response.
24
+ */
25
+ Action["REDIRECT"] = "redirect";
26
+ /**
27
+ * Indicates that the client would be redirected, but no redirect response will be returned (monitor mode).
28
+ */
29
+ Action["SIMULATED_REDIRECT"] = "simulated_redirect";
22
30
  })(Action || (Action = {}));
@@ -4,6 +4,8 @@ import { Action } from './Action.js';
4
4
  * and the first action that matches a product recommendation will be taken.
5
5
  */
6
6
  export const ACTION_PRIORITY_ORDER = [
7
+ Action.REDIRECT,
8
+ Action.SIMULATED_REDIRECT,
7
9
  Action.BLOCK,
8
10
  Action.SIMULATED_BLOCK,
9
11
  Action.TRIGGER_RISK_API,
@@ -4,4 +4,5 @@ export var ActivityType;
4
4
  ActivityType["BLOCK"] = "block";
5
5
  ActivityType["ADDITIONAL_S2S"] = "additional_s2s";
6
6
  ActivityType["ENFORCER_TELEMETRY"] = "enforcer_telemetry";
7
+ ActivityType["PAGE_REDIRECTED"] = "page_redirected";
7
8
  })(ActivityType || (ActivityType = {}));
@@ -46,17 +46,30 @@ export class HttpActivityClient {
46
46
  */
47
47
  createActivities(context) {
48
48
  const activities = [];
49
- if (this.shouldCreateBlockActivity(context)) {
50
- activities.push(this.createBlockActivity(context));
49
+ if (this.shouldCreatePageRedirectedActivity(context)) {
50
+ activities.push(this.createPageRedirectedActivity(context));
51
51
  }
52
- if (this.shouldCreatePageRequestedActivity(context)) {
53
- activities.push(this.createPageRequestedActivity(context));
54
- }
55
- if (this.shouldCreateAdditionalS2SActivity(context)) {
56
- activities.push(this.createAdditionalS2SActivity(context));
52
+ else {
53
+ if (this.shouldCreateBlockActivity(context)) {
54
+ activities.push(this.createBlockActivity(context));
55
+ }
56
+ if (this.shouldCreatePageRequestedActivity(context)) {
57
+ activities.push(this.createPageRequestedActivity(context));
58
+ }
59
+ if (this.shouldCreateAdditionalS2SActivity(context)) {
60
+ activities.push(this.createAdditionalS2SActivity(context));
61
+ }
57
62
  }
58
63
  return activities.map(this.finalizeActivity);
59
64
  }
65
+ /**
66
+ * Whether to create a page_redirected activity based on the context.
67
+ * @param context
68
+ * @protected
69
+ */
70
+ shouldCreatePageRedirectedActivity(context) {
71
+ return context.action === Action.REDIRECT || context.action === Action.SIMULATED_REDIRECT;
72
+ }
60
73
  /**
61
74
  * Whether to create a block activity based on the context.
62
75
  * @param context
@@ -83,6 +96,14 @@ export class HttpActivityClient {
83
96
  !!context.productData.ci &&
84
97
  this.config.ciAutomaticAdditionalS2SEnabled);
85
98
  }
99
+ /**
100
+ * Creates a page_redirected activity based on the context.
101
+ * @param context
102
+ * @protected
103
+ */
104
+ createPageRedirectedActivity(context) {
105
+ return createAsyncActivity(ActivityType.PAGE_REDIRECTED, this.config, context);
106
+ }
86
107
  /**
87
108
  * Creates a block activity based on the context.
88
109
  * @param context
@@ -46,6 +46,9 @@ export const createActivityDetails = (activityType, config, context) => {
46
46
  case ActivityType.ADDITIONAL_S2S:
47
47
  specificActivityDetails = createAdditionalS2SActivityDetails(config, context);
48
48
  break;
49
+ case ActivityType.PAGE_REDIRECTED:
50
+ specificActivityDetails = createPageRedirectedActivityDetails(context);
51
+ break;
49
52
  }
50
53
  return Object.assign(commonActivityDetails, commonAsyncActivityDetails, specificActivityDetails);
51
54
  };
@@ -297,6 +300,21 @@ export const createAdditionalS2SActivityDetails = ({ ciSendRawUsernameOnAddition
297
300
  }
298
301
  return details;
299
302
  };
303
+ export const createPageRedirectedActivityDetails = (context) => {
304
+ const details = {
305
+ redirect_url: context.redirectUrl,
306
+ redirect_reason: 's2s',
307
+ simulated_redirect: context.action === Action.SIMULATED_REDIRECT,
308
+ };
309
+ const { riskResponse } = context.riskApiData;
310
+ if (riskResponse?.redirectRuleId) {
311
+ details.redirect_rule_id = riskResponse.redirectRuleId;
312
+ }
313
+ if (riskResponse?.redirectRuleName) {
314
+ details.redirect_rule_name = riskResponse.redirectRuleName;
315
+ }
316
+ return details;
317
+ };
300
318
  export const toHeaderEntryArray = (headers) => {
301
319
  return Object.entries(headers).flatMap(([key, values]) => values.map((value) => ({
302
320
  name: key,
@@ -6,4 +6,5 @@ export var BlockAction;
6
6
  BlockAction["CHALLENGE"] = "j";
7
7
  BlockAction["HYPE_SALE_CHALLENGE"] = "hsc";
8
8
  BlockAction["HYPE_SALE_CHALLENGE_LEGACY"] = "cp";
9
+ BlockAction["REDIRECT"] = "re";
9
10
  })(BlockAction || (BlockAction = {}));
@@ -28,6 +28,7 @@ export class DefaultContext {
28
28
  reasons;
29
29
  score;
30
30
  blockAction;
31
+ redirectUrl;
31
32
  pxhd;
32
33
  pxde;
33
34
  pxdeVerified;
@@ -1,4 +1,4 @@
1
- import { AdditionalActivityHandlerPhase, CreateBlockResponsePhase, EnrichContextFromRequestPhase, ModifyIncomingRequestPhase, ParseTokenPhase, RiskApiPhase, } from '../phase/index.js';
1
+ import { AdditionalActivityHandlerPhase, CreateBlockResponsePhase, CreateRedirectResponsePhase, EnrichContextFromRequestPhase, ModifyIncomingRequestPhase, ParseTokenPhase, RiskApiPhase, } from '../phase/index.js';
2
2
  import { Flow } from './Flow.js';
3
3
  export class EnforceFlow extends Flow {
4
4
  constructor(config, { dataEnrichment, tokenParser, riskApiClient, cors, products, graphQLParser, }) {
@@ -7,6 +7,7 @@ export class EnforceFlow extends Flow {
7
7
  new EnrichContextFromRequestPhase(config, products, dataEnrichment, graphQLParser),
8
8
  new RiskApiPhase(products, riskApiClient),
9
9
  new AdditionalActivityHandlerPhase(config),
10
+ new CreateRedirectResponsePhase(config, cors),
10
11
  new CreateBlockResponsePhase(config, CreateBlockResponsePhase.getBlockers(products), cors),
11
12
  new ModifyIncomingRequestPhase(Object.values(products)),
12
13
  ]);
@@ -0,0 +1,40 @@
1
+ import { MinimalResponseImpl, MinimalResponseUtils } from '../../http/index.js';
2
+ import { Action } from '../../action/index.js';
3
+ import { PXHDSource, PXHDUtils } from '../../pxhd/index.js';
4
+ /**
5
+ * `CreateRedirectResponsePhase` is a phase that creates a 302 redirect response when the enforcer
6
+ * action is REDIRECT. It mirrors the structure of `CreateBlockResponsePhase`.
7
+ */
8
+ export class CreateRedirectResponsePhase {
9
+ config;
10
+ cors;
11
+ constructor(config, cors) {
12
+ this.config = config;
13
+ if (cors) {
14
+ this.cors = cors;
15
+ }
16
+ }
17
+ async execute(context) {
18
+ if (context.action !== Action.REDIRECT) {
19
+ return { done: false };
20
+ }
21
+ context.logger.debug('create redirect response phase');
22
+ let response = new MinimalResponseImpl({
23
+ status: 302,
24
+ headers: { location: [context.redirectUrl] },
25
+ body: '',
26
+ });
27
+ response = await this.addHeadersToResponse(response, context);
28
+ return { done: true, response };
29
+ }
30
+ async addHeadersToResponse(response, context) {
31
+ if (!context.isMobile && context.pxhd?.source === PXHDSource.RISK) {
32
+ response = PXHDUtils.addPxhdToMinimalResponse(this.config, context, response);
33
+ }
34
+ if (this.config.corsSupportEnabled && this.cors && this.cors.isCorsRequest(context)) {
35
+ const corsHeaders = await this.cors.getCorsBlockHeaders(context);
36
+ response = MinimalResponseUtils.appendHeaders(response, corsHeaders);
37
+ }
38
+ return response;
39
+ }
40
+ }
@@ -1,6 +1,7 @@
1
1
  import { Action, getReasonForHighestPriorityProduct } from '../../action/index.js';
2
2
  import { isNullOrUndefined } from '../../utils/index.js';
3
3
  import { PXHDSource } from '../../pxhd/index.js';
4
+ import { BlockAction } from '../../blocker/index.js';
4
5
  import { DecideActionPhase } from './DecideActionPhase.js';
5
6
  /**
6
7
  * The `RiskApiPhase` class is responsible for making the synchronous Risk API request when necessary. If a Risk API call
@@ -64,6 +65,9 @@ export class RiskApiPhase extends DecideActionPhase {
64
65
  source: PXHDSource.RISK,
65
66
  };
66
67
  }
68
+ if (riskResponse.action === BlockAction.REDIRECT && riskResponse.redirectUrl) {
69
+ context.redirectUrl = riskResponse.redirectUrl;
70
+ }
67
71
  }
68
72
  async enrichContextWithProductDataFromRiskApi(context) {
69
73
  await Promise.all(Object.entries(this.products).map(async ([name, product]) => {
@@ -10,7 +10,13 @@ export class SendAsyncActivitiesPhase {
10
10
  this.activityClient = activityClient;
11
11
  }
12
12
  async execute(context) {
13
- if ([Action.PASS_REQUEST, Action.BLOCK, Action.SIMULATED_BLOCK].includes(context.action)) {
13
+ if ([
14
+ Action.PASS_REQUEST,
15
+ Action.BLOCK,
16
+ Action.SIMULATED_BLOCK,
17
+ Action.REDIRECT,
18
+ Action.SIMULATED_REDIRECT,
19
+ ].includes(context.action)) {
14
20
  // ensure error handling is done in the sendActivities method
15
21
  const promise = this.activityClient.sendActivities(context);
16
22
  if (this.config.awaitAsyncHttpRequests) {
@@ -6,6 +6,7 @@ export * from './ParseTokenPhase.js';
6
6
  export * from './DecideActionPhase.js';
7
7
  export * from './RiskApiPhase.js';
8
8
  export * from './CreateBlockResponsePhase.js';
9
+ export * from './CreateRedirectResponsePhase.js';
9
10
  export * from './ModifyIncomingRequestPhase.js';
10
11
  export * from './EnrichContextFromResponsePhase.js';
11
12
  export * from './ModifyOutgoingResponsePhase.js';
@@ -7,7 +7,8 @@ import { MonitoredRequestUtils } from '../../monitored_request/index.js';
7
7
  import { DefaultBotDefenderBlocker } from './block/index.js';
8
8
  import { DefaultBotDefenderFilter } from './filter/index.js';
9
9
  import { DefaultBotDefenderFirstParty } from './first_party/index.js';
10
- import { BotDefenderBlockReason, BotDefenderPassReason, BotDefenderS2SCallReason } from './reasons/index.js';
10
+ import { BotDefenderBlockReason, BotDefenderPassReason, BotDefenderRedirectReason, BotDefenderS2SCallReason, } from './reasons/index.js';
11
+ import { BlockAction } from '../../blocker/index.js';
11
12
  import { BotDefenderActionData } from './BotDefenderActionData.js';
12
13
  import { BotDefenderUtils } from './BotDefenderUtils.js';
13
14
  export class BotDefender {
@@ -77,7 +78,7 @@ export class BotDefender {
77
78
  async enrichContextFromRiskApi(context) {
78
79
  return await this.getRiskApiActionData(context);
79
80
  }
80
- async getRiskApiActionData({ riskApiData, productData: { bd }, }) {
81
+ async getRiskApiActionData({ riskApiData, productData: { bd }, logger, }) {
81
82
  switch (riskApiData.riskApiCallResult) {
82
83
  case RiskApiCallResult.NONE:
83
84
  throw new EnforcerError('risk api call result should not be none!');
@@ -85,11 +86,22 @@ export class BotDefender {
85
86
  return new BotDefenderActionData(Action.PASS_REQUEST, BotDefenderPassReason.S2S_ERROR);
86
87
  case RiskApiCallResult.TIMEOUT:
87
88
  return new BotDefenderActionData(Action.PASS_REQUEST, BotDefenderPassReason.S2S_TIMEOUT);
88
- case RiskApiCallResult.SUCCESSFUL:
89
- return this.getSuccessfulRiskApiAction(riskApiData.riskResponse, !!bd?.isMonitoredRequest);
89
+ case RiskApiCallResult.SUCCESSFUL: {
90
+ const riskResponse = riskApiData.riskResponse;
91
+ const isMonitored = !!bd?.isMonitoredRequest;
92
+ const actionData = this.getSuccessfulRiskApiAction(riskResponse, isMonitored);
93
+ logger.debug(`bot defender action: ${actionData.action}, risk action: ${riskResponse.action}, redirect url: ${riskResponse.redirectUrl}, score: ${riskResponse.score}, monitored: ${isMonitored}`);
94
+ return actionData;
95
+ }
90
96
  }
91
97
  }
92
98
  getSuccessfulRiskApiAction(riskResponse, isMonitored) {
99
+ if (riskResponse.action === BlockAction.REDIRECT) {
100
+ if (!riskResponse.redirectUrl) {
101
+ return new BotDefenderActionData(Action.PASS_REQUEST, BotDefenderPassReason.S2S_ERROR);
102
+ }
103
+ return new BotDefenderActionData(isMonitored ? Action.SIMULATED_REDIRECT : Action.REDIRECT, BotDefenderRedirectReason.S2S);
104
+ }
93
105
  if (riskResponse.score >= this.config.blockingScore) {
94
106
  return new BotDefenderActionData(isMonitored ? Action.SIMULATED_BLOCK : Action.BLOCK, BotDefenderBlockReason.S2S_HIGH_SCORE);
95
107
  }
@@ -0,0 +1,4 @@
1
+ export var BotDefenderRedirectReason;
2
+ (function (BotDefenderRedirectReason) {
3
+ BotDefenderRedirectReason["S2S"] = "s2s";
4
+ })(BotDefenderRedirectReason || (BotDefenderRedirectReason = {}));
@@ -1,4 +1,5 @@
1
1
  export * from './BotDefenderBlockReason.js';
2
2
  export * from './BotDefenderPassReason.js';
3
+ export * from './BotDefenderRedirectReason.js';
3
4
  export * from './BotDefenderS2SCallReason.js';
4
5
  export * from './BotDefenderReasonType.js';
@@ -10,4 +10,7 @@ export const GetRiskResponseHeaders = {
10
10
  Status: 'x-px-status',
11
11
  TelemetryRequested: 'x-px-telemetry-requested',
12
12
  Uuid: 'x-px-uuid',
13
+ RedirectUrl: 'x-px-redirect-url',
14
+ RedirectRuleId: 'x-px-redirect-rule-id',
15
+ RedirectRuleName: 'x-px-redirect-rule-name',
13
16
  };
@@ -48,6 +48,15 @@ export class RiskResponseBase {
48
48
  get drc() {
49
49
  return this.riskResponse?.drc;
50
50
  }
51
+ get redirectUrl() {
52
+ return this.riskResponse?.redirect_url;
53
+ }
54
+ get redirectRuleId() {
55
+ return this.riskResponse?.redirect_rule_id;
56
+ }
57
+ get redirectRuleName() {
58
+ return this.riskResponse?.redirect_rule_name;
59
+ }
51
60
  toJSON() {
52
61
  return {
53
62
  status: this.status,
@@ -61,6 +70,9 @@ export class RiskResponseBase {
61
70
  additionalRiskInfo: this.additionalRiskInfo,
62
71
  telemetryRequested: this.telemetryRequested,
63
72
  uuid: this.uuid,
73
+ redirectUrl: this.redirectUrl,
74
+ redirectRuleId: this.redirectRuleId,
75
+ redirectRuleName: this.redirectRuleName,
64
76
  };
65
77
  }
66
78
  }
@@ -11,6 +11,9 @@ export class SerializedRiskResponse {
11
11
  score;
12
12
  status;
13
13
  uuid;
14
+ redirectUrl;
15
+ redirectRuleId;
16
+ redirectRuleName;
14
17
  constructor(riskResponseJson) {
15
18
  this.action = riskResponseJson.action;
16
19
  this.additionalRiskInfo = riskResponseJson.additionalRiskInfo;
@@ -23,6 +26,9 @@ export class SerializedRiskResponse {
23
26
  this.score = riskResponseJson.score;
24
27
  this.status = riskResponseJson.status;
25
28
  this.uuid = riskResponseJson.uuid;
29
+ this.redirectUrl = riskResponseJson.redirectUrl;
30
+ this.redirectRuleId = riskResponseJson.redirectRuleId;
31
+ this.redirectRuleName = riskResponseJson.redirectRuleName;
26
32
  }
27
33
  validate() {
28
34
  throw new EnforcerError('SerializedRiskResponse.validate() should not be invoked');
@@ -20,6 +20,9 @@ export class GetRiskResponseV2 extends RiskResponseBase {
20
20
  const telemetryRequested = this.getHeader(response, GetRiskResponseHeaders.TelemetryRequested);
21
21
  const uuid = this.getHeader(response, GetRiskResponseHeaders.Uuid);
22
22
  const action = this.getHeader(response, GetRiskResponseHeaders.Action);
23
+ const redirectUrl = this.getHeader(response, GetRiskResponseHeaders.RedirectUrl);
24
+ const redirectRuleId = this.getHeader(response, GetRiskResponseHeaders.RedirectRuleId);
25
+ const redirectRuleName = this.getHeader(response, GetRiskResponseHeaders.RedirectRuleName);
23
26
  return {
24
27
  status,
25
28
  action,
@@ -32,6 +35,9 @@ export class GetRiskResponseV2 extends RiskResponseBase {
32
35
  telemetry_requested: telemetryRequested === 'true',
33
36
  drc: drc ? parseInt(drc) : undefined,
34
37
  cookie_cfg_block_result: blockResult,
38
+ redirect_url: redirectUrl,
39
+ redirect_rule_id: redirectRuleId,
40
+ redirect_rule_name: redirectRuleName,
35
41
  };
36
42
  }
37
43
  getHeader(response, header) {
@@ -12,4 +12,4 @@ export const PUSH_DATA_FEATURE_HEADER_NAME = 'x-px-feature';
12
12
  export const EMAIL_ADDRESS_REGEX = /^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,7}$/;
13
13
  export const URL_REGEX = /^(https?:)\/\/(([^@\s:\/]+):?([^@\s\/]*)@)?(([^:\/?#]*)(?:\:([0-9]+))?)(\/?[^?#]*)(\?[^#]*|)(#.*|)$/;
14
14
  export const REGEX_STRUCTURE = /^\/(.+?)\/([gimsuyvd]*)$/;
15
- export const CORE_MODULE_VERSION = 'JS Core 1.0.1';
15
+ export const CORE_MODULE_VERSION = 'JS Core 1.1.0';
@@ -17,5 +17,13 @@ export declare enum Action {
17
17
  /**
18
18
  * Indicates that the request should be blocked and a block response should be returned.
19
19
  */
20
- BLOCK = "block"
20
+ BLOCK = "block",
21
+ /**
22
+ * Indicates that the client should be redirected to a specified URL with a 302 response.
23
+ */
24
+ REDIRECT = "redirect",
25
+ /**
26
+ * Indicates that the client would be redirected, but no redirect response will be returned (monitor mode).
27
+ */
28
+ SIMULATED_REDIRECT = "simulated_redirect"
21
29
  }
@@ -2,5 +2,6 @@ export declare enum ActivityType {
2
2
  PAGE_REQUESTED = "page_requested",
3
3
  BLOCK = "block",
4
4
  ADDITIONAL_S2S = "additional_s2s",
5
- ENFORCER_TELEMETRY = "enforcer_telemetry"
5
+ ENFORCER_TELEMETRY = "enforcer_telemetry",
6
+ PAGE_REDIRECTED = "page_redirected"
6
7
  }
@@ -29,6 +29,12 @@ export declare class HttpActivityClient<Req, Res, Supported extends string, Adde
29
29
  * @protected
30
30
  */
31
31
  protected createActivities(context: ReadonlyContext<Req, Res>): AsyncActivity[];
32
+ /**
33
+ * Whether to create a page_redirected activity based on the context.
34
+ * @param context
35
+ * @protected
36
+ */
37
+ protected shouldCreatePageRedirectedActivity(context: ReadonlyContext<Req, Res>): boolean;
32
38
  /**
33
39
  * Whether to create a block activity based on the context.
34
40
  * @param context
@@ -47,6 +53,12 @@ export declare class HttpActivityClient<Req, Res, Supported extends string, Adde
47
53
  * @protected
48
54
  */
49
55
  protected shouldCreateAdditionalS2SActivity(context: ReadonlyContext<Req, Res>): boolean;
56
+ /**
57
+ * Creates a page_redirected activity based on the context.
58
+ * @param context
59
+ * @protected
60
+ */
61
+ protected createPageRedirectedActivity(context: ReadonlyContext<Req, Res>): AsyncActivity;
50
62
  /**
51
63
  * Creates a block activity based on the context.
52
64
  * @param context
@@ -12,7 +12,7 @@ export type AsyncActivityCommonDetails = Partial<RiskAdditionalData> & {
12
12
  http_status_code?: number;
13
13
  credentials_compromised?: boolean;
14
14
  };
15
- export type ActivityTypeDetails = PageRequestedActivityDetails | BlockActivityDetails | AdditionalS2SActivityDetails;
15
+ export type ActivityTypeDetails = PageRequestedActivityDetails | BlockActivityDetails | AdditionalS2SActivityDetails | PageRedirectedActivityDetails;
16
16
  /**
17
17
  * Activity details that should be included in page_requested activities.
18
18
  */
@@ -39,3 +39,13 @@ export type AdditionalS2SActivityDetails = {
39
39
  login_successful: boolean;
40
40
  raw_username?: string;
41
41
  };
42
+ /**
43
+ * Activity details that should be included in page_redirected activities.
44
+ */
45
+ export type PageRedirectedActivityDetails = {
46
+ redirect_url: string;
47
+ redirect_reason: 's2s';
48
+ simulated_redirect: boolean;
49
+ redirect_rule_id?: string;
50
+ redirect_rule_name?: string;
51
+ };
@@ -3,7 +3,7 @@ import { ReadonlyContext, RequestData, ServerData, TlsData, TokenData } from '..
3
3
  import { IConfiguration } from '../config';
4
4
  import { ProductData } from '../products';
5
5
  import { ActivityType } from './ActivityType';
6
- import { AdditionalS2SActivityDetails, AsyncActivity, AsyncActivityCommonDetails, AsyncActivityDetails, BlockActivityDetails, CommonActivityDetails, HeaderEntry, PageRequestedActivityDetails } from './model';
6
+ import { AdditionalS2SActivityDetails, AsyncActivity, AsyncActivityCommonDetails, AsyncActivityDetails, BlockActivityDetails, CommonActivityDetails, HeaderEntry, PageRedirectedActivityDetails, PageRequestedActivityDetails } from './model';
7
7
  import { CustomParameters, ResponseCustomParameters } from '../custom_parameters';
8
8
  /**
9
9
  * Creates an async activity object based on the provided parameters.
@@ -37,4 +37,5 @@ export declare const addResponseDataToAsyncActivityCommonDetails: <Req, Res>(det
37
37
  export declare const createPageRequestedActivityDetails: <Req, Res>(context: ReadonlyContext<Req, Res>) => PageRequestedActivityDetails;
38
38
  export declare const createBlockActivityDetails: <Req, Res>(context: ReadonlyContext<Req, Res>) => BlockActivityDetails;
39
39
  export declare const createAdditionalS2SActivityDetails: <Req, Res, Supported extends string, Added>({ ciSendRawUsernameOnAdditionalS2SActivity }: IConfiguration<Req, Res, Supported, Added>, { productData }: ReadonlyContext<Req, Res>) => AdditionalS2SActivityDetails;
40
+ export declare const createPageRedirectedActivityDetails: <Req, Res>(context: ReadonlyContext<Req, Res>) => PageRedirectedActivityDetails;
40
41
  export declare const toHeaderEntryArray: (headers: Record<string, string[]>) => HeaderEntry[];
@@ -4,5 +4,6 @@ export declare enum BlockAction {
4
4
  RATE_LIMIT = "r",
5
5
  CHALLENGE = "j",
6
6
  HYPE_SALE_CHALLENGE = "hsc",
7
- HYPE_SALE_CHALLENGE_LEGACY = "cp"
7
+ HYPE_SALE_CHALLENGE_LEGACY = "cp",
8
+ REDIRECT = "re"
8
9
  }
@@ -40,6 +40,7 @@ export declare class DefaultContext<Req, Res, Supported extends string, Added> i
40
40
  reasons: Partial<Record<ProductName, string>>;
41
41
  score?: number;
42
42
  blockAction: BlockAction;
43
+ redirectUrl?: string;
43
44
  pxhd?: PXHD;
44
45
  pxde?: PXDE;
45
46
  pxdeVerified?: boolean;
@@ -95,6 +95,10 @@ export interface IContext<Req, Res> {
95
95
  * The type of block page to present in case of a block.
96
96
  */
97
97
  blockAction: BlockAction;
98
+ /**
99
+ * The URL to redirect the client to when the action is REDIRECT.
100
+ */
101
+ redirectUrl?: string;
98
102
  /**
99
103
  * The PXHD (PerimeterX Hashed Data) cookie value, if it exists on the request.
100
104
  */
@@ -0,0 +1,17 @@
1
+ import { IConfiguration } from '../../config';
2
+ import { IContext, ReadonlyContext } from '../../context';
3
+ import { IMinimalResponse } from '../../http';
4
+ import { ICors } from '../../cors';
5
+ import { IPhase } from '../IPhase';
6
+ import { PhaseResult } from '../PhaseResult';
7
+ /**
8
+ * `CreateRedirectResponsePhase` is a phase that creates a 302 redirect response when the enforcer
9
+ * action is REDIRECT. It mirrors the structure of `CreateBlockResponsePhase`.
10
+ */
11
+ export declare class CreateRedirectResponsePhase<Req, Res, Supported extends string, Added> implements IPhase<Req, Res> {
12
+ protected readonly config: IConfiguration<Req, Res, Supported, Added>;
13
+ protected readonly cors?: ICors<Req, Res>;
14
+ constructor(config: IConfiguration<Req, Res, Supported, Added>, cors?: ICors<Req, Res>);
15
+ execute(context: IContext<Req, Res>): Promise<PhaseResult>;
16
+ protected addHeadersToResponse(response: IMinimalResponse, context: ReadonlyContext<Req, Res>): Promise<IMinimalResponse>;
17
+ }
@@ -6,6 +6,7 @@ export * from './ParseTokenPhase';
6
6
  export * from './DecideActionPhase';
7
7
  export * from './RiskApiPhase';
8
8
  export * from './CreateBlockResponsePhase';
9
+ export * from './CreateRedirectResponsePhase';
9
10
  export * from './ModifyIncomingRequestPhase';
10
11
  export * from './EnrichContextFromResponsePhase';
11
12
  export * from './ModifyOutgoingResponsePhase';
@@ -42,7 +42,7 @@ export declare class BotDefender<Req, Res, Supported extends string, Added> impl
42
42
  protected getNoTokenS2SCallReason({ pxhd, isMobile, tokenData, }: ReadonlyContext<Req, Res>): BotDefenderS2SCallReason;
43
43
  protected getActionForSuccessfulTokenParse(token: IToken<Req, Res>, isMonitoredRequest: boolean, isSensitiveRequest: boolean): ActionData;
44
44
  enrichContextFromRiskApi(context: ReadonlyContext<Req, Res>): Promise<Partial<BotDefenderData>>;
45
- protected getRiskApiActionData({ riskApiData, productData: { bd }, }: ReadonlyContext<Req, Res>): Promise<ActionData>;
45
+ protected getRiskApiActionData({ riskApiData, productData: { bd }, logger, }: ReadonlyContext<Req, Res>): Promise<ActionData>;
46
46
  protected getSuccessfulRiskApiAction(riskResponse: DeepReadonly<IRiskResponse>, isMonitored: boolean): ActionData;
47
47
  shouldBlock(context: ReadonlyContext<Req, Res>): boolean;
48
48
  createBlockResponse(context: ReadonlyContext<Req, Res>): IMinimalResponse;
@@ -1,5 +1,6 @@
1
1
  import { Action } from '../../../action';
2
2
  import { BotDefenderBlockReason } from './BotDefenderBlockReason';
3
3
  import { BotDefenderPassReason } from './BotDefenderPassReason';
4
+ import { BotDefenderRedirectReason } from './BotDefenderRedirectReason';
4
5
  import { BotDefenderS2SCallReason } from './BotDefenderS2SCallReason';
5
- export type BotDefenderReasonType<A extends Action> = A extends Action.BLOCK ? BotDefenderBlockReason : A extends Action.SIMULATED_BLOCK ? BotDefenderBlockReason : A extends Action.PASS_REQUEST ? BotDefenderPassReason : A extends Action.TRIGGER_RISK_API ? BotDefenderS2SCallReason : never;
6
+ export type BotDefenderReasonType<A extends Action> = A extends Action.BLOCK ? BotDefenderBlockReason : A extends Action.SIMULATED_BLOCK ? BotDefenderBlockReason : A extends Action.PASS_REQUEST ? BotDefenderPassReason : A extends Action.TRIGGER_RISK_API ? BotDefenderS2SCallReason : A extends Action.REDIRECT ? BotDefenderRedirectReason : never;
@@ -0,0 +1,3 @@
1
+ export declare enum BotDefenderRedirectReason {
2
+ S2S = "s2s"
3
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './BotDefenderBlockReason';
2
2
  export * from './BotDefenderPassReason';
3
+ export * from './BotDefenderRedirectReason';
3
4
  export * from './BotDefenderS2SCallReason';
4
5
  export * from './BotDefenderReasonType';
@@ -12,4 +12,7 @@ export type CommonRiskResponsePayload = {
12
12
  drc?: number;
13
13
  additional_risk_info?: string;
14
14
  telemetry_requested?: boolean;
15
+ redirect_url?: string;
16
+ redirect_rule_id?: string;
17
+ redirect_rule_name?: string;
15
18
  };
@@ -15,6 +15,9 @@ export interface IRiskResponse {
15
15
  readonly drc?: number;
16
16
  readonly additionalRiskInfo?: string;
17
17
  readonly telemetryRequested?: boolean;
18
+ readonly redirectUrl?: string;
19
+ readonly redirectRuleId?: string;
20
+ readonly redirectRuleName?: string;
18
21
  validate(): AsyncOrSync<boolean>;
19
22
  toJSON(): RiskResponseJson;
20
23
  }
@@ -25,5 +25,8 @@ export declare abstract class RiskResponseBase<TokenV extends TokenVersion> impl
25
25
  get additionalRiskInfo(): string | undefined;
26
26
  get telemetryRequested(): boolean | undefined;
27
27
  get drc(): number | undefined;
28
+ get redirectUrl(): string | undefined;
29
+ get redirectRuleId(): string | undefined;
30
+ get redirectRuleName(): string | undefined;
28
31
  toJSON(): RiskResponseJson;
29
32
  }
@@ -13,4 +13,7 @@ export type RiskResponseJson = {
13
13
  readonly drc?: number;
14
14
  readonly additionalRiskInfo?: string;
15
15
  readonly telemetryRequested?: boolean;
16
+ readonly redirectUrl?: string;
17
+ readonly redirectRuleId?: string;
18
+ readonly redirectRuleName?: string;
16
19
  };
@@ -15,6 +15,9 @@ export declare class SerializedRiskResponse implements IRiskResponse {
15
15
  readonly score: number | undefined;
16
16
  readonly status?: RiskStatus;
17
17
  readonly uuid: string | undefined;
18
+ readonly redirectUrl: string | undefined;
19
+ readonly redirectRuleId: string | undefined;
20
+ readonly redirectRuleName: string | undefined;
18
21
  constructor(riskResponseJson: RiskResponseJson);
19
22
  validate(): boolean;
20
23
  toJSON(): RiskResponseJson;
@@ -12,4 +12,4 @@ export declare const PUSH_DATA_FEATURE_HEADER_NAME = "x-px-feature";
12
12
  export declare const EMAIL_ADDRESS_REGEX: RegExp;
13
13
  export declare const URL_REGEX: RegExp;
14
14
  export declare const REGEX_STRUCTURE: RegExp;
15
- export declare const CORE_MODULE_VERSION = "JS Core 1.0.1";
15
+ export declare const CORE_MODULE_VERSION = "JS Core 1.1.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perimeterx-js-core",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "typesVersions": {