@tellescope/sdk 1.255.19 → 1.256.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/lib/cjs/tests/api_tests/custom_dashboards.test.d.ts.map +1 -1
  2. package/lib/cjs/tests/api_tests/custom_dashboards.test.js +99 -8
  3. package/lib/cjs/tests/api_tests/custom_dashboards.test.js.map +1 -1
  4. package/lib/cjs/tests/api_tests/default_summary_journey.test.d.ts.map +1 -0
  5. package/lib/cjs/tests/api_tests/default_summary_journey.test.js.map +1 -0
  6. package/lib/cjs/tests/api_tests/file_download_unicode_names.test.d.ts +6 -0
  7. package/lib/cjs/tests/api_tests/file_download_unicode_names.test.d.ts.map +1 -0
  8. package/lib/cjs/tests/api_tests/file_download_unicode_names.test.js +278 -0
  9. package/lib/cjs/tests/api_tests/file_download_unicode_names.test.js.map +1 -0
  10. package/lib/cjs/tests/api_tests/openloop_webhooks.test.d.ts.map +1 -1
  11. package/lib/cjs/tests/api_tests/openloop_webhooks.test.js +212 -33
  12. package/lib/cjs/tests/api_tests/openloop_webhooks.test.js.map +1 -1
  13. package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.d.ts.map +1 -1
  14. package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.js +85 -9
  15. package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.js.map +1 -1
  16. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts +2 -0
  17. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts.map +1 -0
  18. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js +242 -0
  19. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js.map +1 -0
  20. package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts +29 -0
  21. package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts.map +1 -0
  22. package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js +443 -0
  23. package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js.map +1 -0
  24. package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts +35 -0
  25. package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts.map +1 -0
  26. package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js +231 -0
  27. package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js.map +1 -0
  28. package/lib/cjs/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.d.ts.map +1 -1
  29. package/lib/cjs/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js +524 -294
  30. package/lib/cjs/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js.map +1 -1
  31. package/lib/cjs/tests/tests.d.ts.map +1 -1
  32. package/lib/cjs/tests/tests.js +431 -230
  33. package/lib/cjs/tests/tests.js.map +1 -1
  34. package/lib/esm/enduser.d.ts +0 -1
  35. package/lib/esm/enduser.d.ts.map +1 -1
  36. package/lib/esm/sdk.d.ts +2 -3
  37. package/lib/esm/sdk.d.ts.map +1 -1
  38. package/lib/esm/session.d.ts +0 -1
  39. package/lib/esm/session.d.ts.map +1 -1
  40. package/lib/esm/tests/api_tests/custom_dashboards.test.d.ts.map +1 -1
  41. package/lib/esm/tests/api_tests/custom_dashboards.test.js +99 -8
  42. package/lib/esm/tests/api_tests/custom_dashboards.test.js.map +1 -1
  43. package/lib/esm/tests/api_tests/default_summary_journey.test.d.ts.map +1 -0
  44. package/lib/esm/tests/api_tests/default_summary_journey.test.js.map +1 -0
  45. package/lib/esm/tests/api_tests/file_download_unicode_names.test.d.ts +6 -0
  46. package/lib/esm/tests/api_tests/file_download_unicode_names.test.d.ts.map +1 -0
  47. package/lib/esm/tests/api_tests/file_download_unicode_names.test.js +248 -0
  48. package/lib/esm/tests/api_tests/file_download_unicode_names.test.js.map +1 -0
  49. package/lib/esm/tests/api_tests/openloop_webhooks.test.d.ts.map +1 -1
  50. package/lib/esm/tests/api_tests/openloop_webhooks.test.js +213 -34
  51. package/lib/esm/tests/api_tests/openloop_webhooks.test.js.map +1 -1
  52. package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.d.ts.map +1 -1
  53. package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.js +85 -9
  54. package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.js.map +1 -1
  55. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts +2 -0
  56. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts.map +1 -0
  57. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js +238 -0
  58. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js.map +1 -0
  59. package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts +29 -0
  60. package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts.map +1 -0
  61. package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js +436 -0
  62. package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js.map +1 -0
  63. package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts +35 -0
  64. package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts.map +1 -0
  65. package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js +224 -0
  66. package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js.map +1 -0
  67. package/lib/esm/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.d.ts.map +1 -1
  68. package/lib/esm/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js +524 -294
  69. package/lib/esm/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js.map +1 -1
  70. package/lib/esm/tests/tests.d.ts.map +1 -1
  71. package/lib/esm/tests/tests.js +431 -230
  72. package/lib/esm/tests/tests.js.map +1 -1
  73. package/lib/tsconfig.tsbuildinfo +1 -1
  74. package/package.json +10 -10
  75. package/src/tests/api_tests/custom_dashboards.test.ts +92 -0
  76. package/src/tests/api_tests/file_download_unicode_names.test.ts +223 -0
  77. package/src/tests/api_tests/openloop_webhooks.test.ts +138 -0
  78. package/src/tests/api_tests/phone_calls_cold_transfer.test.ts +54 -0
  79. package/src/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.ts +215 -0
  80. package/src/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.ts +215 -0
  81. package/src/tests/tests.ts +212 -1
  82. package/test_generated.pdf +0 -0
