@tellescope/sdk 1.251.0 → 1.252.1

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 (131) hide show
  1. package/lib/cjs/tests/api_tests/calendar_canvas_coding_clear.test.d.ts +6 -0
  2. package/lib/cjs/tests/api_tests/calendar_canvas_coding_clear.test.d.ts.map +1 -0
  3. package/lib/cjs/tests/api_tests/calendar_canvas_coding_clear.test.js +139 -0
  4. package/lib/cjs/tests/api_tests/calendar_canvas_coding_clear.test.js.map +1 -0
  5. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.d.ts +6 -0
  6. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.d.ts.map +1 -0
  7. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.js +337 -0
  8. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.js.map +1 -0
  9. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.d.ts +6 -0
  10. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.d.ts.map +1 -0
  11. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.js +287 -0
  12. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.js.map +1 -0
  13. package/lib/cjs/tests/api_tests/integrations_redacted.test.d.ts.map +1 -1
  14. package/lib/cjs/tests/api_tests/integrations_redacted.test.js +30 -20
  15. package/lib/cjs/tests/api_tests/integrations_redacted.test.js.map +1 -1
  16. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts.map +1 -1
  17. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.js +234 -198
  18. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.js.map +1 -1
  19. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts +28 -0
  20. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts.map +1 -0
  21. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js +349 -0
  22. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js.map +1 -0
  23. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts +28 -0
  24. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts.map +1 -0
  25. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js +247 -0
  26. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js.map +1 -0
  27. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts +29 -0
  28. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts.map +1 -0
  29. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.js +278 -0
  30. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.js.map +1 -0
  31. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts +24 -0
  32. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
  33. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js +201 -0
  34. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js.map +1 -0
  35. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts +2 -0
  36. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts.map +1 -0
  37. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.js +148 -0
  38. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.js.map +1 -0
  39. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts +2 -0
  40. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts.map +1 -0
  41. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.js +88 -0
  42. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.js.map +1 -0
  43. package/lib/cjs/tests/api_tests/security/F-0053-cascade-role-rename-cross-tenant.test.d.ts +32 -0
  44. package/lib/cjs/tests/api_tests/security/F-0053-cascade-role-rename-cross-tenant.test.d.ts.map +1 -0
  45. package/lib/cjs/tests/api_tests/security/F-0053-cascade-role-rename-cross-tenant.test.js +237 -0
  46. package/lib/cjs/tests/api_tests/security/F-0053-cascade-role-rename-cross-tenant.test.js.map +1 -0
  47. package/lib/cjs/tests/api_tests/security/F-0076-self-admin-role-assignment.test.d.ts +38 -0
  48. package/lib/cjs/tests/api_tests/security/F-0076-self-admin-role-assignment.test.d.ts.map +1 -0
  49. package/lib/cjs/tests/api_tests/security/F-0076-self-admin-role-assignment.test.js +222 -0
  50. package/lib/cjs/tests/api_tests/security/F-0076-self-admin-role-assignment.test.js.map +1 -0
  51. package/lib/cjs/tests/api_tests/user_portal_settings.test.d.ts +6 -0
  52. package/lib/cjs/tests/api_tests/user_portal_settings.test.d.ts.map +1 -0
  53. package/lib/cjs/tests/api_tests/user_portal_settings.test.js +301 -0
  54. package/lib/cjs/tests/api_tests/user_portal_settings.test.js.map +1 -0
  55. package/lib/cjs/tests/tests.d.ts.map +1 -1
  56. package/lib/cjs/tests/tests.js +198 -151
  57. package/lib/cjs/tests/tests.js.map +1 -1
  58. package/lib/esm/tests/api_tests/calendar_canvas_coding_clear.test.d.ts +6 -0
  59. package/lib/esm/tests/api_tests/calendar_canvas_coding_clear.test.d.ts.map +1 -0
  60. package/lib/esm/tests/api_tests/calendar_canvas_coding_clear.test.js +135 -0
  61. package/lib/esm/tests/api_tests/calendar_canvas_coding_clear.test.js.map +1 -0
  62. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.d.ts +6 -0
  63. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.d.ts.map +1 -0
  64. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.js +333 -0
  65. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.js.map +1 -0
  66. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.d.ts +6 -0
  67. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.d.ts.map +1 -0
  68. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.js +280 -0
  69. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.js.map +1 -0
  70. package/lib/esm/tests/api_tests/integrations_redacted.test.d.ts.map +1 -1
  71. package/lib/esm/tests/api_tests/integrations_redacted.test.js +30 -20
  72. package/lib/esm/tests/api_tests/integrations_redacted.test.js.map +1 -1
  73. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts.map +1 -1
  74. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.js +235 -199
  75. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.js.map +1 -1
  76. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts +28 -0
  77. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts.map +1 -0
  78. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js +345 -0
  79. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js.map +1 -0
  80. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts +28 -0
  81. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts.map +1 -0
  82. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js +243 -0
  83. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js.map +1 -0
  84. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts +29 -0
  85. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts.map +1 -0
  86. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.js +271 -0
  87. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.js.map +1 -0
  88. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts +24 -0
  89. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
  90. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js +194 -0
  91. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js.map +1 -0
  92. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts +2 -0
  93. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts.map +1 -0
  94. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.js +144 -0
  95. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.js.map +1 -0
  96. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts +2 -0
  97. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts.map +1 -0
  98. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.js +84 -0
  99. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.js.map +1 -0
  100. package/lib/esm/tests/api_tests/security/F-0053-cascade-role-rename-cross-tenant.test.d.ts +32 -0
  101. package/lib/esm/tests/api_tests/security/F-0053-cascade-role-rename-cross-tenant.test.d.ts.map +1 -0
  102. package/lib/esm/tests/api_tests/security/F-0053-cascade-role-rename-cross-tenant.test.js +233 -0
  103. package/lib/esm/tests/api_tests/security/F-0053-cascade-role-rename-cross-tenant.test.js.map +1 -0
  104. package/lib/esm/tests/api_tests/security/F-0076-self-admin-role-assignment.test.d.ts +38 -0
  105. package/lib/esm/tests/api_tests/security/F-0076-self-admin-role-assignment.test.d.ts.map +1 -0
  106. package/lib/esm/tests/api_tests/security/F-0076-self-admin-role-assignment.test.js +218 -0
  107. package/lib/esm/tests/api_tests/security/F-0076-self-admin-role-assignment.test.js.map +1 -0
  108. package/lib/esm/tests/api_tests/user_portal_settings.test.d.ts +6 -0
  109. package/lib/esm/tests/api_tests/user_portal_settings.test.d.ts.map +1 -0
  110. package/lib/esm/tests/api_tests/user_portal_settings.test.js +297 -0
  111. package/lib/esm/tests/api_tests/user_portal_settings.test.js.map +1 -0
  112. package/lib/esm/tests/tests.d.ts.map +1 -1
  113. package/lib/esm/tests/tests.js +198 -151
  114. package/lib/esm/tests/tests.js.map +1 -1
  115. package/lib/tsconfig.tsbuildinfo +1 -1
  116. package/package.json +10 -10
  117. package/src/tests/api_tests/calendar_event_webhook_template.test.ts +204 -0
  118. package/src/tests/api_tests/enduser_login_rate_limits.test.ts +178 -0
  119. package/src/tests/api_tests/integrations_redacted.test.ts +8 -0
  120. package/src/tests/api_tests/push_forms_to_portal_group_completion.test.ts +113 -88
  121. package/src/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.ts +236 -0
  122. package/src/tests/api_tests/security/F-0005-ai-conversations-rbac.test.ts +154 -0
  123. package/src/tests/api_tests/security/F-0007-invite-user-enumeration.test.ts +198 -0
  124. package/src/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.ts +130 -0
  125. package/src/tests/api_tests/security/F-0013-sanitize-user-html.test.ts +109 -0
  126. package/src/tests/api_tests/security/F-0016-prototype-pollution.test.ts +50 -0
  127. package/src/tests/api_tests/security/F-0053-cascade-role-rename-cross-tenant.test.ts +161 -0
  128. package/src/tests/api_tests/security/F-0076-self-admin-role-assignment.test.ts +165 -0
  129. package/src/tests/api_tests/user_portal_settings.test.ts +217 -0
  130. package/src/tests/tests.ts +25 -2
  131. package/test_generated.pdf +0 -0
