@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,389 @@
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.schema_adminonly_enforcement_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
+ /**
65
+ * Regression test for F-0191
66
+ * (security-audit/findings/F-0191-schema-adminonly-not-enforced.md).
67
+ *
68
+ * `adminOnly: true` on a CUSTOM action in schema.ts was documentation-only. The runtime gate
69
+ * (routing.ts:944) reads the flag off the options object built by getCustomEndpointFields, and
70
+ * api.ts:609 set `adminOnly: o.adminOnly` without ever consulting `action.adminOnly`. So every
71
+ * admin-only custom action had to declare the flag twice, and the copies desynced: 11 of 20
72
+ * declared custom actions were unenforced at runtime while both public doc artifacts
73
+ * (openapi.json and the unauthenticated GET /documentation feed, which read the SCHEMA flag)
74
+ * advertised "**Admin only.**".
75
+ *
76
+ * DEFAULT CRUD actions never had this bug — routing.ts:2434 etc. read
77
+ * model.defaultActions.<op>.adminOnly straight off the schema. The fix
78
+ * (`adminOnly: o.adminOnly ?? action.adminOnly`) makes custom actions behave the same way.
79
+ *
80
+ * Five of the 11 were de-flagged in the schema instead, because the flag itself was wrong and
81
+ * enforcing it would revert a deliberate decision: users.invite_user carries an explicit
82
+ * "no longer admin only, but controlled by users.create" and send_invitation_to_existing is its
83
+ * sibling; organizations.create_and_join is the self-service signup path and would 401 every new
84
+ * org (a fresh user has no roles — the handler is what grants Admin); sync_note_to_canvas is
85
+ * designed around `access: 'read'` for non-admin callers; background_errors.mark_read substitutes
86
+ * its own RBAC check that intentionally admits non-admins holding the verb.
87
+ *
88
+ * SAFETY: every probe fails EARLY inside its handler — after the adminOnly gate but before any
89
+ * vendor call. Pre-fix these requests really do reach the handler, and these handlers talk to
90
+ * Twilio, Athena and Canvas:
91
+ * - trigger_events : no `action` key + an absent triggerId => the loop `continue`s (inert)
92
+ * - athena-subscription: frequency 1 trips the `frequency < 5` guard on the handler's first line
93
+ * - sync-athena : 404s on the missing Athena integration (skipped if one exists)
94
+ * - link-twilio : takes NO parameters and can CREATE A REAL TWILIO SUBACCOUNT, so it is
95
+ * probed only when the org already has a twilioSID (handler short-circuits)
96
+ * - twilio-embed : no phoneNumberSid => 404 no-config or 400, both before any Twilio call
97
+ * - handle-autoreply : absent enduserId => `if (!enduser) return` (communications.ts:42)
98
+ * Input validation runs BEFORE the adminOnly gate (routing.ts:921 orders access, then validate),
99
+ * so every probe body must be schema-VALID or the test would prove nothing.
100
+ *
101
+ * RED (pre-fix): the six group-B rows fail — each reaches its handler and returns a non-401.
102
+ * GREEN (post-fix): all six return 401 "Admin access only"; the group-A controls are unchanged.
103
+ */
104
+ var schema_adminonly_enforcement_tests = function (_a) {
105
+ var sdk = _a.sdk;
106
+ return __awaiter(void 0, void 0, void 0, function () {
107
+ var checks, record, request, isAdminDenial, summarize, roleId, attackerId, enduserId, role, rbap, attacker, attackerToken, adminToken, attackerSDK, enduser, doomed, ABSENT_ID, athenaIntegrationExists, existing, _b, orgHasTwilioSID, org, _c, groupB, _i, groupB_1, probe, attackerResult, baseline, badProbe, groupA, _d, groupA_1, probe, r, _e, checks_1, c, _f, checks_2, c, _g, _h, _j;
108
+ var _k;
109
+ return __generator(this, function (_l) {
110
+ switch (_l.label) {
111
+ case 0:
112
+ (0, testing_1.log_header)("F-0191: schema-declared adminOnly must be enforced on custom actions");
113
+ checks = [];
114
+ record = function (label, pass, detail) { return checks.push({ label: label, pass: pass, detail: detail }); };
115
+ request = function (authToken, method, path, data) { return __awaiter(void 0, void 0, void 0, function () {
116
+ var r;
117
+ return __generator(this, function (_a) {
118
+ switch (_a.label) {
119
+ case 0: return [4 /*yield*/, axios_1.default.request(__assign(__assign({ method: method, url: "".concat(host, "/v1").concat(path) }, method === 'get' ? { params: data } : { data: data }), { headers: { Authorization: "Bearer ".concat(authToken) }, validateStatus: function () { return true; } }))];
120
+ case 1:
121
+ r = _a.sent();
122
+ return [2 /*return*/, { status: r.status, body: r.data }];
123
+ }
124
+ });
125
+ }); };
126
+ isAdminDenial = function (r) {
127
+ var _a;
128
+ return (r.status === 401 && ((_a = r.body) === null || _a === void 0 ? void 0 : _a.message) === 'Admin access only');
129
+ };
130
+ summarize = function (r) {
131
+ var _a;
132
+ return ("".concat(r.status, " ").concat(((_a = JSON.stringify(r.body)) !== null && _a !== void 0 ? _a : '').slice(0, 120)));
133
+ };
134
+ roleId = '';
135
+ attackerId = '';
136
+ enduserId = '';
137
+ _l.label = 1;
138
+ case 1:
139
+ _l.trys.push([1, , 27, 40]);
140
+ role = "f0191-staff-".concat(RAND());
141
+ return [4 /*yield*/, sdk.api.role_based_access_permissions.createOne({
142
+ role: role,
143
+ permissions: __assign(__assign({}, constants_1.PROVIDER_PERMISSIONS), {
144
+ // grant exactly the verbs the actions under test declare, so a rejection proves the ADMIN
145
+ // gate fired rather than ordinary RBAC
146
+ automation_triggers: FULL_ACCESS, organizations: FULL_ACCESS, availability_blocks: FULL_ACCESS, background_errors: FULL_ACCESS, users: FULL_ACCESS }),
147
+ })];
148
+ case 2:
149
+ rbap = _l.sent();
150
+ roleId = rbap.id;
151
+ return [4 /*yield*/, sdk.api.users.createOne({
152
+ email: "f0191-attacker-".concat(RAND(), "@tellescope.example"),
153
+ fname: 'F0191', lname: 'Staff',
154
+ verifiedEmail: true,
155
+ notificationEmailsDisabled: true,
156
+ roles: [role],
157
+ })];
158
+ case 3:
159
+ attacker = _l.sent();
160
+ attackerId = attacker.id;
161
+ return [4 /*yield*/, sdk.api.users.generate_auth_token({ id: attackerId })];
162
+ case 4:
163
+ attackerToken = (_l.sent()).authToken;
164
+ return [4 /*yield*/, sdk.api.users.generate_auth_token({ id: sdk.userInfo.id })];
165
+ case 5:
166
+ adminToken = (_l.sent()).authToken;
167
+ attackerSDK = new sdk_1.Session({ host: host, authToken: attackerToken });
168
+ return [4 /*yield*/, attackerSDK.refresh_session()];
169
+ case 6:
170
+ _l.sent();
171
+ record('setup: attacker is a non-admin holding every verb under test', attackerSDK.userInfo.id === attackerId && !((_k = attackerSDK.userInfo.roles) !== null && _k !== void 0 ? _k : []).includes('Admin'), "id=".concat(attackerSDK.userInfo.id, " roles=").concat(JSON.stringify(attackerSDK.userInfo.roles)));
172
+ return [4 /*yield*/, sdk.api.endusers.createOne({ fname: 'F0191', lname: 'Patient' })];
173
+ case 7:
174
+ enduser = _l.sent();
175
+ enduserId = enduser.id;
176
+ return [4 /*yield*/, sdk.api.endusers.createOne({ fname: 'F0191', lname: 'Absent' })];
177
+ case 8:
178
+ doomed = _l.sent();
179
+ ABSENT_ID = doomed.id;
180
+ return [4 /*yield*/, sdk.api.endusers.deleteOne(ABSENT_ID)
181
+ /* ===== environment guards for the two probes input alone cannot make safe ===== */
182
+ ];
183
+ case 9:
184
+ _l.sent();
185
+ athenaIntegrationExists = false;
186
+ _l.label = 10;
187
+ case 10:
188
+ _l.trys.push([10, 12, , 13]);
189
+ return [4 /*yield*/, sdk.api.integrations.load_redacted({})];
190
+ case 11:
191
+ existing = _l.sent();
192
+ athenaIntegrationExists = !!existing.integrations.find(function (i) { var _a; return /athena/i.test((_a = i.title) !== null && _a !== void 0 ? _a : ''); });
193
+ return [3 /*break*/, 13];
194
+ case 12:
195
+ _b = _l.sent();
196
+ return [3 /*break*/, 13];
197
+ case 13:
198
+ orgHasTwilioSID = false;
199
+ _l.label = 14;
200
+ case 14:
201
+ _l.trys.push([14, 16, , 17]);
202
+ return [4 /*yield*/, sdk.api.organizations.getOne(sdk.userInfo.businessId)];
203
+ case 15:
204
+ org = _l.sent();
205
+ orgHasTwilioSID = !!(org === null || org === void 0 ? void 0 : org.twilioSID);
206
+ return [3 /*break*/, 17];
207
+ case 16:
208
+ _c = _l.sent();
209
+ return [3 /*break*/, 17];
210
+ case 17:
211
+ groupB = [
212
+ {
213
+ label: 'automation_triggers.trigger_events',
214
+ method: 'post', path: '/automation-triggers/trigger-events',
215
+ // no `action` key: stays off the arbitrary-action-execution branch entirely
216
+ data: { triggers: [{ enduserId: enduserId, automationTriggerId: ABSENT_ID }] },
217
+ },
218
+ {
219
+ label: 'organizations.add_athena_subscription',
220
+ method: 'post', path: '/organizations/athena-subscription',
221
+ data: { type: 'patients', frequency: 1 }, // trips `frequency < 5` on the handler's first line
222
+ },
223
+ {
224
+ label: 'organizations.sync_athena_subscription',
225
+ method: 'post', path: '/organizations/sync-athena-subscription',
226
+ data: { type: 'patients' },
227
+ skip: athenaIntegrationExists
228
+ ? 'org HAS an Athena integration — probing would call Athena.process_subscription for real'
229
+ : undefined,
230
+ },
231
+ {
232
+ label: 'organizations.link_twilio',
233
+ method: 'post', path: '/organizations/link-twilio',
234
+ data: {},
235
+ skip: orgHasTwilioSID
236
+ ? undefined
237
+ : 'org has no twilioSID — probing would CREATE A REAL TWILIO SUBACCOUNT',
238
+ },
239
+ {
240
+ label: 'organizations.load_twilio_embed',
241
+ method: 'get', path: '/organizations/twilio-embed',
242
+ data: { type: 'tfn-verification' }, // no phoneNumberSid => rejected before any Twilio call
243
+ },
244
+ {
245
+ label: 'availability_blocks.handle_autoreply',
246
+ method: 'post', path: '/handle-out-of-office-autoreply',
247
+ data: { channel: 'Email', enduserId: ABSENT_ID }, // communications.ts:42 `if (!enduser) return`
248
+ },
249
+ ];
250
+ _i = 0, groupB_1 = groupB;
251
+ _l.label = 18;
252
+ case 18:
253
+ if (!(_i < groupB_1.length)) return [3 /*break*/, 22];
254
+ probe = groupB_1[_i];
255
+ if (probe.skip) {
256
+ record("B: ".concat(probe.label, " rejects a non-admin"), true, "SKIPPED (safety) \u2014 ".concat(probe.skip));
257
+ return [3 /*break*/, 21];
258
+ }
259
+ return [4 /*yield*/, request(attackerToken, probe.method, probe.path, probe.data)
260
+ // baseline: an admin must NOT be admin-denied on the same path, so a blanket 401 can't pass
261
+ ];
262
+ case 19:
263
+ attackerResult = _l.sent();
264
+ return [4 /*yield*/, request(adminToken, probe.method, probe.path, probe.data)];
265
+ case 20:
266
+ baseline = _l.sent();
267
+ badProbe = isAdminDenial(baseline);
268
+ record("B: ".concat(probe.label, " rejects a non-admin"), !badProbe && isAdminDenial(attackerResult), "attacker=".concat(summarize(attackerResult), " | admin baseline=").concat(baseline.status)
269
+ + (badProbe ? ' BAD PROBE: admin was also admin-denied' : ''));
270
+ _l.label = 21;
271
+ case 21:
272
+ _i++;
273
+ return [3 /*break*/, 18];
274
+ case 22:
275
+ groupA = [
276
+ {
277
+ label: 'organizations.sync_note_to_canvas',
278
+ method: 'post', path: '/organizations/sync-note-to-canvas',
279
+ data: { enduserId: ABSENT_ID, note: 'F-0191 control' }, // 404 Enduser not found
280
+ },
281
+ {
282
+ label: 'background_errors.mark_read',
283
+ method: 'post', path: '/background-errors/mark-read', data: {},
284
+ },
285
+ {
286
+ label: 'users.send_invitation_to_existing',
287
+ method: 'post', path: '/invite-existing-user-to-organization',
288
+ // takes userId (not email); an absent id hits RecordNotFoundError, well short of any invite
289
+ data: { userId: ABSENT_ID },
290
+ },
291
+ ];
292
+ _d = 0, groupA_1 = groupA;
293
+ _l.label = 23;
294
+ case 23:
295
+ if (!(_d < groupA_1.length)) return [3 /*break*/, 26];
296
+ probe = groupA_1[_d];
297
+ return [4 /*yield*/, request(attackerToken, probe.method, probe.path, probe.data)];
298
+ case 24:
299
+ r = _l.sent();
300
+ record("A: ".concat(probe.label, " stays reachable by a non-admin (flag intentionally dropped)"), !isAdminDenial(r), "".concat(summarize(r), " (expected a handler response, not 401 Admin access only)"));
301
+ _l.label = 25;
302
+ case 25:
303
+ _d++;
304
+ return [3 /*break*/, 23];
305
+ case 26:
306
+ /* ===== report: print full table first, then assert (harness exits on first failure) ===== */
307
+ console.log('\n F-0191 check summary:');
308
+ for (_e = 0, checks_1 = checks; _e < checks_1.length; _e++) {
309
+ c = checks_1[_e];
310
+ console.log(" ".concat(c.pass ? 'PASS' : 'FAIL', " ").concat(c.label, " \u2014 ").concat(c.detail));
311
+ }
312
+ console.log('');
313
+ for (_f = 0, checks_2 = checks; _f < checks_2.length; _f++) {
314
+ c = checks_2[_f];
315
+ (0, testing_1.assert)(c.pass, "".concat(c.label, " \u2014 ").concat(c.detail), c.label);
316
+ }
317
+ return [3 /*break*/, 40];
318
+ case 27:
319
+ if (!enduserId) return [3 /*break*/, 31];
320
+ _l.label = 28;
321
+ case 28:
322
+ _l.trys.push([28, 30, , 31]);
323
+ return [4 /*yield*/, sdk.api.endusers.deleteOne(enduserId)];
324
+ case 29:
325
+ _l.sent();
326
+ return [3 /*break*/, 31];
327
+ case 30:
328
+ _g = _l.sent();
329
+ return [3 /*break*/, 31];
330
+ case 31:
331
+ if (!attackerId) return [3 /*break*/, 35];
332
+ _l.label = 32;
333
+ case 32:
334
+ _l.trys.push([32, 34, , 35]);
335
+ return [4 /*yield*/, sdk.api.users.deleteOne(attackerId)];
336
+ case 33:
337
+ _l.sent();
338
+ return [3 /*break*/, 35];
339
+ case 34:
340
+ _h = _l.sent();
341
+ return [3 /*break*/, 35];
342
+ case 35:
343
+ if (!roleId) return [3 /*break*/, 39];
344
+ _l.label = 36;
345
+ case 36:
346
+ _l.trys.push([36, 38, , 39]);
347
+ return [4 /*yield*/, sdk.api.role_based_access_permissions.deleteOne(roleId)];
348
+ case 37:
349
+ _l.sent();
350
+ return [3 /*break*/, 39];
351
+ case 38:
352
+ _j = _l.sent();
353
+ return [3 /*break*/, 39];
354
+ case 39: return [7 /*endfinally*/];
355
+ case 40: return [2 /*return*/];
356
+ }
357
+ });
358
+ });
359
+ };
360
+ exports.schema_adminonly_enforcement_tests = schema_adminonly_enforcement_tests;
361
+ // Allow running this test file independently
362
+ if (require.main === module) {
363
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
364
+ var sdk_2 = new sdk_1.Session({ host: host });
365
+ var sdkNonAdmin_1 = new sdk_1.Session({ host: host });
366
+ var runTests = function () { return __awaiter(void 0, void 0, void 0, function () {
367
+ return __generator(this, function (_a) {
368
+ switch (_a.label) {
369
+ case 0: return [4 /*yield*/, (0, setup_1.setup_tests)(sdk_2, sdkNonAdmin_1)];
370
+ case 1:
371
+ _a.sent();
372
+ return [4 /*yield*/, (0, exports.schema_adminonly_enforcement_tests)({ sdk: sdk_2, sdkNonAdmin: sdkNonAdmin_1 })];
373
+ case 2:
374
+ _a.sent();
375
+ return [2 /*return*/];
376
+ }
377
+ });
378
+ }); };
379
+ runTests()
380
+ .then(function () {
381
+ console.log("✅ F-0191 schema adminOnly enforcement test suite completed successfully");
382
+ process.exit(0);
383
+ })
384
+ .catch(function (error) {
385
+ console.error("❌ F-0191 schema adminOnly enforcement test suite failed:", error);
386
+ process.exit(1);
387
+ });
388
+ }
389
+ //# sourceMappingURL=F-0191-schema-adminonly-enforcement.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"F-0191-schema-adminonly-enforcement.test.js","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { Session } from "../../../sdk";
2
+ /**
3
+ * Regression test for F-0192
4
+ * (security-audit/findings/F-0192-automated-actions-process-fire-replay.md).
5
+ *
6
+ * `automated_actions.process` (api.ts:3902) is the WORKER's action runner — its schema description
7
+ * reads "Used by worker for new action types". It was registered with only
8
+ * `{ requiresSubscription: true }`, so the framework gated it on the ordinary
9
+ * `automated_actions.update` verb, which the default provider role holds ('Assigned'). The handler
10
+ * then dispatched `automatedAction.action` through a 14-branch if-chain reaching real vendors:
11
+ * chargebeeChargeCardOnFile (card charge), belugaAutoRx / belugaTriggerRefill / mdiTriggerRefill
12
+ * (prescriptions and refills), createScriptSureDraft (medication draft), healthiePushForms /
13
+ * metriportPushFormResponse (PHI export). It checked neither the row's `status` nor whether it had
14
+ * already run, so any row was replayable: MDI advances to the next scheduled refill each call,
15
+ * Chargebee charges again. And there was no storeUserLog, so the action was attributed to the
16
+ * worker rather than the caller.
17
+ *
18
+ * The fix has three parts:
19
+ * 1. `adminOnly: true` on the api.ts registration (routing.ts:944 reads the flag from that
20
+ * options object — a schema-only flag is inert, which is the separate finding F-0191).
21
+ * 2. A single-fire claim for human callers: an atomic findOneAndUpdate with `status: 'active'` in
22
+ * the filter, stamping `finished` + `processedAt` before the vendor dispatch. The worker
23
+ * (`wasAutomated`) is exempt because its own claim in
24
+ * worker/modules/database.ts:114 is already atomic and it needs the row to stay 'active'
25
+ * across the call so a retryable failure (worker/modules/automations.ts:2053) can be
26
+ * re-claimed after the lock times out.
27
+ * 3. storeUserLog on the human path, recording the caller and the automatedActionId.
28
+ *
29
+ * The seeded row uses `setEnduserStatus`, which is NOT one of the API's if-chain branches, so it
30
+ * falls through to `{ success: false, unimplemented: true }` (api.ts) — no vendor call, no Bedrock,
31
+ * no PHI export. `processAfter` is set an hour out so the live worker cannot race the row (the same
32
+ * trick mdi_trigger_refill / beluga_trigger_refill use).
33
+ *
34
+ * RED (pre-fix): A1 fails (a non-admin holding only `automated_actions.update` reaches the handler)
35
+ * and C1 fails (the second call on the same row executes again instead of being rejected).
36
+ * B1 (admin positive control) passes pre-fix and must keep passing.
37
+ * GREEN (post-fix): all five checks pass.
38
+ */
39
+ export declare const automated_actions_process_gate_tests: ({ sdk }: {
40
+ sdk: Session;
41
+ sdkNonAdmin: Session;
42
+ }) => Promise<void>;
43
+ //# sourceMappingURL=F-0192-automated-actions-process-gate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"F-0192-automated-actions-process-gate.test.d.ts","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/F-0192-automated-actions-process-gate.test.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AA0BtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,oCAAoC;SAA2B,OAAO;iBAAe,OAAO;mBAoJxG,CAAA"}