@tellescope/sdk 1.256.13 → 1.256.14

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 (106) hide show
  1. package/lib/cjs/tests/api_tests/organization_signup.test.d.ts +20 -0
  2. package/lib/cjs/tests/api_tests/organization_signup.test.d.ts.map +1 -0
  3. package/lib/cjs/tests/api_tests/organization_signup.test.js +272 -0
  4. package/lib/cjs/tests/api_tests/organization_signup.test.js.map +1 -0
  5. package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts +30 -0
  6. package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts.map +1 -0
  7. package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js +336 -0
  8. package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js.map +1 -0
  9. package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
  10. package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
  11. package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js +354 -0
  12. package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js.map +1 -0
  13. package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts +46 -0
  14. package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts.map +1 -0
  15. package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js +389 -0
  16. package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js.map +1 -0
  17. package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts +43 -0
  18. package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts.map +1 -0
  19. package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js +324 -0
  20. package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js.map +1 -0
  21. package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts +43 -0
  22. package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
  23. package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js +282 -0
  24. package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js.map +1 -0
  25. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts +5 -0
  26. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -0
  27. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js +329 -0
  28. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js.map +1 -0
  29. package/lib/cjs/tests/setup_isolated.d.ts +68 -0
  30. package/lib/cjs/tests/setup_isolated.d.ts.map +1 -0
  31. package/lib/cjs/tests/setup_isolated.js +232 -0
  32. package/lib/cjs/tests/setup_isolated.js.map +1 -0
  33. package/lib/cjs/tests/tests.d.ts.map +1 -1
  34. package/lib/cjs/tests/tests.js +257 -209
  35. package/lib/cjs/tests/tests.js.map +1 -1
  36. package/lib/cjs/tests/unit_tests/do_not_send_address.test.d.ts +2 -0
  37. package/lib/cjs/tests/unit_tests/do_not_send_address.test.d.ts.map +1 -0
  38. package/lib/cjs/tests/unit_tests/do_not_send_address.test.js +89 -0
  39. package/lib/cjs/tests/unit_tests/do_not_send_address.test.js.map +1 -0
  40. package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.d.ts +17 -0
  41. package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.d.ts.map +1 -0
  42. package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.js +140 -0
  43. package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.js.map +1 -0
  44. package/lib/esm/enduser.d.ts +1 -0
  45. package/lib/esm/enduser.d.ts.map +1 -1
  46. package/lib/esm/sdk.d.ts +3 -2
  47. package/lib/esm/sdk.d.ts.map +1 -1
  48. package/lib/esm/session.d.ts +1 -0
  49. package/lib/esm/session.d.ts.map +1 -1
  50. package/lib/esm/tests/api_tests/organization_signup.test.d.ts +20 -0
  51. package/lib/esm/tests/api_tests/organization_signup.test.d.ts.map +1 -0
  52. package/lib/esm/tests/api_tests/organization_signup.test.js +264 -0
  53. package/lib/esm/tests/api_tests/organization_signup.test.js.map +1 -0
  54. package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts +30 -0
  55. package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts.map +1 -0
  56. package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js +332 -0
  57. package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js.map +1 -0
  58. package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
  59. package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
  60. package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js +350 -0
  61. package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js.map +1 -0
  62. package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts +46 -0
  63. package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts.map +1 -0
  64. package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js +382 -0
  65. package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js.map +1 -0
  66. package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts +43 -0
  67. package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts.map +1 -0
  68. package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js +317 -0
  69. package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js.map +1 -0
  70. package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts +43 -0
  71. package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
  72. package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js +277 -0
  73. package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js.map +1 -0
  74. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts +5 -0
  75. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -0
  76. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js +324 -0
  77. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js.map +1 -0
  78. package/lib/esm/tests/setup_isolated.d.ts +68 -0
  79. package/lib/esm/tests/setup_isolated.d.ts.map +1 -0
  80. package/lib/esm/tests/setup_isolated.js +221 -0
  81. package/lib/esm/tests/setup_isolated.js.map +1 -0
  82. package/lib/esm/tests/tests.d.ts.map +1 -1
  83. package/lib/esm/tests/tests.js +257 -209
  84. package/lib/esm/tests/tests.js.map +1 -1
  85. package/lib/esm/tests/unit_tests/do_not_send_address.test.d.ts +2 -0
  86. package/lib/esm/tests/unit_tests/do_not_send_address.test.d.ts.map +1 -0
  87. package/lib/esm/tests/unit_tests/do_not_send_address.test.js +85 -0
  88. package/lib/esm/tests/unit_tests/do_not_send_address.test.js.map +1 -0
  89. package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.d.ts +17 -0
  90. package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.d.ts.map +1 -0
  91. package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.js +113 -0
  92. package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.js.map +1 -0
  93. package/lib/tsconfig.tsbuildinfo +1 -1
  94. package/package.json +10 -10
  95. package/src/tests/api_tests/organization_signup.test.ts +240 -0
  96. package/src/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.ts +225 -0
  97. package/src/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.ts +162 -0
  98. package/src/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.ts +288 -0
  99. package/src/tests/api_tests/security/F-0192-automated-actions-process-gate.test.ts +238 -0
  100. package/src/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.ts +208 -0
  101. package/src/tests/api_tests/tickets_bulk_assign_from_queue.test.ts +279 -0
  102. package/src/tests/setup_isolated.ts +170 -0
  103. package/src/tests/tests.ts +25 -0
  104. package/src/tests/unit_tests/do_not_send_address.test.ts +60 -0
  105. package/src/tests/unit_tests/openapi_spec_freshness.test.ts +88 -0
  106. package/test_generated.pdf +0 -0