@@ -0,0 +1,345 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ var __generator = (this && this.__generator) || function (thisArg, body) {
22
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
23
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
24
+ function verb(n) { return function (v) { return step([n, v]); }; }
25
+ function step(op) {
26
+ if (f) throw new TypeError("Generator is already executing.");
27
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
28
+ 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;
29
+ if (y = 0, t) op = [op[0] & 2, t.value];
30
+ switch (op[0]) {
31
+ case 0: case 1: t = op; break;
32
+ case 4: _.label++; return { value: op[1], done: false };
33
+ case 5: _.label++; y = op[1]; op = [0]; continue;
34
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
35
+ default:
36
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
37
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
38
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
39
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
40
+ if (t[2]) _.ops.pop();
41
+ _.trys.pop(); continue;
42
+ }
43
+ op = body.call(thisArg, _);
44
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
45
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
+ }
47
+ };
48
+ require('source-map-support').install();
49
+ import { Session } from "../../../sdk";
50
+ import { async_test, log_header, wait, } from "@tellescope/testing";
51
+ import { setup_tests } from "../../setup";
52
+ import { PROVIDER_PERMISSIONS } from "@tellescope/constants";
53
+ var host = process.env.API_URL || 'http://localhost:8080';
54
+ var _a = [process.env.NON_ADMIN_EMAIL, process.env.NON_ADMIN_PASSWORD], nonAdminEmail = _a[0], nonAdminPassword = _a[1];
55
+ var FULL_ACCESS = { create: 'All', read: 'All', update: 'All', delete: 'All' };
56
+ // Schema fields tagged `redactions: ['all']` that must never appear in
57
+ // `/v1/data-sync` results. See packages/public/schema/src/schema.ts.
58
+ var REDACTABLE_FIELDS_BY_MODEL = {
59
+ users: ['hashedPass', 'hashedInviteCode'],
60
+ endusers: ['hashedPassword'],
61
+ };
62
+ var collectViolations = function (results) {
63
+ var violations = [];
64
+ for (var _i = 0, results_1 = results; _i < results_1.length; _i++) {
65
+ var record = results_1[_i];
66
+ if (!record.data || record.data === 'deleted')
67
+ continue;
68
+ var fields = REDACTABLE_FIELDS_BY_MODEL[record.modelName];
69
+ if (!fields)
70
+ continue;
71
+ var parsed = void 0;
72
+ try {
73
+ parsed = JSON.parse(record.data);
74
+ }
75
+ catch (_a) {
76
+ continue;
77
+ }
78
+ for (var _b = 0, fields_1 = fields; _b < fields_1.length; _b++) {
79
+ var f = fields_1[_b];
80
+ if (f in parsed && parsed[f] !== undefined && parsed[f] !== null && parsed[f] !== '') {
81
+ violations.push({ modelName: record.modelName, recordId: record.recordId, leakedField: f });
82
+ }
83
+ }
84
+ }
85
+ return violations;
86
+ };
87
+ /**
88
+ * Regression test for F-0001 (security-audit/findings/F-0001-data-sync-bypasses-applyRedactions.md).
89
+ *
90
+ * The /v1/data-sync handler must apply the central applyRedactions() pipeline to
91
+ * every non-deleted record. The original bug: redactions were gated behind
92
+ * `if (session.fieldRedactions && session.fieldRedactions[record.modelName])`
93
+ * which meant any session without role-based field redactions (including all
94
+ * admins) received raw records — leaking schema-level `redactions: ['all']`
95
+ * fields (hashedPass, hashedPassword, hashedInviteCode).
96
+ *
97
+ * This test:
98
+ * 1. Configures a non-admin user with broad read access on users + endusers
99
+ * and NO fieldRedactions — the realistic "regular user with read access"
100
+ * condition that triggers the bypass.
101
+ * 2. Creates an enduser with a password to populate the sync stream.
102
+ * 3. Calls /v1/data-sync as the non-admin.
103
+ * 4. Asserts no returned record contains hashedPass / hashedPassword /
104
+ * hashedInviteCode.
105
+ *
106
+ * Pre-fix: assertion fails with leaked records.
107
+ * Post-fix: assertion passes.
108
+ */
109
+ export var data_sync_redaction_bypass_tests = function (_a) {
110
+ var sdk = _a.sdk, sdkNonAdmin = _a.sdkNonAdmin;
111
+ return __awaiter(void 0, void 0, void 0, function () {
112
+ var roleName, testEnduserId, rbapId, originalRoles, rbap, testEnduser, sync_1, violations_1, userRecordsInStream_1, adminSync, adminViolations_1, otherUsersInStream_2, linkedAccountLeaks_1, _i, otherUsersInStream_1, record, parsed, _b, _c, _d, _e;
113
+ return __generator(this, function (_f) {
114
+ switch (_f.label) {
115
+ case 0:
116
+ log_header("F-0001: /v1/data-sync field-redaction bypass regression");
117
+ roleName = "f0001-data-sync-bypass-".concat(Date.now());
118
+ originalRoles = sdkNonAdmin.userInfo.roles;
119
+ _f.label = 1;
120
+ case 1:
121
+ _f.trys.push([1, , 15, 32]);
122
+ return [4 /*yield*/, sdk.api.role_based_access_permissions.createOne({
123
+ role: roleName,
124
+ permissions: __assign(__assign({}, PROVIDER_PERMISSIONS), { users: FULL_ACCESS, endusers: FULL_ACCESS }),
125
+ // intentionally NO fieldRedactions — this is the exploit condition.
126
+ })];
127
+ case 2:
128
+ rbap = _f.sent();
129
+ rbapId = rbap.id;
130
+ // 2. Assign role to the non-admin user and re-authenticate so the new
131
+ // session reflects the role's permissions.
132
+ return [4 /*yield*/, sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { roles: [roleName] }, { replaceObjectFields: true })];
133
+ case 3:
134
+ // 2. Assign role to the non-admin user and re-authenticate so the new
135
+ // session reflects the role's permissions.
136
+ _f.sent();
137
+ return [4 /*yield*/, wait(undefined, 1500)];
138
+ case 4:
139
+ _f.sent();
140
+ return [4 /*yield*/, sdkNonAdmin.authenticate(nonAdminEmail, nonAdminPassword)
141
+ // 3. Create a test enduser and set a password — this populates
142
+ // `hashedPassword` on the enduser record and writes a data_sync_records
143
+ // row for it.
144
+ ];
145
+ case 5:
146
+ _f.sent();
147
+ return [4 /*yield*/, sdk.api.endusers.createOne({
148
+ fname: 'F0001Target',
149
+ lname: 'Patient',
150
+ email: "f0001-target-".concat(Date.now(), "@example.com"),
151
+ })];
152
+ case 6:
153
+ testEnduser = _f.sent();
154
+ testEnduserId = testEnduser.id;
155
+ return [4 /*yield*/, sdk.api.endusers.set_password({ id: testEnduser.id, password: 'F0001TestPassword!123' })
156
+ // The non-admin user's own `hashedPass` is set from login and refreshed
157
+ // on every write to their user record (e.g., the role-assignment update
158
+ // above). No extra setup needed for users.hashedPass to be in the stream.
159
+ ];
160
+ case 7:
161
+ _f.sent();
162
+ // The non-admin user's own `hashedPass` is set from login and refreshed
163
+ // on every write to their user record (e.g., the role-assignment update
164
+ // above). No extra setup needed for users.hashedPass to be in the stream.
165
+ return [4 /*yield*/, wait(undefined, 500)
166
+ // 4. As the non-admin, call /v1/data-sync from epoch zero to capture all
167
+ // sync records the role can see.
168
+ ];
169
+ case 8:
170
+ // The non-admin user's own `hashedPass` is set from login and refreshed
171
+ // on every write to their user record (e.g., the role-assignment update
172
+ // above). No extra setup needed for users.hashedPass to be in the stream.
173
+ _f.sent();
174
+ return [4 /*yield*/, sdkNonAdmin.sync({ from: new Date(0) })
175
+ // 5. Walk every record. Fail if any contains a `redactions: ['all']` field.
176
+ ];
177
+ case 9:
178
+ sync_1 = _f.sent();
179
+ violations_1 = collectViolations(sync_1.results);
180
+ userRecordsInStream_1 = sync_1.results.filter(function (r) { return r.modelName === 'users'; }).length;
181
+ return [4 /*yield*/, async_test("F-0001 guard: /v1/data-sync sync stream contains at least one user record", function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
182
+ return [2 /*return*/, ({ userRecords: userRecordsInStream_1, totalRecords: sync_1.results.length })];
183
+ }); }); }, { onResult: function (r) { return r.userRecords >= 1; } })];
184
+ case 10:
185
+ _f.sent();
186
+ return [4 /*yield*/, async_test("F-0001: /v1/data-sync must NOT return hashedPass / hashedPassword / hashedInviteCode (see security-audit/findings/F-0001)", function () { return __awaiter(void 0, void 0, void 0, function () {
187
+ return __generator(this, function (_a) {
188
+ return [2 /*return*/, ({
189
+ violationCount: violations_1.length,
190
+ violations: violations_1.slice(0, 10),
191
+ affectedModels: Array.from(new Set(violations_1.map(function (v) { return v.modelName; }))),
192
+ affectedFields: Array.from(new Set(violations_1.map(function (v) { return v.leakedField; }))),
193
+ })];
194
+ });
195
+ }); }, { onResult: function (r) { return r.violationCount === 0; } })
196
+ // ========================================================================
197
+ // Additional coverage for applyRedactions code paths reachable via /v1/data-sync.
198
+ // Each of these is a distinct branch in applyRedactions (routing.ts:1165-1238)
199
+ // and could regress independently of the F-0001 fix.
200
+ // ========================================================================
201
+ // Case A: schema-level `redactions: ['all']` must apply to ADMIN sessions too.
202
+ // Admins have no session.fieldRedactions, but `redactions: ['all']` is universal.
203
+ // Pre-fix: admin saw hashedPass via data-sync because applyRedactions was skipped entirely.
204
+ // Post-fix: applyRedactions always runs and `redactions: ['all']` strips for everyone.
205
+ ];
206
+ case 11:
207
+ _f.sent();
208
+ return [4 /*yield*/, sdk.sync({ from: new Date(0) })];
209
+ case 12:
210
+ adminSync = _f.sent();
211
+ adminViolations_1 = collectViolations(adminSync.results);
212
+ return [4 /*yield*/, async_test("F-0001 coverage A: admin /v1/data-sync also strips redactions:['all'] fields (hashedPass etc.)", function () { return __awaiter(void 0, void 0, void 0, function () {
213
+ return __generator(this, function (_a) {
214
+ return [2 /*return*/, ({
215
+ violationCount: adminViolations_1.length,
216
+ violations: adminViolations_1.slice(0, 10),
217
+ })];
218
+ });
219
+ }); }, { onResult: function (r) { return r.violationCount === 0; } })
220
+ // Case B: `linkedAccountAccess` on users must be stripped when the caller is NOT
221
+ // the record's owner. This is a separate branch in applyRedactions (routing.ts:1220-1225)
222
+ // and protects against cross-user enumeration of who-requested-access-to-whom.
223
+ // The non-admin user reads other user records via data-sync; if any of those
224
+ // records have linkedAccountAccess set, it must be stripped on read.
225
+ ];
226
+ case 13:
227
+ _f.sent();
228
+ otherUsersInStream_2 = sync_1.results.filter(function (r) { return r.modelName === 'users' && r.recordId !== sdkNonAdmin.userInfo.id; });
229
+ linkedAccountLeaks_1 = [];
230
+ for (_i = 0, otherUsersInStream_1 = otherUsersInStream_2; _i < otherUsersInStream_1.length; _i++) {
231
+ record = otherUsersInStream_1[_i];
232
+ if (!record.data || record.data === 'deleted')
233
+ continue;
234
+ try {
235
+ parsed = JSON.parse(record.data);
236
+ if ('linkedAccountAccess' in parsed && parsed.linkedAccountAccess !== undefined) {
237
+ linkedAccountLeaks_1.push({ recordId: record.recordId });
238
+ }
239
+ }
240
+ catch (_g) { }
241
+ }
242
+ return [4 /*yield*/, async_test("F-0001 coverage B: /v1/data-sync strips linkedAccountAccess from other users' records", function () { return __awaiter(void 0, void 0, void 0, function () {
243
+ return __generator(this, function (_a) {
244
+ return [2 /*return*/, ({
245
+ otherUserRecords: otherUsersInStream_2.length,
246
+ leakCount: linkedAccountLeaks_1.length,
247
+ leaks: linkedAccountLeaks_1.slice(0, 5),
248
+ })];
249
+ });
250
+ }); }, { onResult: function (r) { return r.leakCount === 0; } })];
251
+ case 14:
252
+ _f.sent();
253
+ return [3 /*break*/, 32];
254
+ case 15:
255
+ _f.trys.push([15, 17, , 18]);
256
+ return [4 /*yield*/, sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { roles: originalRoles !== null && originalRoles !== void 0 ? originalRoles : [] }, { replaceObjectFields: true })];
257
+ case 16:
258
+ _f.sent();
259
+ return [3 /*break*/, 18];
260
+ case 17:
261
+ _b = _f.sent();
262
+ return [3 /*break*/, 18];
263
+ case 18:
264
+ if (!rbapId) return [3 /*break*/, 22];
265
+ _f.label = 19;
266
+ case 19:
267
+ _f.trys.push([19, 21, , 22]);
268
+ return [4 /*yield*/, sdk.api.role_based_access_permissions.deleteOne(rbapId)];
269
+ case 20:
270
+ _f.sent();
271
+ return [3 /*break*/, 22];
272
+ case 21:
273
+ _c = _f.sent();
274
+ return [3 /*break*/, 22];
275
+ case 22:
276
+ if (!testEnduserId) return [3 /*break*/, 26];
277
+ _f.label = 23;
278
+ case 23:
279
+ _f.trys.push([23, 25, , 26]);
280
+ return [4 /*yield*/, sdk.api.endusers.deleteOne(testEnduserId)];
281
+ case 24:
282
+ _f.sent();
283
+ return [3 /*break*/, 26];
284
+ case 25:
285
+ _d = _f.sent();
286
+ return [3 /*break*/, 26];
287
+ case 26:
288
+ // Re-authenticate the non-admin to drop the exploit role from their JWT
289
+ // before subsequent tests run.
290
+ // Role restore above re-triggers deauthenticate_user; wait > 1s so the freshly minted
291
+ // token's (second-floored) iat lands after the deauth timestamp and isn't permanently
292
+ // rejected by is_logged_in's iat-slack check. Matches the in-test re-auth above.
293
+ return [4 /*yield*/, wait(undefined, 1500)];
294
+ case 27:
295
+ // Re-authenticate the non-admin to drop the exploit role from their JWT
296
+ // before subsequent tests run.
297
+ // Role restore above re-triggers deauthenticate_user; wait > 1s so the freshly minted
298
+ // token's (second-floored) iat lands after the deauth timestamp and isn't permanently
299
+ // rejected by is_logged_in's iat-slack check. Matches the in-test re-auth above.
300
+ _f.sent();
301
+ _f.label = 28;
302
+ case 28:
303
+ _f.trys.push([28, 30, , 31]);
304
+ return [4 /*yield*/, sdkNonAdmin.authenticate(nonAdminEmail, nonAdminPassword)];
305
+ case 29:
306
+ _f.sent();
307
+ return [3 /*break*/, 31];
308
+ case 30:
309
+ _e = _f.sent();
310
+ return [3 /*break*/, 31];
311
+ case 31: return [7 /*endfinally*/];
312
+ case 32: return [2 /*return*/];
313
+ }
314
+ });
315
+ });
316
+ };
317
+ // Allow running this test file independently
318
+ if (require.main === module) {
319
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
320
+ var sdk_1 = new Session({ host: host });
321
+ var sdkNonAdmin_1 = new Session({ host: host });
322
+ var runTests = function () { return __awaiter(void 0, void 0, void 0, function () {
323
+ return __generator(this, function (_a) {
324
+ switch (_a.label) {
325
+ case 0: return [4 /*yield*/, setup_tests(sdk_1, sdkNonAdmin_1)];
326
+ case 1:
327
+ _a.sent();
328
+ return [4 /*yield*/, data_sync_redaction_bypass_tests({ sdk: sdk_1, sdkNonAdmin: sdkNonAdmin_1 })];
329
+ case 2:
330
+ _a.sent();
331
+ return [2 /*return*/];
332
+ }
333
+ });
334
+ }); };
335
+ runTests()
336
+ .then(function () {
337
+ console.log("✅ F-0001 data-sync redaction-bypass test suite completed successfully");
338
+ process.exit(0);
339
+ })
340
+ .catch(function (error) {
341
+ console.error("❌ F-0001 data-sync redaction-bypass test suite failed:", error);
342
+ process.exit(1);
343
+ });
344
+ }
345
+ //# sourceMappingURL=F-0001-data-sync-redaction-bypass.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"F-0001-data-sync-redaction-bypass.test.js","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { Session } from "../../../sdk";
2
+ /**
3
+ * Regression test for F-0005 (security-audit/findings/F-0005-ai-conversations-bypass-rbac.md).
4
+ *
5
+ * Both `ai_conversations.send_message` and `ai_conversations.generate_ai_decision` are
6
+ * registered with `noAccessPermissions: true` ([api.ts:22699, 22721](packages/private/api/api/v1/api.ts)).
7
+ * Their only access gate is `if (req.session.type === 'enduser') throw 403`. They must ALSO
8
+ * check `session.access?.ai_conversations?.<action>` (and `session.access?.endusers?.read`
9
+ * for generate_ai_decision) — the standard pattern used 16 lines earlier in the same file
10
+ * at api.ts:22680 for the background_errors handler.
11
+ *
12
+ * This test:
13
+ * 1. Creates a role with explicit NO_ACCESS for ai_conversations (and endusers).
14
+ * 2. Assigns the role to the non-admin user.
15
+ * 3. Calls each endpoint as the non-admin.
16
+ * 4. Asserts each endpoint returns a 403-equivalent error (not 200).
17
+ *
18
+ * Pre-fix:
19
+ * - send_message: 200 (or some downstream error from Bedrock) — NOT 403. Test fails.
20
+ * - generate_ai_decision: 200 with `{}` (handler responds early before access check). Test fails.
21
+ *
22
+ * Post-fix: both endpoints throw 403 before any work happens. Test passes.
23
+ */
24
+ export declare const ai_conversations_rbac_tests: ({ sdk, sdkNonAdmin }: {
25
+ sdk: Session;
26
+ sdkNonAdmin: Session;
27
+ }) => Promise<void>;
28
+ //# sourceMappingURL=F-0005-ai-conversations-rbac.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"F-0005-ai-conversations-rbac.test.d.ts","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/F-0005-ai-conversations-rbac.test.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAYtC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,2BAA2B;SAAwC,OAAO;iBAAe,OAAO;mBA8F5G,CAAA"}
@@ -0,0 +1,243 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ var __generator = (this && this.__generator) || function (thisArg, body) {
22
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
23
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
24
+ function verb(n) { return function (v) { return step([n, v]); }; }
25
+ function step(op) {
26
+ if (f) throw new TypeError("Generator is already executing.");
27
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
28
+ 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;
29
+ if (y = 0, t) op = [op[0] & 2, t.value];
30
+ switch (op[0]) {
31
+ case 0: case 1: t = op; break;
32
+ case 4: _.label++; return { value: op[1], done: false };
33
+ case 5: _.label++; y = op[1]; op = [0]; continue;
34
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
35
+ default:
36
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
37
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
38
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
39
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
40
+ if (t[2]) _.ops.pop();
41
+ _.trys.pop(); continue;
42
+ }
43
+ op = body.call(thisArg, _);
44
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
45
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
+ }
47
+ };
48
+ require('source-map-support').install();
49
+ import { ObjectId } from 'bson';
50
+ import { Session } from "../../../sdk";
51
+ import { async_test, log_header, wait, } from "@tellescope/testing";
52
+ import { setup_tests } from "../../setup";
53
+ import { PROVIDER_PERMISSIONS } from "@tellescope/constants";
54
+ var host = process.env.API_URL || 'http://localhost:8080';
55
+ var _a = [process.env.NON_ADMIN_EMAIL, process.env.NON_ADMIN_PASSWORD], nonAdminEmail = _a[0], nonAdminPassword = _a[1];
56
+ /**
57
+ * Regression test for F-0005 (security-audit/findings/F-0005-ai-conversations-bypass-rbac.md).
58
+ *
59
+ * Both `ai_conversations.send_message` and `ai_conversations.generate_ai_decision` are
60
+ * registered with `noAccessPermissions: true` ([api.ts:22699, 22721](packages/private/api/api/v1/api.ts)).
61
+ * Their only access gate is `if (req.session.type === 'enduser') throw 403`. They must ALSO
62
+ * check `session.access?.ai_conversations?.<action>` (and `session.access?.endusers?.read`
63
+ * for generate_ai_decision) — the standard pattern used 16 lines earlier in the same file
64
+ * at api.ts:22680 for the background_errors handler.
65
+ *
66
+ * This test:
67
+ * 1. Creates a role with explicit NO_ACCESS for ai_conversations (and endusers).
68
+ * 2. Assigns the role to the non-admin user.
69
+ * 3. Calls each endpoint as the non-admin.
70
+ * 4. Asserts each endpoint returns a 403-equivalent error (not 200).
71
+ *
72
+ * Pre-fix:
73
+ * - send_message: 200 (or some downstream error from Bedrock) — NOT 403. Test fails.
74
+ * - generate_ai_decision: 200 with `{}` (handler responds early before access check). Test fails.
75
+ *
76
+ * Post-fix: both endpoints throw 403 before any work happens. Test passes.
77
+ */
78
+ export var ai_conversations_rbac_tests = function (_a) {
79
+ var sdk = _a.sdk, sdkNonAdmin = _a.sdkNonAdmin;
80
+ return __awaiter(void 0, void 0, void 0, function () {
81
+ var roleName, rbapId, originalRoles, rbap, _b, _c, _d;
82
+ return __generator(this, function (_e) {
83
+ switch (_e.label) {
84
+ case 0:
85
+ log_header("F-0005: ai_conversations RBAC bypass regression");
86
+ roleName = "f0005-ai-conversations-no-access-".concat(Date.now());
87
+ originalRoles = sdkNonAdmin.userInfo.roles;
88
+ _e.label = 1;
89
+ case 1:
90
+ _e.trys.push([1, , 8, 21]);
91
+ return [4 /*yield*/, sdk.api.role_based_access_permissions.createOne({
92
+ role: roleName,
93
+ permissions: __assign(__assign({}, PROVIDER_PERMISSIONS), { ai_conversations: { create: null, read: null, update: null, delete: null }, endusers: { create: null, read: null, update: null, delete: null } }),
94
+ })];
95
+ case 2:
96
+ rbap = _e.sent();
97
+ rbapId = rbap.id;
98
+ // 2. Assign the role to the non-admin user and re-authenticate so the new
99
+ // session reflects the role's denied permissions.
100
+ return [4 /*yield*/, sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { roles: [roleName] }, { replaceObjectFields: true })];
101
+ case 3:
102
+ // 2. Assign the role to the non-admin user and re-authenticate so the new
103
+ // session reflects the role's denied permissions.
104
+ _e.sent();
105
+ return [4 /*yield*/, wait(undefined, 1500)];
106
+ case 4:
107
+ _e.sent();
108
+ return [4 /*yield*/, sdkNonAdmin.authenticate(nonAdminEmail, nonAdminPassword)
109
+ // 3a. send_message must throw 403 (or equivalent access error). Pre-fix: succeeds (or
110
+ // fails downstream in Bedrock without 403). Post-fix: throws before any work.
111
+ ];
112
+ case 5:
113
+ _e.sent();
114
+ // 3a. send_message must throw 403 (or equivalent access error). Pre-fix: succeeds (or
115
+ // fails downstream in Bedrock without 403). Post-fix: throws before any work.
116
+ return [4 /*yield*/, async_test("F-0005: ai_conversations.send_message must throw 403 when role denies ai_conversations", function () { return sdkNonAdmin.api.ai_conversations.send_message({
117
+ message: 'F-0005 regression test',
118
+ type: 'Test',
119
+ }); }, {
120
+ shouldError: true,
121
+ onError: function (e) {
122
+ var _a, _b;
123
+ // Accept any 4xx access-denial response — handler may use 403 (recommended)
124
+ // or 400 with "access" / "permission" in the message.
125
+ var msg = ((_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : '').toLowerCase();
126
+ var status = (_b = e === null || e === void 0 ? void 0 : e.status) !== null && _b !== void 0 ? _b : e === null || e === void 0 ? void 0 : e.code;
127
+ return status === 403 || status === 401
128
+ || msg.includes('access') || msg.includes('permission') || msg.includes('forbidden');
129
+ },
130
+ })
131
+ // 3b. generate_ai_decision must throw 403 BEFORE the early res.json({}) response.
132
+ // Pre-fix: handler responds 200 with {} immediately and processes in background.
133
+ // Post-fix: handler throws 403 before res.json({}).
134
+ ];
135
+ case 6:
136
+ // 3a. send_message must throw 403 (or equivalent access error). Pre-fix: succeeds (or
137
+ // fails downstream in Bedrock without 403). Post-fix: throws before any work.
138
+ _e.sent();
139
+ // 3b. generate_ai_decision must throw 403 BEFORE the early res.json({}) response.
140
+ // Pre-fix: handler responds 200 with {} immediately and processes in background.
141
+ // Post-fix: handler throws 403 before res.json({}).
142
+ return [4 /*yield*/, async_test("F-0005: ai_conversations.generate_ai_decision must throw 403 when role denies endusers/ai_conversations", function () { return sdkNonAdmin.api.ai_conversations.generate_ai_decision({
143
+ enduserId: new ObjectId().toHexString(),
144
+ automationStepId: new ObjectId().toHexString(),
145
+ outcomes: ['yes', 'no'],
146
+ prompt: 'F-0005 regression test',
147
+ sources: [{ type: 'SMS', limit: 1 }], // non-empty so the validator passes; access check then fires
148
+ }); }, {
149
+ shouldError: true,
150
+ onError: function (e) {
151
+ var _a, _b;
152
+ var msg = ((_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : '').toLowerCase();
153
+ var status = (_b = e === null || e === void 0 ? void 0 : e.status) !== null && _b !== void 0 ? _b : e === null || e === void 0 ? void 0 : e.code;
154
+ return status === 403 || status === 401
155
+ || msg.includes('access') || msg.includes('permission') || msg.includes('forbidden');
156
+ },
157
+ })];
158
+ case 7:
159
+ // 3b. generate_ai_decision must throw 403 BEFORE the early res.json({}) response.
160
+ // Pre-fix: handler responds 200 with {} immediately and processes in background.
161
+ // Post-fix: handler throws 403 before res.json({}).
162
+ _e.sent();
163
+ return [3 /*break*/, 21];
164
+ case 8:
165
+ _e.trys.push([8, 10, , 11]);
166
+ return [4 /*yield*/, sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { roles: originalRoles !== null && originalRoles !== void 0 ? originalRoles : [] }, { replaceObjectFields: true })];
167
+ case 9:
168
+ _e.sent();
169
+ return [3 /*break*/, 11];
170
+ case 10:
171
+ _b = _e.sent();
172
+ return [3 /*break*/, 11];
173
+ case 11:
174
+ if (!rbapId) return [3 /*break*/, 15];
175
+ _e.label = 12;
176
+ case 12:
177
+ _e.trys.push([12, 14, , 15]);
178
+ return [4 /*yield*/, sdk.api.role_based_access_permissions.deleteOne(rbapId)];
179
+ case 13:
180
+ _e.sent();
181
+ return [3 /*break*/, 15];
182
+ case 14:
183
+ _c = _e.sent();
184
+ return [3 /*break*/, 15];
185
+ case 15:
186
+ // Re-authenticate the non-admin to drop the no-access role from their JWT
187
+ // before subsequent tests run.
188
+ // Role restore above re-triggers deauthenticate_user; wait > 1s so the freshly minted
189
+ // token's (second-floored) iat lands after the deauth timestamp and isn't permanently
190
+ // rejected by is_logged_in's iat-slack check. Matches the in-test re-auth above.
191
+ return [4 /*yield*/, wait(undefined, 1500)];
192
+ case 16:
193
+ // Re-authenticate the non-admin to drop the no-access role from their JWT
194
+ // before subsequent tests run.
195
+ // Role restore above re-triggers deauthenticate_user; wait > 1s so the freshly minted
196
+ // token's (second-floored) iat lands after the deauth timestamp and isn't permanently
197
+ // rejected by is_logged_in's iat-slack check. Matches the in-test re-auth above.
198
+ _e.sent();
199
+ _e.label = 17;
200
+ case 17:
201
+ _e.trys.push([17, 19, , 20]);
202
+ return [4 /*yield*/, sdkNonAdmin.authenticate(nonAdminEmail, nonAdminPassword)];
203
+ case 18:
204
+ _e.sent();
205
+ return [3 /*break*/, 20];
206
+ case 19:
207
+ _d = _e.sent();
208
+ return [3 /*break*/, 20];
209
+ case 20: return [7 /*endfinally*/];
210
+ case 21: return [2 /*return*/];
211
+ }
212
+ });
213
+ });
214
+ };
215
+ // Allow running this test file independently
216
+ if (require.main === module) {
217
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
218
+ var sdk_1 = new Session({ host: host });
219
+ var sdkNonAdmin_1 = new Session({ host: host });
220
+ var runTests = function () { return __awaiter(void 0, void 0, void 0, function () {
221
+ return __generator(this, function (_a) {
222
+ switch (_a.label) {
223
+ case 0: return [4 /*yield*/, setup_tests(sdk_1, sdkNonAdmin_1)];
224
+ case 1:
225
+ _a.sent();
226
+ return [4 /*yield*/, ai_conversations_rbac_tests({ sdk: sdk_1, sdkNonAdmin: sdkNonAdmin_1 })];
227
+ case 2:
228
+ _a.sent();
229
+ return [2 /*return*/];
230
+ }
231
+ });
232
+ }); };
233
+ runTests()
234
+ .then(function () {
235
+ console.log("✅ F-0005 ai_conversations RBAC test suite completed successfully");
236
+ process.exit(0);
237
+ })
238
+ .catch(function (error) {
239
+ console.error("❌ F-0005 ai_conversations RBAC test suite failed:", error);
240
+ process.exit(1);
241
+ });
242
+ }
243
+ //# sourceMappingURL=F-0005-ai-conversations-rbac.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"F-0005-ai-conversations-rbac.test.js","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/F-0005-ai-conversations-rbac.test.ts"],"names":[],"mappings":"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"}