@@ -96,10 +96,11 @@ var OBJECT_ID = '68a1b2c3d4e5f60718293a4b';
96
96
  export var allowed_paths_confinement_tests = function (_a) {
97
97
  var sdk = _a.sdk;
98
98
  return __awaiter(void 0, void 0, void 0, function () {
99
- var probes, request, isPathDenial, expectDenied, expectAllowed, formId, productId, joinEnduserId, formEnduserEmail, hostUserId, eventIds, businessId, form, publicFormSession, product, joinEnduser, joinLinkToken, joinEvent, unrelatedEvent, joinSession, hostUser, startLinkToken, startEvent, startSession, baselineEnduserToken, baselineUserToken, S1, S2, refreshedJoin, refreshedJoinToken, S3, refreshedStart, refreshedStartToken, S4, prefixGranted, _i, prefixGranted_1, _b, method, path, r, S6, ABSENT_ID_A, ABSENT_ID_B, INVALID_EMAIL, consentAttempt, refreshedForScopes, rescopedToken, S5, _c, _d, path, _e, _f, path, adminAuthenticated, apiKeySDK, _g, _h, path, r, failures, scenarios, _loop_1, _j, scenarios_1, scenario, _k, probes_1, p, _l, eventIds_1, id, _m, _o, e, _p, _q, _r, _s;
100
- var _t, _u, _v, _w;
101
- return __generator(this, function (_x) {
102
- switch (_x.label) {
99
+ var probes, request, isPathDenial, expectDenied, expectAllowed, formId, productId, joinEnduserId, formEnduserEmail, hostUserId, templateId, bookingEnduserId, eventIds, businessId, form, publicFormSession, product, joinEnduser, joinLinkToken, joinEvent, unrelatedEvent, joinSession, hostUser, startLinkToken, startEvent, startSession, baselineEnduserToken, baselineUserToken, S1, formEnduserId, publicRefresh, refreshedEnduser_1, withheld, consentEcho, echoedKeys, leakedKeys, fieldsThatMustBeRefused, _i, fieldsThatMustBeRefused_1, _b, field, value, r, mixed, afterProbes, S2, attendanceWrite, echoedEvent, refreshedJoin, refreshedJoinToken, S3, refreshedStart, refreshedStartToken, S4, prefixGranted, _c, prefixGranted_1, _d, method, path, r, S6, ABSENT_ID_A, ABSENT_ID_B, INVALID_EMAIL, consentAttempt, refreshedForScopes, rescopedToken, S7, bookingTemplate, bookingEnduserEmail, bookingEnduser, bookingSession, bookingEcho, bookingLeaked, _e, _f, _g, field, value, r, S5, _h, _j, path, _k, _l, path, adminAuthenticated, apiKeySDK, _m, _o, path, r, failures, scenarios, _loop_1, _p, scenarios_1, scenario, _q, probes_1, p, _r, eventIds_1, id, _s, _t, e, _u, _v, _w, _x, _y, _z;
100
+ var _0, _1;
101
+ var _2, _3, _4, _5, _6, _7, _8, _9, _10;
102
+ return __generator(this, function (_11) {
103
+ switch (_11.label) {
103
104
  case 0:
104
105
  log_header("F-0166: allowedPaths wildcard bypasses session path confinement");
105
106
  probes = [];
@@ -166,16 +167,16 @@ export var allowed_paths_confinement_tests = function (_a) {
166
167
  });
167
168
  };
168
169
  eventIds = [];
169
- _x.label = 1;
170
+ _11.label = 1;
170
171
  case 1:
171
- _x.trys.push([1, , 101, 130]);
172
+ _11.trys.push([1, , 123, 160]);
172
173
  businessId = sdk.userInfo.businessId;
173
174
  return [4 /*yield*/, sdk.api.forms.createOne({
174
175
  title: "F-0166 path confinement ".concat(RAND()),
175
176
  allowPublicURL: true,
176
177
  })];
177
178
  case 2:
178
- form = _x.sent();
179
+ form = _11.sent();
179
180
  formId = form.id;
180
181
  return [4 /*yield*/, sdk.api.form_fields.createOne({
181
182
  formId: form.id,
@@ -184,7 +185,7 @@ export var allowed_paths_confinement_tests = function (_a) {
184
185
  previousFields: [{ type: 'root', info: {} }],
185
186
  })];
186
187
  case 3:
187
- _x.sent();
188
+ _11.sent();
188
189
  formEnduserEmail = "f0166-form-".concat(RAND(), "@tellescope.example");
189
190
  return [4 /*yield*/, new EnduserSession({ host: host, businessId: businessId })
190
191
  .api.form_responses.session_for_public_form({
@@ -196,20 +197,20 @@ export var allowed_paths_confinement_tests = function (_a) {
196
197
  skipMatch: true, // also skips the intake-phone requirement
197
198
  })];
198
199
  case 4:
199
- publicFormSession = _x.sent();
200
+ publicFormSession = _11.sent();
200
201
  return [4 /*yield*/, sdk.api.products.createOne({
201
202
  title: "F-0166 product ".concat(RAND()),
202
203
  cost: { amount: 500, currency: 'USD' },
203
204
  processor: 'Stripe',
204
205
  })];
205
206
  case 5:
206
- product = _x.sent();
207
+ product = _11.sent();
207
208
  productId = product.id;
208
209
  return [4 /*yield*/, sdk.api.endusers.createOne({
209
210
  email: "f0166-join-".concat(RAND(), "@tellescope.example"),
210
211
  })];
211
212
  case 6:
212
- joinEnduser = _x.sent();
213
+ joinEnduser = _11.sent();
213
214
  joinEnduserId = joinEnduser.id;
214
215
  joinLinkToken = "f0166-join-".concat(RAND()).concat(RAND());
215
216
  return [4 /*yield*/, sdk.api.calendar_events.createOne({
@@ -217,14 +218,14 @@ export var allowed_paths_confinement_tests = function (_a) {
217
218
  attendees: [{ type: 'enduser', id: joinEnduser.id, joinLinkToken: joinLinkToken }],
218
219
  })];
219
220
  case 7:
220
- joinEvent = _x.sent();
221
+ joinEvent = _11.sent();
221
222
  eventIds.push(joinEvent.id);
222
223
  return [4 /*yield*/, sdk.api.calendar_events.createOne({
223
224
  title: 'F-0166 unrelated', durationInMinutes: 30, startTimeInMS: Date.now() + 3600000,
224
225
  attendees: [{ type: 'enduser', id: joinEnduser.id }],
225
226
  })];
226
227
  case 8:
227
- unrelatedEvent = _x.sent();
228
+ unrelatedEvent = _11.sent();
228
229
  eventIds.push(unrelatedEvent.id);
229
230
  return [4 /*yield*/, new EnduserSession({ host: host, businessId: businessId })
230
231
  .api.calendar_events.session_for_join_link({ token: joinLinkToken })
@@ -232,7 +233,7 @@ export var allowed_paths_confinement_tests = function (_a) {
232
233
  // throwaway user; verifiedEmail must be set at create time (it is rejected on update)
233
234
  ];
234
235
  case 9:
235
- joinSession = _x.sent();
236
+ joinSession = _11.sent();
236
237
  return [4 /*yield*/, sdk.api.users.createOne({
237
238
  email: "f0166-host-".concat(RAND(), "@tellescope.example"),
238
239
  fname: 'Path', lname: 'Host',
@@ -240,7 +241,7 @@ export var allowed_paths_confinement_tests = function (_a) {
240
241
  notificationEmailsDisabled: true,
241
242
  })];
242
243
  case 10:
243
- hostUser = _x.sent();
244
+ hostUser = _11.sent();
244
245
  hostUserId = hostUser.id;
245
246
  startLinkToken = "f0166-start-".concat(RAND()).concat(RAND());
246
247
  return [4 /*yield*/, sdk.api.calendar_events.createOne({
@@ -249,204 +250,333 @@ export var allowed_paths_confinement_tests = function (_a) {
249
250
  startLinkToken: startLinkToken,
250
251
  })];
251
252
  case 11:
252
- startEvent = _x.sent();
253
+ startEvent = _11.sent();
253
254
  eventIds.push(startEvent.id);
254
255
  return [4 /*yield*/, sdk.api.calendar_events.session_for_start_link({ token: startLinkToken })
255
256
  // -- unconfined baselines of each session type (generate_auth_token, never .authenticate)
256
257
  ];
257
258
  case 12:
258
- startSession = _x.sent();
259
+ startSession = _11.sent();
259
260
  return [4 /*yield*/, sdk.api.endusers.generate_auth_token({ id: joinEnduser.id })];
260
261
  case 13:
261
- baselineEnduserToken = (_x.sent()).authToken;
262
+ baselineEnduserToken = (_11.sent()).authToken;
262
263
  return [4 /*yield*/, sdk.api.users.generate_auth_token({ id: hostUser.id })
263
264
  /* ============ S1: public-form session — no wildcard, correctly confined already ============ */
264
265
  ];
265
266
  case 14:
266
- baselineUserToken = (_x.sent()).authToken;
267
+ baselineUserToken = (_11.sent()).authToken;
267
268
  S1 = 'S1 public-form';
268
269
  return [4 /*yield*/, expectAllowed(S1, 'always-allowed path', publicFormSession.authToken, 'get', '/test-authenticated')];
269
270
  case 15:
270
- _x.sent();
271
+ _11.sent();
271
272
  return [4 /*yield*/, expectAllowed(S1, 'listed: form info for access code', publicFormSession.authToken, 'get', '/form-info-for-access-code', { accessCode: publicFormSession.accessCode })];
272
273
  case 16:
273
- _x.sent();
274
+ _11.sent();
274
275
  return [4 /*yield*/, expectAllowed(S1, 'listed: refresh enduser session', publicFormSession.authToken, 'post', '/refresh-enduser-session')];
275
276
  case 17:
276
- _x.sent();
277
+ _11.sent();
277
278
  return [4 /*yield*/, expectAllowed(S1, 'listed: product by id', publicFormSession.authToken, 'get', "/product/".concat(productId))];
278
279
  case 18:
279
- _x.sent();
280
+ _11.sent();
280
281
  return [4 /*yield*/, expectDenied(S1, 'unlisted: calendar events', publicFormSession.authToken, baselineEnduserToken, 'get', '/calendar-events')];
281
282
  case 19:
282
- _x.sent();
283
+ _11.sent();
283
284
  return [4 /*yield*/, expectDenied(S1, 'unlisted: form responses', publicFormSession.authToken, baselineEnduserToken, 'get', '/form-responses')];
284
285
  case 20:
285
- _x.sent();
286
+ _11.sent();
286
287
  return [4 /*yield*/, expectDenied(S1, 'unlisted: files', publicFormSession.authToken, baselineEnduserToken, 'get', '/files')
287
288
  // 'get:/product' names one route, so it no longer leaks the whole /products namespace the way a
288
289
  // bare '/product' prefix entry did — that used to reach /products/prepare-stripe-checkout too
289
290
  ];
290
291
  case 21:
291
- _x.sent();
292
+ _11.sent();
292
293
  // 'get:/product' names one route, so it no longer leaks the whole /products namespace the way a
293
294
  // bare '/product' prefix entry did — that used to reach /products/prepare-stripe-checkout too
294
295
  return [4 /*yield*/, expectDenied(S1, 'exact matching: /products not granted by get:/product', publicFormSession.authToken, baselineEnduserToken, 'get', '/products')
295
- /* ============ S2: join-link session RED before the fix ============ */
296
+ /* ---- field confinement on the granted generic update route ----
297
+ * 'patch:/enduser/:id' is granted for Portal V2's terms gate, so the route check alone no longer
298
+ * protects this session — SESSION_SCOPE_WRITABLE_FIELDS does. These probes are the live control:
299
+ * the consent write lands, and every contactable/identity field is refused. Without them the
300
+ * module test is the only thing standing between a scope-table edit and account takeover.
301
+ */
296
302
  ];
297
303
  case 22:
298
304
  // 'get:/product' names one route, so it no longer leaks the whole /products namespace the way a
299
305
  // bare '/product' prefix entry did — that used to reach /products/prepare-stripe-checkout too
300
- _x.sent();
306
+ _11.sent();
307
+ formEnduserId = publicFormSession.enduserId;
308
+ return [4 /*yield*/, request(publicFormSession.authToken, 'post', '/refresh-enduser-session', {})];
309
+ case 23:
310
+ publicRefresh = _11.sent();
311
+ refreshedEnduser_1 = (_3 = (_2 = publicRefresh.body) === null || _2 === void 0 ? void 0 : _2.enduser) !== null && _3 !== void 0 ? _3 : {};
312
+ probes.push({
313
+ scenario: S1,
314
+ label: 'public refresh exposes the session\'s own id',
315
+ path: '/refresh-enduser-session',
316
+ expected: 'allowed',
317
+ status: refreshedEnduser_1.id === formEnduserId ? 200 : 500,
318
+ pass: refreshedEnduser_1.id === formEnduserId,
319
+ note: "id=".concat(refreshedEnduser_1.id),
320
+ });
321
+ withheld = ['email', 'phone', 'dateOfBirth', 'fname', 'lname', 'note', 'insurance', 'tags']
322
+ .filter(function (f) { return refreshedEnduser_1[f] !== undefined; });
323
+ probes.push({
324
+ scenario: S1,
325
+ label: 'public refresh still withholds the patient record',
326
+ path: '/refresh-enduser-session',
327
+ expected: 'denied',
328
+ status: withheld.length ? 200 : 403,
329
+ pass: withheld.length === 0,
330
+ note: withheld.length ? "LEAKED [".concat(withheld, "]") : "keys=[".concat(Object.keys(refreshedEnduser_1), "]"),
331
+ });
332
+ return [4 /*yield*/, expectAllowed(S1, 'field-restricted: may record terms consent', publicFormSession.authToken, 'patch', "/enduser/".concat(formEnduserId), { updates: { termsSigned: new Date().toISOString(), termsVersion: '1' } })
333
+ // The write being confined is not enough: the update RESPONSE is not redacted for enduser sessions
334
+ // (applyRedactions runs only for user sessions on that path), so an unprojected echo would hand
335
+ // this session the record it can't otherwise read — GET /enduser/:id is not even in this scope.
336
+ // Assert the response carries ONLY what the session was allowed to write.
337
+ ];
338
+ case 24:
339
+ _11.sent();
340
+ return [4 /*yield*/, request(publicFormSession.authToken, 'patch', "/enduser/".concat(formEnduserId), { updates: { termsVersion: '2' } })];
341
+ case 25:
342
+ consentEcho = _11.sent();
343
+ echoedKeys = Object.keys((_4 = consentEcho.body) !== null && _4 !== void 0 ? _4 : {});
344
+ leakedKeys = echoedKeys.filter(function (k) { return !['id', 'termsSigned', 'termsVersion'].includes(k); });
345
+ probes.push({
346
+ scenario: S1,
347
+ label: 'field-restricted: update response echoes only the writable fields',
348
+ path: "/enduser/".concat(formEnduserId),
349
+ expected: 'allowed',
350
+ status: leakedKeys.length === 0 ? consentEcho.status : 500,
351
+ pass: consentEcho.status === 200 && leakedKeys.length === 0,
352
+ note: leakedKeys.length ? "LEAKED [".concat(leakedKeys, "]") : "echoed [".concat(echoedKeys, "]"),
353
+ });
354
+ fieldsThatMustBeRefused = [
355
+ ['email', "f0166-takeover-".concat(RAND(), "@tellescope.example")],
356
+ ['phone', '+15555550123'],
357
+ ['landline', '+15555550124'],
358
+ ['dateOfBirth', '1990-01-01'],
359
+ ['fname', 'F0166Overwritten'],
360
+ ];
361
+ _i = 0, fieldsThatMustBeRefused_1 = fieldsThatMustBeRefused;
362
+ _11.label = 26;
363
+ case 26:
364
+ if (!(_i < fieldsThatMustBeRefused_1.length)) return [3 /*break*/, 29];
365
+ _b = fieldsThatMustBeRefused_1[_i], field = _b[0], value = _b[1];
366
+ return [4 /*yield*/, request(publicFormSession.authToken, 'patch', "/enduser/".concat(formEnduserId), { updates: (_0 = {}, _0[field] = value, _0) })];
367
+ case 27:
368
+ r = _11.sent();
369
+ probes.push({
370
+ scenario: S1,
371
+ label: "field-restricted: cannot write enduser '".concat(field, "'"),
372
+ path: "/enduser/".concat(formEnduserId),
373
+ expected: 'denied',
374
+ status: r.status,
375
+ pass: r.status === 403,
376
+ note: 'route is granted; the field allowlist is what refuses this',
377
+ });
378
+ _11.label = 28;
379
+ case 28:
380
+ _i++;
381
+ return [3 /*break*/, 26];
382
+ case 29: return [4 /*yield*/, request(publicFormSession.authToken, 'patch', "/enduser/".concat(formEnduserId), { updates: { termsVersion: '2', email: "f0166-mixed-".concat(RAND(), "@tellescope.example") } })];
383
+ case 30:
384
+ mixed = _11.sent();
385
+ probes.push({
386
+ scenario: S1,
387
+ label: 'field-restricted: a mixed allowed+denied update is refused whole',
388
+ path: "/enduser/".concat(formEnduserId),
389
+ expected: 'denied',
390
+ status: mixed.status,
391
+ pass: mixed.status === 403,
392
+ note: 'no partial application — email must not ride along with termsVersion',
393
+ });
394
+ return [4 /*yield*/, sdk.api.endusers.getOne(formEnduserId)];
395
+ case 31:
396
+ afterProbes = _11.sent();
397
+ probes.push({
398
+ scenario: S1,
399
+ label: 'field-restricted: stored email survived every attempt',
400
+ path: "/enduser/".concat(formEnduserId),
401
+ expected: 'denied',
402
+ status: afterProbes.email === formEnduserEmail ? 403 : 200,
403
+ pass: afterProbes.email === formEnduserEmail,
404
+ note: "stored=".concat(afterProbes.email),
405
+ });
301
406
  S2 = 'S2 join-link (enduser)';
302
407
  return [4 /*yield*/, expectDenied(S2, 'unlisted: form responses', joinSession.authToken, baselineEnduserToken, 'get', '/form-responses')];
303
- case 23:
304
- _x.sent();
408
+ case 32:
409
+ _11.sent();
305
410
  return [4 /*yield*/, expectDenied(S2, 'unlisted: files', joinSession.authToken, baselineEnduserToken, 'get', '/files')];
306
- case 24:
307
- _x.sent();
411
+ case 33:
412
+ _11.sent();
308
413
  return [4 /*yield*/, expectDenied(S2, 'unlisted: portal customizations', joinSession.authToken, baselineEnduserToken, 'get', '/portal-customizations')];
309
- case 25:
310
- _x.sent();
414
+ case 34:
415
+ _11.sent();
311
416
  return [4 /*yield*/, expectDenied(S2, 'unlisted: UNRELATED calendar event', joinSession.authToken, baselineEnduserToken, 'get', "/calendar-event/".concat(unrelatedEvent.id))];
312
- case 26:
313
- _x.sent();
417
+ case 35:
418
+ _11.sent();
314
419
  return [4 /*yield*/, expectAllowed(S2, 'always-allowed path', joinSession.authToken, 'get', '/test-authenticated')];
315
- case 27:
316
- _x.sent();
420
+ case 36:
421
+ _11.sent();
317
422
  return [4 /*yield*/, expectAllowed(S2, 'listed: my meetings', joinSession.authToken, 'get', '/my-meetings')
318
423
  // enduserActions for organizations is {}, so an enduser 401s here regardless of confinement
319
424
  ];
320
- case 28:
321
- _x.sent();
425
+ case 37:
426
+ _11.sent();
322
427
  // enduserActions for organizations is {}, so an enduser 401s here regardless of confinement
323
428
  return [4 /*yield*/, expectAllowed(S2, 'listed: organizations', joinSession.authToken, 'get', '/organizations', undefined, { allow401: true })];
324
- case 29:
429
+ case 38:
325
430
  // enduserActions for organizations is {}, so an enduser 401s here regardless of confinement
326
- _x.sent();
431
+ _11.sent();
327
432
  return [4 /*yield*/, expectAllowed(S2, 'listed: users', joinSession.authToken, 'get', '/users')];
328
- case 30:
329
- _x.sent();
433
+ case 39:
434
+ _11.sent();
330
435
  return [4 /*yield*/, expectAllowed(S2, 'listed: pinned calendar event', joinSession.authToken, 'get', "/calendar-event/".concat(joinEvent.id))];
331
- case 31:
332
- _x.sent();
436
+ case 40:
437
+ _11.sent();
333
438
  return [4 /*yield*/, expectAllowed(S2, 'wildcard: meeting by id', joinSession.authToken, 'get', "/meeting/".concat(OBJECT_ID))
334
439
  // the two paths widened in this change so the portal video flow keeps working
335
440
  ];
336
- case 32:
337
- _x.sent();
441
+ case 41:
442
+ _11.sent();
338
443
  // the two paths widened in this change so the portal video flow keeps working
339
444
  return [4 /*yield*/, expectAllowed(S2, 'widened: calendar events list', joinSession.authToken, 'get', '/calendar-events')];
340
- case 33:
445
+ case 42:
341
446
  // the two paths widened in this change so the portal video flow keeps working
342
- _x.sent();
447
+ _11.sent();
343
448
  return [4 /*yield*/, expectAllowed(S2, 'widened: join meeting for event', joinSession.authToken, 'post', '/join-meeting-for-event', { calendarEventId: joinEvent.id })
344
- // confinement must survive a refresh rather than widening
449
+ // The WaitingRoom marks attendance here. This write is deliberately NOT field-restricted: the
450
+ // portal reads the event back through useCalendarEvents, whose updateElement REPLACES the local
451
+ // record with the update response (react state.tsx), so a truncated response would drop event
452
+ // fields out of local state mid-visit and break a live Twilio call. Assert both halves of that
453
+ // contract — the write lands, AND the response is still a whole event.
345
454
  ];
346
- case 34:
347
- _x.sent();
455
+ case 43:
456
+ _11.sent();
457
+ return [4 /*yield*/, request(joinSession.authToken, 'patch', "/calendar-event/".concat(joinEvent.id), { updates: { joinedVideoCall: [{ id: joinEnduserId, at: new Date().toISOString() }] } })];
458
+ case 44:
459
+ attendanceWrite = _11.sent();
460
+ probes.push({
461
+ scenario: S2,
462
+ label: 'marks joinedVideoCall on its pinned event',
463
+ path: "/calendar-event/".concat(joinEvent.id),
464
+ expected: 'allowed',
465
+ status: attendanceWrite.status,
466
+ pass: attendanceWrite.status === 200,
467
+ });
468
+ echoedEvent = (_5 = attendanceWrite.body) !== null && _5 !== void 0 ? _5 : {};
469
+ probes.push({
470
+ scenario: S2,
471
+ label: 'attendance write returns the WHOLE event (client replaces local state from it)',
472
+ path: "/calendar-event/".concat(joinEvent.id),
473
+ expected: 'allowed',
474
+ status: (echoedEvent.title && echoedEvent.durationInMinutes) ? 200 : 500,
475
+ pass: !!echoedEvent.title && !!echoedEvent.durationInMinutes && !!echoedEvent.joinedVideoCall,
476
+ note: "echoed ".concat(Object.keys(echoedEvent).length, " keys"),
477
+ });
348
478
  return [4 /*yield*/, request(joinSession.authToken, 'post', '/refresh-enduser-session', {})];
349
- case 35:
350
- refreshedJoin = _x.sent();
351
- refreshedJoinToken = (_t = refreshedJoin.body) === null || _t === void 0 ? void 0 : _t.authToken;
479
+ case 45:
480
+ refreshedJoin = _11.sent();
481
+ refreshedJoinToken = (_6 = refreshedJoin.body) === null || _6 === void 0 ? void 0 : _6.authToken;
352
482
  probes.push({
353
483
  scenario: S2, label: 'refresh returned a new token', path: '/refresh-enduser-session',
354
484
  expected: 'allowed', status: refreshedJoin.status, pass: !!refreshedJoinToken,
355
485
  });
356
- if (!refreshedJoinToken) return [3 /*break*/, 38];
486
+ if (!refreshedJoinToken) return [3 /*break*/, 48];
357
487
  return [4 /*yield*/, expectDenied(S2, 'after refresh: still denied form responses', refreshedJoinToken, baselineEnduserToken, 'get', '/form-responses')];
358
- case 36:
359
- _x.sent();
488
+ case 46:
489
+ _11.sent();
360
490
  return [4 /*yield*/, expectAllowed(S2, 'after refresh: still allowed my meetings', refreshedJoinToken, 'get', '/my-meetings')];
361
- case 37:
362
- _x.sent();
363
- _x.label = 38;
364
- case 38:
491
+ case 47:
492
+ _11.sent();
493
+ _11.label = 48;
494
+ case 48:
365
495
  S3 = 'S3 start-link (STAFF)';
366
496
  return [4 /*yield*/, expectDenied(S3, 'unlisted: form responses', startSession.authToken, baselineUserToken, 'get', '/form-responses')];
367
- case 39:
368
- _x.sent();
497
+ case 49:
498
+ _11.sent();
369
499
  return [4 /*yield*/, expectDenied(S3, 'unlisted: journeys', startSession.authToken, baselineUserToken, 'get', '/journeys')];
370
- case 40:
371
- _x.sent();
500
+ case 50:
501
+ _11.sent();
372
502
  return [4 /*yield*/, expectDenied(S3, 'unlisted: tickets', startSession.authToken, baselineUserToken, 'get', '/tickets')];
373
- case 41:
374
- _x.sent();
503
+ case 51:
504
+ _11.sent();
375
505
  return [4 /*yield*/, expectDenied(S3, 'unlisted: data sync (org-wide change firehose)', startSession.authToken, baselineUserToken, 'get', '/data-sync', { from: new Date(Date.now() - 60000).toISOString() })];
376
- case 42:
377
- _x.sent();
506
+ case 52:
507
+ _11.sent();
378
508
  return [4 /*yield*/, expectDenied(S3, 'unlisted: file download URL', startSession.authToken, baselineUserToken, 'get', '/file-download-URL', { secureName: 'f0166-does-not-exist' })];
379
- case 43:
380
- _x.sent();
509
+ case 53:
510
+ _11.sent();
381
511
  return [4 /*yield*/, expectDenied(S3, 'unlisted: UNRELATED calendar event', startSession.authToken, baselineUserToken, 'get', "/calendar-event/".concat(unrelatedEvent.id))];
382
- case 44:
383
- _x.sent();
512
+ case 54:
513
+ _11.sent();
384
514
  return [4 /*yield*/, expectAllowed(S3, 'always-allowed path', startSession.authToken, 'get', '/test-authenticated')];
385
- case 45:
386
- _x.sent();
515
+ case 55:
516
+ _11.sent();
387
517
  return [4 /*yield*/, expectAllowed(S3, 'listed: my meetings', startSession.authToken, 'get', '/my-meetings')];
388
- case 46:
389
- _x.sent();
518
+ case 56:
519
+ _11.sent();
390
520
  return [4 /*yield*/, expectAllowed(S3, 'listed: calendar events', startSession.authToken, 'get', '/calendar-events')];
391
- case 47:
392
- _x.sent();
521
+ case 57:
522
+ _11.sent();
393
523
  return [4 /*yield*/, expectAllowed(S3, 'listed: organizations', startSession.authToken, 'get', '/organizations')];
394
- case 48:
395
- _x.sent();
524
+ case 58:
525
+ _11.sent();
396
526
  return [4 /*yield*/, expectAllowed(S3, 'listed: users', startSession.authToken, 'get', '/users')];
397
- case 49:
398
- _x.sent();
527
+ case 59:
528
+ _11.sent();
399
529
  return [4 /*yield*/, expectAllowed(S3, 'listed: endusers', startSession.authToken, 'get', '/endusers')];
400
- case 50:
401
- _x.sent();
530
+ case 60:
531
+ _11.sent();
402
532
  return [4 /*yield*/, expectAllowed(S3, 'listed: pinned calendar event', startSession.authToken, 'get', "/calendar-event/".concat(startEvent.id))];
403
- case 51:
404
- _x.sent();
533
+ case 61:
534
+ _11.sent();
405
535
  return [4 /*yield*/, expectAllowed(S3, 'wildcard: meeting by id', startSession.authToken, 'get', "/meeting/".concat(OBJECT_ID))];
406
- case 52:
407
- _x.sent();
536
+ case 62:
537
+ _11.sent();
408
538
  return [4 /*yield*/, expectAllowed(S3, 'prefix descendant: users bulk-read', startSession.authToken, 'post', '/users/bulk-read', { ids: [hostUser.id] })];
409
- case 53:
410
- _x.sent();
539
+ case 63:
540
+ _11.sent();
411
541
  return [4 /*yield*/, expectAllowed(S3, 'prefix descendant: endusers bulk-read', startSession.authToken, 'post', '/endusers/bulk-read', { ids: [joinEnduser.id] })];
412
- case 54:
413
- _x.sent();
542
+ case 64:
543
+ _11.sent();
414
544
  return [4 /*yield*/, expectAllowed(S3, 'widened: join meeting for event', startSession.authToken, 'post', '/join-meeting-for-event', { calendarEventId: startEvent.id })];
415
- case 55:
416
- _x.sent();
545
+ case 65:
546
+ _11.sent();
417
547
  return [4 /*yield*/, expectAllowed(S3, 'widened: add attendees to meeting', startSession.authToken, 'post', '/add-attendees-to-meeting', { id: OBJECT_ID })
418
548
  // login_user propagates allowedPaths implicitly through its ...toSign rest spread — the
419
549
  // "(ASSUMPTION)" comments in the mint sites depend on this, so assert it explicitly
420
550
  ];
421
- case 56:
422
- _x.sent();
551
+ case 66:
552
+ _11.sent();
423
553
  return [4 /*yield*/, request(startSession.authToken, 'post', '/refresh-session', {})];
424
- case 57:
425
- refreshedStart = _x.sent();
426
- refreshedStartToken = (_u = refreshedStart.body) === null || _u === void 0 ? void 0 : _u.authToken;
554
+ case 67:
555
+ refreshedStart = _11.sent();
556
+ refreshedStartToken = (_7 = refreshedStart.body) === null || _7 === void 0 ? void 0 : _7.authToken;
427
557
  probes.push({
428
558
  scenario: S3, label: 'refresh returned a new token', path: '/refresh-session',
429
559
  expected: 'allowed', status: refreshedStart.status, pass: !!refreshedStartToken,
430
560
  });
431
- if (!refreshedStartToken) return [3 /*break*/, 60];
561
+ if (!refreshedStartToken) return [3 /*break*/, 70];
432
562
  return [4 /*yield*/, expectDenied(S3, 'after refresh: still denied journeys', refreshedStartToken, baselineUserToken, 'get', '/journeys')];
433
- case 58:
434
- _x.sent();
563
+ case 68:
564
+ _11.sent();
435
565
  return [4 /*yield*/, expectAllowed(S3, 'after refresh: still allowed my meetings', refreshedStartToken, 'get', '/my-meetings')];
436
- case 59:
437
- _x.sent();
438
- _x.label = 60;
439
- case 60:
566
+ case 69:
567
+ _11.sent();
568
+ _11.label = 70;
569
+ case 70:
440
570
  S4 = 'S4 widening side-effects';
441
571
  prefixGranted = [
442
572
  ['patch', '/calendar-events/bulk-update'],
443
573
  ['post', '/calendar-events/report'],
444
574
  ];
445
- _i = 0, prefixGranted_1 = prefixGranted;
446
- _x.label = 61;
447
- case 61:
448
- if (!(_i < prefixGranted_1.length)) return [3 /*break*/, 64];
449
- _b = prefixGranted_1[_i], method = _b[0], path = _b[1];
575
+ _c = 0, prefixGranted_1 = prefixGranted;
576
+ _11.label = 71;
577
+ case 71:
578
+ if (!(_c < prefixGranted_1.length)) return [3 /*break*/, 74];
579
+ _d = prefixGranted_1[_c], method = _d[0], path = _d[1];
450
580
  return [4 /*yield*/, axios.request({
451
581
  method: method,
452
582
  url: "".concat(host, "/v1").concat(path),
@@ -454,8 +584,8 @@ export var allowed_paths_confinement_tests = function (_a) {
454
584
  headers: { Authorization: "Bearer ".concat(joinSession.authToken) },
455
585
  validateStatus: function () { return true; },
456
586
  })];
457
- case 62:
458
- r = _x.sent();
587
+ case 72:
588
+ r = _11.sent();
459
589
  probes.push({
460
590
  scenario: S4,
461
591
  label: "enduser action gating blocks ".concat(method.toUpperCase(), " ").concat(path),
@@ -465,146 +595,222 @@ export var allowed_paths_confinement_tests = function (_a) {
465
595
  pass: r.status === 401 || r.status === 403,
466
596
  note: 'prefix-granted by /calendar-events; blocked by enduserActions instead',
467
597
  });
468
- _x.label = 63;
469
- case 63:
470
- _i++;
471
- return [3 /*break*/, 61];
472
- case 64:
598
+ _11.label = 73;
599
+ case 73:
600
+ _c++;
601
+ return [3 /*break*/, 71];
602
+ case 74:
473
603
  S6 = 'S6 namespace escapes';
474
604
  ABSENT_ID_A = '68a1b2c3d4e5f60718293a4b';
475
605
  ABSENT_ID_B = '68a1b2c3d4e5f60718293a4c';
476
606
  INVALID_EMAIL = 'f0166-not-an-email';
477
607
  return [4 /*yield*/, expectDenied(S6, 'ESCAPE: /users/consent (minted an unconfined token)', startSession.authToken, baselineUserToken, 'patch', '/users/consent', { termsVersion: '1' })];
478
- case 65:
479
- _x.sent();
608
+ case 75:
609
+ _11.sent();
480
610
  return [4 /*yield*/, expectDenied(S6, 'ESCAPE: /users/switch-account', startSession.authToken, baselineUserToken, 'post', '/users/switch-account', { targetUserId: ABSENT_ID_A })];
481
- case 66:
482
- _x.sent();
611
+ case 76:
612
+ _11.sent();
483
613
  return [4 /*yield*/, expectDenied(S6, 'ESCAPE: /users/configure-inbox', startSession.authToken, baselineUserToken, 'post', '/users/configure-inbox', {})];
484
- case 67:
485
- _x.sent();
614
+ case 77:
615
+ _11.sent();
486
616
  return [4 /*yield*/, expectDenied(S6, 'PHI: /endusers/load-inbox-data', startSession.authToken, baselineUserToken, 'get', '/endusers/load-inbox-data')];
487
- case 68:
488
- _x.sent();
617
+ case 78:
618
+ _11.sent();
489
619
  return [4 /*yield*/, expectDenied(S6, 'PHI: /endusers/report', startSession.authToken, baselineUserToken, 'get', '/endusers/report')];
490
- case 69:
491
- _x.sent();
620
+ case 79:
621
+ _11.sent();
492
622
  return [4 /*yield*/, expectDenied(S6, 'PHI: /endusers/engagement', startSession.authToken, baselineUserToken, 'get', '/endusers/engagement')];
493
- case 70:
494
- _x.sent();
623
+ case 80:
624
+ _11.sent();
495
625
  return [4 /*yield*/, expectDenied(S6, 'DESTRUCTIVE: /endusers/merge', startSession.authToken, baselineUserToken, 'post', '/endusers/merge', { sourceEnduserId: ABSENT_ID_A, destinationEnduserId: ABSENT_ID_B })];
496
- case 71:
497
- _x.sent();
626
+ case 81:
627
+ _11.sent();
498
628
  return [4 /*yield*/, expectDenied(S6, 'DESTRUCTIVE: /endusers/bulk-update', startSession.authToken, baselineUserToken, 'patch', '/endusers/bulk-update', { ids: [ABSENT_ID_A], fields: {} })
499
629
  // method scoping, end to end: the same path is readable but not writable.
500
630
  // Baselines use the ADMIN token — the throwaway host user cannot create users or endusers, so it
501
631
  // would 403 on permissions and the probe could not distinguish that from a confinement denial.
502
632
  ];
503
- case 72:
504
- _x.sent();
633
+ case 82:
634
+ _11.sent();
505
635
  // method scoping, end to end: the same path is readable but not writable.
506
636
  // Baselines use the ADMIN token — the throwaway host user cannot create users or endusers, so it
507
637
  // would 403 on permissions and the probe could not distinguish that from a confinement denial.
508
638
  return [4 /*yield*/, expectAllowed(S6, 'method scoping: GET /endusers allowed', startSession.authToken, 'get', '/endusers')];
509
- case 73:
639
+ case 83:
510
640
  // method scoping, end to end: the same path is readable but not writable.
511
641
  // Baselines use the ADMIN token — the throwaway host user cannot create users or endusers, so it
512
642
  // would 403 on permissions and the probe could not distinguish that from a confinement denial.
513
- _x.sent();
643
+ _11.sent();
514
644
  return [4 /*yield*/, expectDenied(S6, 'method scoping: POST /endusers (create) denied', startSession.authToken, sdk.authToken, 'post', '/endusers', { email: INVALID_EMAIL })];
515
- case 74:
516
- _x.sent();
645
+ case 84:
646
+ _11.sent();
517
647
  return [4 /*yield*/, expectAllowed(S6, 'method scoping: GET /users allowed', startSession.authToken, 'get', '/users')];
518
- case 75:
519
- _x.sent();
648
+ case 85:
649
+ _11.sent();
520
650
  return [4 /*yield*/, expectDenied(S6, 'method scoping: POST /users (create) denied', startSession.authToken, sdk.authToken, 'post', '/users', { email: INVALID_EMAIL })
521
651
  // even if consent were reachable, the token it mints must stay confined (defense in depth)
522
652
  ];
523
- case 76:
524
- _x.sent();
653
+ case 86:
654
+ _11.sent();
525
655
  return [4 /*yield*/, request(startSession.authToken, 'patch', '/users/consent', { termsVersion: '1' })];
526
- case 77:
527
- consentAttempt = _x.sent();
528
- if (!(consentAttempt.status === 200 && ((_v = consentAttempt.body) === null || _v === void 0 ? void 0 : _v.authToken))) return [3 /*break*/, 79];
656
+ case 87:
657
+ consentAttempt = _11.sent();
658
+ if (!(consentAttempt.status === 200 && ((_8 = consentAttempt.body) === null || _8 === void 0 ? void 0 : _8.authToken))) return [3 /*break*/, 89];
529
659
  return [4 /*yield*/, expectDenied(S6, 'consent-minted token is still confined', consentAttempt.body.authToken, baselineUserToken, 'get', '/journeys')];
530
- case 78:
531
- _x.sent();
532
- _x.label = 79;
533
- case 79: return [4 /*yield*/, request(startSession.authToken, 'post', '/refresh-session', {})];
534
- case 80:
535
- refreshedForScopes = _x.sent();
536
- rescopedToken = (_w = refreshedForScopes.body) === null || _w === void 0 ? void 0 : _w.authToken;
660
+ case 88:
661
+ _11.sent();
662
+ _11.label = 89;
663
+ case 89: return [4 /*yield*/, request(startSession.authToken, 'post', '/refresh-session', {})];
664
+ case 90:
665
+ refreshedForScopes = _11.sent();
666
+ rescopedToken = (_9 = refreshedForScopes.body) === null || _9 === void 0 ? void 0 : _9.authToken;
537
667
  probes.push({
538
668
  scenario: S6, label: 'refresh returned a new token', path: '/refresh-session',
539
669
  expected: 'allowed', status: refreshedForScopes.status, pass: !!rescopedToken,
540
670
  });
541
- if (!rescopedToken) return [3 /*break*/, 86];
671
+ if (!rescopedToken) return [3 /*break*/, 96];
542
672
  return [4 /*yield*/, expectDenied(S6, 'after refresh: still cannot reach /users/consent', rescopedToken, baselineUserToken, 'patch', '/users/consent', { termsVersion: '1' })];
543
- case 81:
544
- _x.sent();
673
+ case 91:
674
+ _11.sent();
545
675
  return [4 /*yield*/, expectDenied(S6, 'after refresh: still cannot reach /endusers/load-inbox-data', rescopedToken, baselineUserToken, 'get', '/endusers/load-inbox-data')];
546
- case 82:
547
- _x.sent();
676
+ case 92:
677
+ _11.sent();
548
678
  return [4 /*yield*/, expectDenied(S6, 'after refresh: POST /endusers still denied', rescopedToken, baselineUserToken, 'post', '/endusers', { email: "f0166-should-not-exist-".concat(RAND(), "@tellescope.example") })];
549
- case 83:
550
- _x.sent();
679
+ case 93:
680
+ _11.sent();
551
681
  return [4 /*yield*/, expectAllowed(S6, 'after refresh: GET /endusers still allowed', rescopedToken, 'get', '/endusers')];
552
- case 84:
553
- _x.sent();
682
+ case 94:
683
+ _11.sent();
554
684
  return [4 /*yield*/, expectAllowed(S6, 'after refresh: my-meetings still allowed', rescopedToken, 'get', '/my-meetings')];
555
- case 85:
556
- _x.sent();
557
- _x.label = 86;
558
- case 86:
685
+ case 95:
686
+ _11.sent();
687
+ _11.label = 96;
688
+ case 96:
689
+ S7 = 'S7 appointment-booking';
690
+ return [4 /*yield*/, sdk.api.calendar_event_templates.createOne({
691
+ title: "F-0166 booking ".concat(RAND()), durationInMinutes: 30,
692
+ })];
693
+ case 97:
694
+ bookingTemplate = _11.sent();
695
+ templateId = bookingTemplate.id;
696
+ bookingEnduserEmail = "f0166-booking-".concat(RAND(), "@tellescope.example");
697
+ return [4 /*yield*/, sdk.api.endusers.createOne({
698
+ email: bookingEnduserEmail, fname: 'Booking', lname: 'Confinement',
699
+ })];
700
+ case 98:
701
+ bookingEnduser = _11.sent();
702
+ bookingEnduserId = bookingEnduser.id;
703
+ return [4 /*yield*/, new EnduserSession({ host: host, businessId: businessId })
704
+ .api.calendar_events.session_for_public_appointment_booking({
705
+ businessId: businessId,
706
+ calendarEventTemplateId: bookingTemplate.id,
707
+ email: bookingEnduserEmail,
708
+ fname: 'Booking',
709
+ lname: 'Confinement',
710
+ })];
711
+ case 99:
712
+ bookingSession = _11.sent();
713
+ return [4 /*yield*/, expectAllowed(S7, 'field-restricted: may record terms consent', bookingSession.authToken, 'patch', "/enduser/".concat(bookingEnduser.id), { updates: { termsSigned: new Date().toISOString(), termsVersion: '1' } })];
714
+ case 100:
715
+ _11.sent();
716
+ return [4 /*yield*/, request(bookingSession.authToken, 'patch', "/enduser/".concat(bookingEnduser.id), { updates: { termsVersion: '2' } })];
717
+ case 101:
718
+ bookingEcho = _11.sent();
719
+ bookingLeaked = Object.keys((_10 = bookingEcho.body) !== null && _10 !== void 0 ? _10 : {})
720
+ .filter(function (k) { return !['id', 'termsSigned', 'termsVersion'].includes(k); });
721
+ probes.push({
722
+ scenario: S7,
723
+ label: 'field-restricted: update response echoes only the writable fields',
724
+ path: "/enduser/".concat(bookingEnduser.id),
725
+ expected: 'allowed',
726
+ status: bookingLeaked.length === 0 ? bookingEcho.status : 500,
727
+ pass: bookingEcho.status === 200 && bookingLeaked.length === 0,
728
+ note: bookingLeaked.length ? "LEAKED [".concat(bookingLeaked, "]") : undefined,
729
+ });
730
+ _e = 0, _f = [['email', "f0166-bt-".concat(RAND(), "@tellescope.example")], ['phone', '+15555550188']];
731
+ _11.label = 102;
732
+ case 102:
733
+ if (!(_e < _f.length)) return [3 /*break*/, 105];
734
+ _g = _f[_e], field = _g[0], value = _g[1];
735
+ return [4 /*yield*/, request(bookingSession.authToken, 'patch', "/enduser/".concat(bookingEnduser.id), { updates: (_1 = {}, _1[field] = value, _1) })];
736
+ case 103:
737
+ r = _11.sent();
738
+ probes.push({
739
+ scenario: S7,
740
+ label: "field-restricted: cannot write enduser '".concat(field, "'"),
741
+ path: "/enduser/".concat(bookingEnduser.id),
742
+ expected: 'denied',
743
+ status: r.status,
744
+ pass: r.status === 403,
745
+ note: 'route is granted; the field allowlist is what refuses this',
746
+ });
747
+ _11.label = 104;
748
+ case 104:
749
+ _e++;
750
+ return [3 /*break*/, 102];
751
+ case 105:
752
+ // the booking scope must not have gained anything else along with the enduser write
753
+ return [4 /*yield*/, expectDenied(S7, 'unlisted: form responses', bookingSession.authToken, baselineEnduserToken, 'get', '/form-responses')];
754
+ case 106:
755
+ // the booking scope must not have gained anything else along with the enduser write
756
+ _11.sent();
757
+ return [4 /*yield*/, expectDenied(S7, 'unlisted: calendar events', bookingSession.authToken, baselineEnduserToken, 'get', '/calendar-events')];
758
+ case 107:
759
+ _11.sent();
760
+ return [4 /*yield*/, expectAllowed(S7, 'listed: refresh enduser session', bookingSession.authToken, 'post', '/refresh-enduser-session')
761
+ /* ============ S5: ordinary sessions with no allowedPaths claim ============ */
762
+ ];
763
+ case 108:
764
+ _11.sent();
559
765
  S5 = 'S5 unconfined sessions';
560
- _c = 0, _d = ['/journeys', '/form-responses', '/endusers', '/calendar-events', '/tickets'];
561
- _x.label = 87;
562
- case 87:
563
- if (!(_c < _d.length)) return [3 /*break*/, 90];
564
- path = _d[_c];
766
+ _h = 0, _j = ['/journeys', '/form-responses', '/endusers', '/calendar-events', '/tickets'];
767
+ _11.label = 109;
768
+ case 109:
769
+ if (!(_h < _j.length)) return [3 /*break*/, 112];
770
+ path = _j[_h];
565
771
  return [4 /*yield*/, expectAllowed(S5, "staff session reaches ".concat(path), baselineUserToken, 'get', path)];
566
- case 88:
567
- _x.sent();
568
- _x.label = 89;
569
- case 89:
570
- _c++;
571
- return [3 /*break*/, 87];
572
- case 90: return [4 /*yield*/, expectAllowed(S5, 'staff session reaches /data-sync', baselineUserToken, 'get', '/data-sync', { from: new Date(Date.now() - 60000).toISOString() })];
573
- case 91:
574
- _x.sent();
575
- _e = 0, _f = ['/form-responses', '/calendar-events', '/files'];
576
- _x.label = 92;
577
- case 92:
578
- if (!(_e < _f.length)) return [3 /*break*/, 95];
579
- path = _f[_e];
772
+ case 110:
773
+ _11.sent();
774
+ _11.label = 111;
775
+ case 111:
776
+ _h++;
777
+ return [3 /*break*/, 109];
778
+ case 112: return [4 /*yield*/, expectAllowed(S5, 'staff session reaches /data-sync', baselineUserToken, 'get', '/data-sync', { from: new Date(Date.now() - 60000).toISOString() })];
779
+ case 113:
780
+ _11.sent();
781
+ _k = 0, _l = ['/form-responses', '/calendar-events', '/files'];
782
+ _11.label = 114;
783
+ case 114:
784
+ if (!(_k < _l.length)) return [3 /*break*/, 117];
785
+ path = _l[_k];
580
786
  return [4 /*yield*/, expectAllowed(S5, "enduser session reaches ".concat(path), baselineEnduserToken, 'get', path)];
581
- case 93:
582
- _x.sent();
583
- _x.label = 94;
584
- case 94:
585
- _e++;
586
- return [3 /*break*/, 92];
587
- case 95: return [4 /*yield*/, sdk.test_authenticated()];
588
- case 96:
589
- adminAuthenticated = _x.sent();
787
+ case 115:
788
+ _11.sent();
789
+ _11.label = 116;
790
+ case 116:
791
+ _k++;
792
+ return [3 /*break*/, 114];
793
+ case 117: return [4 /*yield*/, sdk.test_authenticated()];
794
+ case 118:
795
+ adminAuthenticated = _11.sent();
590
796
  probes.push({
591
797
  scenario: S5, label: 'admin sdk still authenticated', path: '/test-authenticated',
592
798
  expected: 'allowed', status: 200, pass: adminAuthenticated === 'Authenticated!',
593
799
  });
594
800
  apiKeySDK = new Session({ host: host, apiKey: '3n5q0SCBT_iUvZz-b9BJtX7o7HQUVJ9v132PgHJNJsg.' });
595
- _g = 0, _h = ['/journeys', '/form-responses', '/endusers', '/calendar-events', '/files'];
596
- _x.label = 97;
597
- case 97:
598
- if (!(_g < _h.length)) return [3 /*break*/, 100];
599
- path = _h[_g];
801
+ _m = 0, _o = ['/journeys', '/form-responses', '/endusers', '/calendar-events', '/files'];
802
+ _11.label = 119;
803
+ case 119:
804
+ if (!(_m < _o.length)) return [3 /*break*/, 122];
805
+ path = _o[_m];
600
806
  return [4 /*yield*/, axios.request({
601
807
  method: 'get',
602
808
  url: "".concat(host, "/v1").concat(path),
603
809
  headers: { Authorization: "API_KEY ".concat(apiKeySDK.apiKey) },
604
810
  validateStatus: function () { return true; },
605
811
  })];
606
- case 98:
607
- r = _x.sent();
812
+ case 120:
813
+ r = _11.sent();
608
814
  probes.push({
609
815
  scenario: S5,
610
816
  label: "API key (headless) reaches ".concat(path),
@@ -613,11 +819,11 @@ export var allowed_paths_confinement_tests = function (_a) {
613
819
  status: r.status,
614
820
  pass: r.status !== 403,
615
821
  });
616
- _x.label = 99;
617
- case 99:
618
- _g++;
619
- return [3 /*break*/, 97];
620
- case 100:
822
+ _11.label = 121;
823
+ case 121:
824
+ _m++;
825
+ return [3 /*break*/, 119];
826
+ case 122:
621
827
  failures = probes.filter(function (p) { return !p.pass; });
622
828
  console.log('');
623
829
  console.log(' F-0166 — full probe table');
@@ -627,111 +833,135 @@ export var allowed_paths_confinement_tests = function (_a) {
627
833
  var inScenario = probes.filter(function (p) { return p.scenario === scenario; });
628
834
  var failed = inScenario.filter(function (p) { return !p.pass; }).length;
629
835
  console.log(" ".concat(scenario, ": ").concat(inScenario.length - failed, "/").concat(inScenario.length, " passing").concat(failed ? " <-- ".concat(failed, " FAILING") : ''));
630
- for (var _y = 0, inScenario_1 = inScenario; _y < inScenario_1.length; _y++) {
631
- var p = inScenario_1[_y];
836
+ for (var _12 = 0, inScenario_1 = inScenario; _12 < inScenario_1.length; _12++) {
837
+ var p = inScenario_1[_12];
632
838
  var baseline = p.baselineStatus !== undefined ? " baseline=".concat(p.baselineStatus) : '';
633
839
  console.log(" ".concat(p.pass ? ' ok ' : 'FAIL', " want=").concat(p.expected.padEnd(7), " got=").concat(p.status).concat(baseline, " ").concat(p.label).concat(p.note ? " (".concat(p.note, ")") : ''));
634
840
  }
635
841
  };
636
- for (_j = 0, scenarios_1 = scenarios; _j < scenarios_1.length; _j++) {
637
- scenario = scenarios_1[_j];
842
+ for (_p = 0, scenarios_1 = scenarios; _p < scenarios_1.length; _p++) {
843
+ scenario = scenarios_1[_p];
638
844
  _loop_1(scenario);
639
845
  }
640
846
  console.log(' ' + '-'.repeat(112));
641
847
  console.log(" TOTAL: ".concat(probes.length - failures.length, "/").concat(probes.length, " passing, ").concat(failures.length, " failing"));
642
848
  console.log('');
643
- for (_k = 0, probes_1 = probes; _k < probes_1.length; _k++) {
644
- p = probes_1[_k];
849
+ for (_q = 0, probes_1 = probes; _q < probes_1.length; _q++) {
850
+ p = probes_1[_q];
645
851
  assert(p.pass, "".concat(p.scenario, " \u2014 ").concat(p.label, " (").concat(p.path, "): wanted ").concat(p.expected, ", got status ").concat(p.status)
646
852
  + (p.baselineStatus !== undefined ? " (unconfined baseline got ".concat(p.baselineStatus, ")") : ''), "".concat(p.scenario, " \u2014 ").concat(p.label));
647
853
  }
648
- return [3 /*break*/, 130];
649
- case 101:
650
- _l = 0, eventIds_1 = eventIds;
651
- _x.label = 102;
652
- case 102:
653
- if (!(_l < eventIds_1.length)) return [3 /*break*/, 107];
654
- id = eventIds_1[_l];
655
- _x.label = 103;
656
- case 103:
657
- _x.trys.push([103, 105, , 106]);
854
+ return [3 /*break*/, 160];
855
+ case 123:
856
+ _r = 0, eventIds_1 = eventIds;
857
+ _11.label = 124;
858
+ case 124:
859
+ if (!(_r < eventIds_1.length)) return [3 /*break*/, 129];
860
+ id = eventIds_1[_r];
861
+ _11.label = 125;
862
+ case 125:
863
+ _11.trys.push([125, 127, , 128]);
658
864
  return [4 /*yield*/, sdk.api.calendar_events.deleteOne(id)];
659
- case 104:
660
- _x.sent();
661
- return [3 /*break*/, 106];
662
- case 105:
663
- _m = _x.sent();
664
- return [3 /*break*/, 106];
665
- case 106:
666
- _l++;
667
- return [3 /*break*/, 102];
668
- case 107:
669
- if (!joinEnduserId) return [3 /*break*/, 111];
670
- _x.label = 108;
671
- case 108:
672
- _x.trys.push([108, 110, , 111]);
865
+ case 126:
866
+ _11.sent();
867
+ return [3 /*break*/, 128];
868
+ case 127:
869
+ _s = _11.sent();
870
+ return [3 /*break*/, 128];
871
+ case 128:
872
+ _r++;
873
+ return [3 /*break*/, 124];
874
+ case 129:
875
+ if (!joinEnduserId) return [3 /*break*/, 133];
876
+ _11.label = 130;
877
+ case 130:
878
+ _11.trys.push([130, 132, , 133]);
673
879
  return [4 /*yield*/, sdk.api.endusers.deleteOne(joinEnduserId)];
674
- case 109:
675
- _x.sent();
676
- return [3 /*break*/, 111];
677
- case 110:
678
- _o = _x.sent();
679
- return [3 /*break*/, 111];
680
- case 111:
681
- if (!formEnduserEmail) return [3 /*break*/, 117];
682
- _x.label = 112;
683
- case 112:
684
- _x.trys.push([112, 116, , 117]);
880
+ case 131:
881
+ _11.sent();
882
+ return [3 /*break*/, 133];
883
+ case 132:
884
+ _t = _11.sent();
885
+ return [3 /*break*/, 133];
886
+ case 133:
887
+ if (!formEnduserEmail) return [3 /*break*/, 139];
888
+ _11.label = 134;
889
+ case 134:
890
+ _11.trys.push([134, 138, , 139]);
685
891
  return [4 /*yield*/, sdk.api.endusers.getOne({ email: formEnduserEmail })];
686
- case 113:
687
- e = _x.sent();
688
- if (!(e === null || e === void 0 ? void 0 : e.id)) return [3 /*break*/, 115];
892
+ case 135:
893
+ e = _11.sent();
894
+ if (!(e === null || e === void 0 ? void 0 : e.id)) return [3 /*break*/, 137];
689
895
  return [4 /*yield*/, sdk.api.endusers.deleteOne(e.id)];
690
- case 114:
691
- _x.sent();
692
- _x.label = 115;
693
- case 115: return [3 /*break*/, 117];
694
- case 116:
695
- _p = _x.sent();
696
- return [3 /*break*/, 117];
697
- case 117:
698
- if (!hostUserId) return [3 /*break*/, 121];
699
- _x.label = 118;
700
- case 118:
701
- _x.trys.push([118, 120, , 121]);
896
+ case 136:
897
+ _11.sent();
898
+ _11.label = 137;
899
+ case 137: return [3 /*break*/, 139];
900
+ case 138:
901
+ _u = _11.sent();
902
+ return [3 /*break*/, 139];
903
+ case 139:
904
+ if (!hostUserId) return [3 /*break*/, 143];
905
+ _11.label = 140;
906
+ case 140:
907
+ _11.trys.push([140, 142, , 143]);
702
908
  return [4 /*yield*/, sdk.api.users.deleteOne(hostUserId)];
703
- case 119:
704
- _x.sent();
705
- return [3 /*break*/, 121];
706
- case 120:
707
- _q = _x.sent();
708
- return [3 /*break*/, 121];
709
- case 121:
710
- if (!productId) return [3 /*break*/, 125];
711
- _x.label = 122;
712
- case 122:
713
- _x.trys.push([122, 124, , 125]);
909
+ case 141:
910
+ _11.sent();
911
+ return [3 /*break*/, 143];
912
+ case 142:
913
+ _v = _11.sent();
914
+ return [3 /*break*/, 143];
915
+ case 143:
916
+ if (!bookingEnduserId) return [3 /*break*/, 147];
917
+ _11.label = 144;
918
+ case 144:
919
+ _11.trys.push([144, 146, , 147]);
920
+ return [4 /*yield*/, sdk.api.endusers.deleteOne(bookingEnduserId)];
921
+ case 145:
922
+ _11.sent();
923
+ return [3 /*break*/, 147];
924
+ case 146:
925
+ _w = _11.sent();
926
+ return [3 /*break*/, 147];
927
+ case 147:
928
+ if (!templateId) return [3 /*break*/, 151];
929
+ _11.label = 148;
930
+ case 148:
931
+ _11.trys.push([148, 150, , 151]);
932
+ return [4 /*yield*/, sdk.api.calendar_event_templates.deleteOne(templateId)];
933
+ case 149:
934
+ _11.sent();
935
+ return [3 /*break*/, 151];
936
+ case 150:
937
+ _x = _11.sent();
938
+ return [3 /*break*/, 151];
939
+ case 151:
940
+ if (!productId) return [3 /*break*/, 155];
941
+ _11.label = 152;
942
+ case 152:
943
+ _11.trys.push([152, 154, , 155]);
714
944
  return [4 /*yield*/, sdk.api.products.deleteOne(productId)];
715
- case 123:
716
- _x.sent();
717
- return [3 /*break*/, 125];
718
- case 124:
719
- _r = _x.sent();
720
- return [3 /*break*/, 125];
721
- case 125:
722
- if (!formId) return [3 /*break*/, 129];
723
- _x.label = 126;
724
- case 126:
725
- _x.trys.push([126, 128, , 129]);
945
+ case 153:
946
+ _11.sent();
947
+ return [3 /*break*/, 155];
948
+ case 154:
949
+ _y = _11.sent();
950
+ return [3 /*break*/, 155];
951
+ case 155:
952
+ if (!formId) return [3 /*break*/, 159];
953
+ _11.label = 156;
954
+ case 156:
955
+ _11.trys.push([156, 158, , 159]);
726
956
  return [4 /*yield*/, sdk.api.forms.deleteOne(formId)];
727
- case 127:
728
- _x.sent();
729
- return [3 /*break*/, 129];
730
- case 128:
731
- _s = _x.sent();
732
- return [3 /*break*/, 129];
733
- case 129: return [7 /*endfinally*/];
734
- case 130: return [2 /*return*/];
957
+ case 157:
958
+ _11.sent();
959
+ return [3 /*break*/, 159];
960
+ case 158:
961
+ _z = _11.sent();
962
+ return [3 /*break*/, 159];
963
+ case 159: return [7 /*endfinally*/];
964
+ case 160: return [2 /*return*/];
735
965
  }
736
966
  });
737
967
  });