@@ -0,0 +1,324 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
24
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ 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;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.automated_actions_process_gate_tests = void 0;
54
+ require('source-map-support').install();
55
+ var axios_1 = __importDefault(require("axios"));
56
+ var sdk_1 = require("../../../sdk");
57
+ var testing_1 = require("@tellescope/testing");
58
+ var constants_1 = require("@tellescope/constants");
59
+ var setup_1 = require("../../setup");
60
+ var host = process.env.API_URL || 'http://localhost:8080';
61
+ var RAND = function () { return Math.random().toString(36).slice(2, 10); };
62
+ var ALL = 'All';
63
+ var FULL_ACCESS = { create: ALL, read: ALL, update: ALL, delete: ALL };
64
+ var PROCESS_PATH = '/v1/automated-actions/process';
65
+ /**
66
+ * Regression test for F-0192
67
+ * (security-audit/findings/F-0192-automated-actions-process-fire-replay.md).
68
+ *
69
+ * `automated_actions.process` (api.ts:3902) is the WORKER's action runner — its schema description
70
+ * reads "Used by worker for new action types". It was registered with only
71
+ * `{ requiresSubscription: true }`, so the framework gated it on the ordinary
72
+ * `automated_actions.update` verb, which the default provider role holds ('Assigned'). The handler
73
+ * then dispatched `automatedAction.action` through a 14-branch if-chain reaching real vendors:
74
+ * chargebeeChargeCardOnFile (card charge), belugaAutoRx / belugaTriggerRefill / mdiTriggerRefill
75
+ * (prescriptions and refills), createScriptSureDraft (medication draft), healthiePushForms /
76
+ * metriportPushFormResponse (PHI export). It checked neither the row's `status` nor whether it had
77
+ * already run, so any row was replayable: MDI advances to the next scheduled refill each call,
78
+ * Chargebee charges again. And there was no storeUserLog, so the action was attributed to the
79
+ * worker rather than the caller.
80
+ *
81
+ * The fix has three parts:
82
+ * 1. `adminOnly: true` on the api.ts registration (routing.ts:944 reads the flag from that
83
+ * options object — a schema-only flag is inert, which is the separate finding F-0191).
84
+ * 2. A single-fire claim for human callers: an atomic findOneAndUpdate with `status: 'active'` in
85
+ * the filter, stamping `finished` + `processedAt` before the vendor dispatch. The worker
86
+ * (`wasAutomated`) is exempt because its own claim in
87
+ * worker/modules/database.ts:114 is already atomic and it needs the row to stay 'active'
88
+ * across the call so a retryable failure (worker/modules/automations.ts:2053) can be
89
+ * re-claimed after the lock times out.
90
+ * 3. storeUserLog on the human path, recording the caller and the automatedActionId.
91
+ *
92
+ * The seeded row uses `setEnduserStatus`, which is NOT one of the API's if-chain branches, so it
93
+ * falls through to `{ success: false, unimplemented: true }` (api.ts) — no vendor call, no Bedrock,
94
+ * no PHI export. `processAfter` is set an hour out so the live worker cannot race the row (the same
95
+ * trick mdi_trigger_refill / beluga_trigger_refill use).
96
+ *
97
+ * RED (pre-fix): A1 fails (a non-admin holding only `automated_actions.update` reaches the handler)
98
+ * and C1 fails (the second call on the same row executes again instead of being rejected).
99
+ * B1 (admin positive control) passes pre-fix and must keep passing.
100
+ * GREEN (post-fix): all five checks pass.
101
+ */
102
+ var automated_actions_process_gate_tests = function (_a) {
103
+ var sdk = _a.sdk;
104
+ return __awaiter(void 0, void 0, void 0, function () {
105
+ var checks, record, post, roleId, attackerId, enduserId, automatedActionIds, role, rbap, attacker, attackerToken, adminToken, attackerSDK, enduser, seedAction, attackerRow, attackerAttempt, baselineRow, baseline, badProbe, attackerRowAfter, replay, baselineRowAfter, _i, checks_1, c, _b, checks_2, c, _c, automatedActionIds_1, id, _d, _e, _f, _g;
106
+ var _h, _j;
107
+ return __generator(this, function (_k) {
108
+ switch (_k.label) {
109
+ case 0:
110
+ (0, testing_1.log_header)("F-0192: automated_actions.process must be admin-only and single-fire");
111
+ checks = [];
112
+ record = function (label, pass, detail) { return checks.push({ label: label, pass: pass, detail: detail }); };
113
+ post = function (authToken, automatedActionId) { return __awaiter(void 0, void 0, void 0, function () {
114
+ var r;
115
+ return __generator(this, function (_a) {
116
+ switch (_a.label) {
117
+ case 0: return [4 /*yield*/, axios_1.default.request({
118
+ method: 'post',
119
+ url: "".concat(host).concat(PROCESS_PATH),
120
+ data: { automatedActionId: automatedActionId },
121
+ headers: { Authorization: "Bearer ".concat(authToken) },
122
+ validateStatus: function () { return true; },
123
+ })];
124
+ case 1:
125
+ r = _a.sent();
126
+ return [2 /*return*/, { status: r.status, body: r.data }];
127
+ }
128
+ });
129
+ }); };
130
+ roleId = '';
131
+ attackerId = '';
132
+ enduserId = '';
133
+ automatedActionIds = [];
134
+ _k.label = 1;
135
+ case 1:
136
+ _k.trys.push([1, , 15, 34]);
137
+ role = "f0192-automations-".concat(RAND());
138
+ return [4 /*yield*/, sdk.api.role_based_access_permissions.createOne({
139
+ role: role,
140
+ permissions: __assign(__assign({}, constants_1.PROVIDER_PERMISSIONS), { automated_actions: FULL_ACCESS }),
141
+ })];
142
+ case 2:
143
+ rbap = _k.sent();
144
+ roleId = rbap.id;
145
+ return [4 /*yield*/, sdk.api.users.createOne({
146
+ email: "f0192-attacker-".concat(RAND(), "@tellescope.example"),
147
+ fname: 'F0192', lname: 'Staff',
148
+ verifiedEmail: true,
149
+ notificationEmailsDisabled: true,
150
+ roles: [role],
151
+ })];
152
+ case 3:
153
+ attacker = _k.sent();
154
+ attackerId = attacker.id;
155
+ return [4 /*yield*/, sdk.api.users.generate_auth_token({ id: attackerId })];
156
+ case 4:
157
+ attackerToken = (_k.sent()).authToken;
158
+ return [4 /*yield*/, sdk.api.users.generate_auth_token({ id: sdk.userInfo.id })];
159
+ case 5:
160
+ adminToken = (_k.sent()).authToken;
161
+ attackerSDK = new sdk_1.Session({ host: host, authToken: attackerToken });
162
+ return [4 /*yield*/, attackerSDK.refresh_session()];
163
+ case 6:
164
+ _k.sent();
165
+ record('setup: attacker session is a non-admin holding automated_actions.update', attackerSDK.userInfo.id === attackerId && !((_h = attackerSDK.userInfo.roles) !== null && _h !== void 0 ? _h : []).includes('Admin'), "id=".concat(attackerSDK.userInfo.id, " roles=").concat(JSON.stringify(attackerSDK.userInfo.roles)));
166
+ return [4 /*yield*/, sdk.api.endusers.createOne({ fname: 'F0192', lname: 'Patient' })];
167
+ case 7:
168
+ enduser = _k.sent();
169
+ enduserId = enduser.id;
170
+ seedAction = function () { return __awaiter(void 0, void 0, void 0, function () {
171
+ var a;
172
+ return __generator(this, function (_a) {
173
+ switch (_a.label) {
174
+ case 0: return [4 /*yield*/, sdk.api.automated_actions.createOne({
175
+ enduserId: enduserId,
176
+ journeyId: constants_1.PLACEHOLDER_ID,
177
+ automationStepId: constants_1.PLACEHOLDER_ID,
178
+ event: { type: 'onJourneyStart', info: {} },
179
+ action: { type: 'setEnduserStatus', info: { status: 'Working' } },
180
+ status: 'active',
181
+ processAfter: Date.now() + 60 * 60 * 1000,
182
+ })];
183
+ case 1:
184
+ a = _a.sent();
185
+ automatedActionIds.push(a.id);
186
+ return [2 /*return*/, a];
187
+ }
188
+ });
189
+ }); };
190
+ return [4 /*yield*/, seedAction()];
191
+ case 8:
192
+ attackerRow = _k.sent();
193
+ return [4 /*yield*/, post(attackerToken, attackerRow.id)
194
+ // Baseline on the same path with an admin token, so a blanket 401 (expired subscription,
195
+ // bad route, broken fixture) cannot masquerade as a working gate.
196
+ ];
197
+ case 9:
198
+ attackerAttempt = _k.sent();
199
+ return [4 /*yield*/, seedAction()];
200
+ case 10:
201
+ baselineRow = _k.sent();
202
+ return [4 /*yield*/, post(adminToken, baselineRow.id)];
203
+ case 11:
204
+ baseline = _k.sent();
205
+ badProbe = baseline.status === 401 || baseline.status === 403;
206
+ record('A1: a non-admin holding automated_actions.update cannot call process', !badProbe && (attackerAttempt.status === 401 || attackerAttempt.status === 403), "attacker=".concat(attackerAttempt.status, " ").concat(JSON.stringify(attackerAttempt.body), " baseline(admin)=").concat(baseline.status)
207
+ + (badProbe ? ' BAD PROBE: admin baseline was also rejected' : ''));
208
+ return [4 /*yield*/, sdk.api.automated_actions.getOne(attackerRow.id)];
209
+ case 12:
210
+ attackerRowAfter = _k.sent();
211
+ record('A2: a rejected non-admin call leaves the row unfired (status still active)', attackerRowAfter.status === 'active', "status=".concat(attackerRowAfter.status, " (expected active)"));
212
+ /* ===== B: positive control — an admin can still call it ===== */
213
+ record('B1: an admin can still process a pending row', baseline.status === 200 && ((_j = baseline.body) === null || _j === void 0 ? void 0 : _j.unimplemented) === true, "status=".concat(baseline.status, " body=").concat(JSON.stringify(baseline.body)));
214
+ return [4 /*yield*/, post(adminToken, baselineRow.id)];
215
+ case 13:
216
+ replay = _k.sent();
217
+ record('C1: processing the same row twice is rejected (no replay)', replay.status === 409, "second call status=".concat(replay.status, " body=").concat(JSON.stringify(replay.body), " (expected 409)"));
218
+ return [4 /*yield*/, sdk.api.automated_actions.getOne(baselineRow.id)];
219
+ case 14:
220
+ baselineRowAfter = _k.sent();
221
+ record('C2: a processed row is marked finished so neither a replay nor the worker re-runs it', baselineRowAfter.status === 'finished', "status=".concat(baselineRowAfter.status, " (expected finished)"));
222
+ /* ===== report: print full table first, then assert (harness exits on first failure) ===== */
223
+ console.log('\n F-0192 check summary:');
224
+ for (_i = 0, checks_1 = checks; _i < checks_1.length; _i++) {
225
+ c = checks_1[_i];
226
+ console.log(" ".concat(c.pass ? 'PASS' : 'FAIL', " ").concat(c.label, " \u2014 ").concat(c.detail));
227
+ }
228
+ console.log('');
229
+ for (_b = 0, checks_2 = checks; _b < checks_2.length; _b++) {
230
+ c = checks_2[_b];
231
+ (0, testing_1.assert)(c.pass, "".concat(c.label, " \u2014 ").concat(c.detail), c.label);
232
+ }
233
+ return [3 /*break*/, 34];
234
+ case 15:
235
+ _c = 0, automatedActionIds_1 = automatedActionIds;
236
+ _k.label = 16;
237
+ case 16:
238
+ if (!(_c < automatedActionIds_1.length)) return [3 /*break*/, 21];
239
+ id = automatedActionIds_1[_c];
240
+ _k.label = 17;
241
+ case 17:
242
+ _k.trys.push([17, 19, , 20]);
243
+ return [4 /*yield*/, sdk.api.automated_actions.deleteOne(id)];
244
+ case 18:
245
+ _k.sent();
246
+ return [3 /*break*/, 20];
247
+ case 19:
248
+ _d = _k.sent();
249
+ return [3 /*break*/, 20];
250
+ case 20:
251
+ _c++;
252
+ return [3 /*break*/, 16];
253
+ case 21:
254
+ if (!enduserId) return [3 /*break*/, 25];
255
+ _k.label = 22;
256
+ case 22:
257
+ _k.trys.push([22, 24, , 25]);
258
+ return [4 /*yield*/, sdk.api.endusers.deleteOne(enduserId)];
259
+ case 23:
260
+ _k.sent();
261
+ return [3 /*break*/, 25];
262
+ case 24:
263
+ _e = _k.sent();
264
+ return [3 /*break*/, 25];
265
+ case 25:
266
+ if (!attackerId) return [3 /*break*/, 29];
267
+ _k.label = 26;
268
+ case 26:
269
+ _k.trys.push([26, 28, , 29]);
270
+ return [4 /*yield*/, sdk.api.users.deleteOne(attackerId)];
271
+ case 27:
272
+ _k.sent();
273
+ return [3 /*break*/, 29];
274
+ case 28:
275
+ _f = _k.sent();
276
+ return [3 /*break*/, 29];
277
+ case 29:
278
+ if (!roleId) return [3 /*break*/, 33];
279
+ _k.label = 30;
280
+ case 30:
281
+ _k.trys.push([30, 32, , 33]);
282
+ return [4 /*yield*/, sdk.api.role_based_access_permissions.deleteOne(roleId)];
283
+ case 31:
284
+ _k.sent();
285
+ return [3 /*break*/, 33];
286
+ case 32:
287
+ _g = _k.sent();
288
+ return [3 /*break*/, 33];
289
+ case 33: return [7 /*endfinally*/];
290
+ case 34: return [2 /*return*/];
291
+ }
292
+ });
293
+ });
294
+ };
295
+ exports.automated_actions_process_gate_tests = automated_actions_process_gate_tests;
296
+ // Allow running this test file independently
297
+ if (require.main === module) {
298
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
299
+ var sdk_2 = new sdk_1.Session({ host: host });
300
+ var sdkNonAdmin_1 = new sdk_1.Session({ host: host });
301
+ var runTests = function () { return __awaiter(void 0, void 0, void 0, function () {
302
+ return __generator(this, function (_a) {
303
+ switch (_a.label) {
304
+ case 0: return [4 /*yield*/, (0, setup_1.setup_tests)(sdk_2, sdkNonAdmin_1)];
305
+ case 1:
306
+ _a.sent();
307
+ return [4 /*yield*/, (0, exports.automated_actions_process_gate_tests)({ sdk: sdk_2, sdkNonAdmin: sdkNonAdmin_1 })];
308
+ case 2:
309
+ _a.sent();
310
+ return [2 /*return*/];
311
+ }
312
+ });
313
+ }); };
314
+ runTests()
315
+ .then(function () {
316
+ console.log("✅ F-0192 automated_actions.process gate test suite completed successfully");
317
+ process.exit(0);
318
+ })
319
+ .catch(function (error) {
320
+ console.error("❌ F-0192 automated_actions.process gate test suite failed:", error);
321
+ process.exit(1);
322
+ });
323
+ }
324
+ //# sourceMappingURL=F-0192-automated-actions-process-gate.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"F-0192-automated-actions-process-gate.test.js","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/F-0192-automated-actions-process-gate.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { Check } from "../../setup_isolated";
2
+ /**
3
+ * Regression test for F-0196
4
+ * (security-audit/findings/F-0196-add-to-journey-plants-foreign-enduserid.md).
5
+ *
6
+ * `endusers.add_to_journey` inserted an `automated_actions` row for EACH caller-supplied `enduserId`
7
+ * with only mongoId-shape validation and no tenant-membership check. The preceding
8
+ * `DB.endusers.updateMany` is scoped but its result was discarded, and
9
+ * `DB.automated_actions.insertMany` is a direct write that bypasses the `ensureValidDependentKeys`
10
+ * foreign-key check the generic CRUD route gets. The resulting row is stamped
11
+ * `businessId: <caller's tenant>` while carrying `enduserId: <a victim in another tenant>`.
12
+ *
13
+ * The worker then dereferences that enduser with the raw driver and no `businessId`
14
+ * (worker/modules/automations.ts, the F-0074/F-0111/F-0159 family), so the victim's record loads
15
+ * under the attacker's automation: `pushFormsToPortal` copies the victim's PHI into the attacker's
16
+ * `form_responses` (readable by their staff) and `iterableCustomEvent` writes an attacker-authored
17
+ * note into the victim's chart. F-0196 is the reachability that made those three exploitable.
18
+ *
19
+ * There are TWO independent plant sites and a fix to only one is a half fix:
20
+ * - inline branch, taken when `automationStepId` is supplied (api.ts add_to_journey handler)
21
+ * - module branch, taken when it is not: `handle_automation_event`'s
22
+ * `validEnduserIds = conditionsQuery ? <scoped find> : event.enduserIds` passes caller ids
23
+ * straight through for a step with NO conditions.
24
+ * Both are covered below (A1/A2).
25
+ *
26
+ * Design notes:
27
+ * - Two provisioned tenants, so the victim is a real foreign enduser and we can also assert their
28
+ * record was not mutated. Both must come from provisioning: `add_to_journey` is
29
+ * `requiresSubscription: true`, and a `create_and_join` org is born with an expired subscription.
30
+ * - The steps use `setEnduserStatus` — no vendor, no Bedrock — and `startAt` is an hour out so a
31
+ * running worker cannot process the rows before we read them.
32
+ * - `throttle` is deliberately omitted: it is the only way add_to_journey's per-pair rate limit can
33
+ * throw.
34
+ * - Everything is observable through `automated_actions` reads, so the worker is never needed.
35
+ *
36
+ * RED (pre-fix): A1, A2 and B2 fail — the victim's id appears in the attacker tenant's
37
+ * automated_actions. GREEN (post-fix): the foreign id is silently dropped and the caller's own
38
+ * endusers still enroll normally.
39
+ */
40
+ export declare const add_to_journey_foreign_enduserid_checks: () => Promise<Check[]>;
41
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
42
+ export declare const add_to_journey_foreign_enduserid_tests: () => Promise<void>;
43
+ //# sourceMappingURL=F-0196-add-to-journey-foreign-enduserid.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"F-0196-add-to-journey-foreign-enduserid.test.d.ts","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,EAEN,MAAM,sBAAsB,CAAA;AAS7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,uCAAuC,QAAa,QAAQ,KAAK,EAAE,CAuI/E,CAAA;AAED,kFAAkF;AAClF,eAAO,MAAM,sCAAsC,qBAElD,CAAA"}