@tellescope/sdk 1.256.9 → 1.256.11

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 (41) hide show
  1. package/lib/cjs/sdk.d.ts +2 -2
  2. package/lib/cjs/tests/api_tests/generate_suggested_reply.test.d.ts.map +1 -0
  3. package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.d.ts +29 -0
  4. package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.d.ts.map +1 -0
  5. package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.js +472 -0
  6. package/lib/cjs/tests/api_tests/security/readonly-enduser-preview.test.js.map +1 -0
  7. package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.d.ts +26 -0
  8. package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.d.ts.map +1 -0
  9. package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.js +336 -0
  10. package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.js.map +1 -0
  11. package/lib/cjs/tests/api_tests/stedi_approval.test.d.ts +6 -0
  12. package/lib/cjs/tests/api_tests/stedi_approval.test.d.ts.map +1 -0
  13. package/lib/cjs/tests/api_tests/stedi_approval.test.js +205 -0
  14. package/lib/cjs/tests/api_tests/stedi_approval.test.js.map +1 -0
  15. package/lib/cjs/tests/tests.d.ts.map +1 -1
  16. package/lib/cjs/tests/tests.js +236 -203
  17. package/lib/cjs/tests/tests.js.map +1 -1
  18. package/lib/esm/sdk.d.ts +2 -2
  19. package/lib/esm/tests/api_tests/generate_suggested_reply.test.d.ts.map +1 -0
  20. package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.d.ts +29 -0
  21. package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.d.ts.map +1 -0
  22. package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.js +465 -0
  23. package/lib/esm/tests/api_tests/security/readonly-enduser-preview.test.js.map +1 -0
  24. package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.d.ts +26 -0
  25. package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.d.ts.map +1 -0
  26. package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.js +332 -0
  27. package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.js.map +1 -0
  28. package/lib/esm/tests/api_tests/stedi_approval.test.d.ts +6 -0
  29. package/lib/esm/tests/api_tests/stedi_approval.test.d.ts.map +1 -0
  30. package/lib/esm/tests/api_tests/stedi_approval.test.js +201 -0
  31. package/lib/esm/tests/api_tests/stedi_approval.test.js.map +1 -0
  32. package/lib/esm/tests/tests.d.ts.map +1 -1
  33. package/lib/esm/tests/tests.js +236 -203
  34. package/lib/esm/tests/tests.js.map +1 -1
  35. package/lib/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +10 -10
  37. package/src/tests/api_tests/security/readonly-enduser-preview.test.ts +301 -0
  38. package/src/tests/api_tests/security/suggested-reply-redaction.test.ts +232 -0
  39. package/src/tests/api_tests/stedi_approval.test.ts +167 -0
  40. package/src/tests/tests.ts +34 -13
  41. package/test_generated.pdf +0 -0
package/lib/cjs/sdk.d.ts CHANGED
@@ -4193,7 +4193,7 @@ export declare class Session extends SessionManager {
4193
4193
  iat: number;
4194
4194
  exp: number;
4195
4195
  requiresMFA?: boolean | undefined;
4196
- sessionScopes?: ("video-join-link" | "video-start-link" | "public-form" | "appointment-booking" | "ics-download" | "embeddables-token")[] | undefined;
4196
+ sessionScopes?: ("video-join-link" | "video-start-link" | "public-form" | "appointment-booking" | "ics-download" | "embeddables-token" | "portal-preview")[] | undefined;
4197
4197
  scopeContext?: import("@tellescope/types-models").SessionScopeContext | undefined;
4198
4198
  allowedPaths?: string[] | undefined;
4199
4199
  users?: number | undefined;
@@ -4358,7 +4358,7 @@ export declare class Session extends SessionManager {
4358
4358
  iat: number;
4359
4359
  exp: number;
4360
4360
  requiresMFA?: boolean | undefined;
4361
- sessionScopes?: ("video-join-link" | "video-start-link" | "public-form" | "appointment-booking" | "ics-download" | "embeddables-token")[] | undefined;
4361
+ sessionScopes?: ("video-join-link" | "video-start-link" | "public-form" | "appointment-booking" | "ics-download" | "embeddables-token" | "portal-preview")[] | undefined;
4362
4362
  scopeContext?: import("@tellescope/types-models").SessionScopeContext | undefined;
4363
4363
  allowedPaths?: string[] | undefined;
4364
4364
  users?: number | undefined;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate_suggested_reply.test.d.ts","sourceRoot":"","sources":["../../../../src/tests/api_tests/generate_suggested_reply.test.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAiBnC,eAAO,MAAM,8BAA8B;SAAwC,OAAO;iBAAe,OAAO;mBAgR/G,CAAA"}
@@ -0,0 +1,29 @@
1
+ import { Session } from "../../../sdk";
2
+ /**
3
+ * Regression test for F-0138
4
+ * (security-audit/findings/F-0138-endusers-generate-auth-token-no-admin-gate.md).
5
+ *
6
+ * `endusers.generate_auth_token` (the "View portal as patient" preview flow) requires only
7
+ * `endusers.read`, yet mints a FULLY-PRIVILEGED enduser session. A staff user who cannot update a
8
+ * patient can therefore act AS that patient: edit the record, submit forms, send chats, book
9
+ * appointments, even set the portal password.
10
+ *
11
+ * The fix mints the preview token with `sessionScopes: ['portal-preview']` whenever the calling
12
+ * staff user lacks update access to that specific enduser (record-level, honoring `Assigned`
13
+ * scoping). The scope's allowlist (api/modules/session_scopes.ts) grants exactly the portal's read
14
+ * surface plus session maintenance; every write path 403s via existing path confinement.
15
+ *
16
+ * RED (pre-fix): section A fails — the response carries no sessionScopes and every write in A2
17
+ * SUCCEEDS. Sections D/E (controls) pass pre-fix. GREEN (post-fix): all sections pass.
18
+ *
19
+ * Probes use raw axios rather than the SDK: parseError (sdk/src/session.ts) discards the status
20
+ * code, and a path-confinement denial (403 "Inaccessible") is byte-identical in message to a
21
+ * permissions denial. Write/blocked-read probes additionally confirm an UNCONFINED enduser token
22
+ * gets a non-403 on the same path in the same run, so a 403 can only be confinement, never bad
23
+ * permissions or a bad body.
24
+ */
25
+ export declare const readonly_enduser_preview_tests: ({ sdk }: {
26
+ sdk: Session;
27
+ sdkNonAdmin: Session;
28
+ }) => Promise<void>;
29
+ //# sourceMappingURL=readonly-enduser-preview.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readonly-enduser-preview.test.d.ts","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/readonly-enduser-preview.test.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAkB,MAAM,cAAc,CAAA;AA8BtD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,8BAA8B;SAA2B,OAAO;iBAAe,OAAO;mBA6NlG,CAAA"}
@@ -0,0 +1,472 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
24
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.readonly_enduser_preview_tests = void 0;
54
+ require('source-map-support').install();
55
+ var axios_1 = __importDefault(require("axios"));
56
+ var sdk_1 = require("../../../sdk");
57
+ var testing_1 = require("@tellescope/testing");
58
+ var constants_1 = require("@tellescope/constants");
59
+ var setup_1 = require("../../setup");
60
+ var host = process.env.API_URL || 'http://localhost:8080';
61
+ var RAND = function () { return Math.random().toString(36).slice(2, 10); };
62
+ var ALL = 'All';
63
+ var READ_ONLY = { create: constants_1.NO_ACCESS, read: ALL, update: constants_1.NO_ACCESS, delete: constants_1.NO_ACCESS };
64
+ var FULL_ACCESS = { create: ALL, read: ALL, update: ALL, delete: ALL };
65
+ var ASSIGNED_UPDATE = { create: constants_1.NO_ACCESS, read: ALL, update: 'Assigned', delete: constants_1.NO_ACCESS };
66
+ var PORTAL_PREVIEW_SCOPE = 'portal-preview';
67
+ /**
68
+ * Regression test for F-0138
69
+ * (security-audit/findings/F-0138-endusers-generate-auth-token-no-admin-gate.md).
70
+ *
71
+ * `endusers.generate_auth_token` (the "View portal as patient" preview flow) requires only
72
+ * `endusers.read`, yet mints a FULLY-PRIVILEGED enduser session. A staff user who cannot update a
73
+ * patient can therefore act AS that patient: edit the record, submit forms, send chats, book
74
+ * appointments, even set the portal password.
75
+ *
76
+ * The fix mints the preview token with `sessionScopes: ['portal-preview']` whenever the calling
77
+ * staff user lacks update access to that specific enduser (record-level, honoring `Assigned`
78
+ * scoping). The scope's allowlist (api/modules/session_scopes.ts) grants exactly the portal's read
79
+ * surface plus session maintenance; every write path 403s via existing path confinement.
80
+ *
81
+ * RED (pre-fix): section A fails — the response carries no sessionScopes and every write in A2
82
+ * SUCCEEDS. Sections D/E (controls) pass pre-fix. GREEN (post-fix): all sections pass.
83
+ *
84
+ * Probes use raw axios rather than the SDK: parseError (sdk/src/session.ts) discards the status
85
+ * code, and a path-confinement denial (403 "Inaccessible") is byte-identical in message to a
86
+ * permissions denial. Write/blocked-read probes additionally confirm an UNCONFINED enduser token
87
+ * gets a non-403 on the same path in the same run, so a 403 can only be confinement, never bad
88
+ * permissions or a bad body.
89
+ */
90
+ var readonly_enduser_preview_tests = function (_a) {
91
+ var sdk = _a.sdk;
92
+ return __awaiter(void 0, void 0, void 0, function () {
93
+ var checks, record, request, isPathDenial, expectDenied, expectAllowed, hasScope, readonlyRole, fullUpdateRole, assignedUpdateRole, roleIds, userIds, enduserIds, _i, _b, _c, role, endusersPerm, rbap, mkUser, readonlyUser, fullUpdateUser, assignedUpdateUser, staffSession, readonlyStaff, fullUpdateStaff, assignedUpdateStaff, enduser, assignedEnduser, form, formResponse, previewMint, previewToken, baselineEnduserToken, refreshed, refreshedToken, cMint, cToken, adminMint, fullMint, assignedMint, assignedMintUnassigned, _d, checks_1, c, _e, checks_2, c, _f, enduserIds_1, id, _g, _h, userIds_1, id, _j, _k, roleIds_1, id, _l;
94
+ var _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
95
+ return __generator(this, function (_y) {
96
+ switch (_y.label) {
97
+ case 0:
98
+ (0, testing_1.log_header)("F-0138: portal-preview scope for read-only staff generate_auth_token");
99
+ checks = [];
100
+ record = function (label, pass, detail) { return checks.push({ label: label, pass: pass, detail: detail }); };
101
+ request = function (token, method, path, data) { return __awaiter(void 0, void 0, void 0, function () {
102
+ var r;
103
+ return __generator(this, function (_a) {
104
+ switch (_a.label) {
105
+ case 0: return [4 /*yield*/, axios_1.default.request(__assign(__assign({ method: method, url: "".concat(host, "/v1").concat(path) }, method === 'get' ? { params: data } : { data: data }), { headers: { Authorization: "Bearer ".concat(token) }, validateStatus: function () { return true; } }))];
106
+ case 1:
107
+ r = _a.sent();
108
+ return [2 /*return*/, { status: r.status, body: r.data }];
109
+ }
110
+ });
111
+ }); };
112
+ isPathDenial = function (r) {
113
+ var _a;
114
+ return (r.status === 403 && ((_a = r.body) === null || _a === void 0 ? void 0 : _a.message) === 'Inaccessible');
115
+ };
116
+ expectDenied = function (label, confinedToken, baselineToken, method, path, data) { return __awaiter(void 0, void 0, void 0, function () {
117
+ var baseline, confined, badProbe;
118
+ return __generator(this, function (_a) {
119
+ switch (_a.label) {
120
+ case 0: return [4 /*yield*/, request(baselineToken, method, path, data)];
121
+ case 1:
122
+ baseline = _a.sent();
123
+ return [4 /*yield*/, request(confinedToken, method, path, data)];
124
+ case 2:
125
+ confined = _a.sent();
126
+ badProbe = baseline.status === 403;
127
+ record(label, !badProbe && isPathDenial(confined), "".concat(method, ":").concat(path, " confined=").concat(confined.status, " baseline=").concat(baseline.status) + (badProbe ? ' BAD PROBE: baseline also 403' : ''));
128
+ return [2 /*return*/];
129
+ }
130
+ });
131
+ }); };
132
+ expectAllowed = function (label, token, method, path, data) { return __awaiter(void 0, void 0, void 0, function () {
133
+ var r;
134
+ return __generator(this, function (_a) {
135
+ switch (_a.label) {
136
+ case 0: return [4 /*yield*/, request(token, method, path, data)];
137
+ case 1:
138
+ r = _a.sent();
139
+ record(label, r.status !== 403 && r.status !== 401, "".concat(method, ":").concat(path, " status=").concat(r.status));
140
+ return [2 /*return*/];
141
+ }
142
+ });
143
+ }); };
144
+ hasScope = function (enduser) { var _a, _b; return !!((_b = (_a = enduser === null || enduser === void 0 ? void 0 : enduser.sessionScopes) === null || _a === void 0 ? void 0 : _a.includes) === null || _b === void 0 ? void 0 : _b.call(_a, PORTAL_PREVIEW_SCOPE)); };
145
+ readonlyRole = "f0138-readonly-".concat(RAND());
146
+ fullUpdateRole = "f0138-full-".concat(RAND());
147
+ assignedUpdateRole = "f0138-assigned-".concat(RAND());
148
+ roleIds = [];
149
+ userIds = [];
150
+ enduserIds = [];
151
+ _y.label = 1;
152
+ case 1:
153
+ _y.trys.push([1, , 47, 66]);
154
+ _i = 0, _b = [
155
+ [readonlyRole, READ_ONLY],
156
+ [fullUpdateRole, FULL_ACCESS],
157
+ [assignedUpdateRole, ASSIGNED_UPDATE],
158
+ ];
159
+ _y.label = 2;
160
+ case 2:
161
+ if (!(_i < _b.length)) return [3 /*break*/, 5];
162
+ _c = _b[_i], role = _c[0], endusersPerm = _c[1];
163
+ return [4 /*yield*/, sdk.api.role_based_access_permissions.createOne({
164
+ role: role,
165
+ permissions: __assign(__assign({}, constants_1.PROVIDER_PERMISSIONS), { endusers: endusersPerm }),
166
+ })];
167
+ case 3:
168
+ rbap = _y.sent();
169
+ roleIds.push(rbap.id);
170
+ _y.label = 4;
171
+ case 4:
172
+ _i++;
173
+ return [3 /*break*/, 2];
174
+ case 5:
175
+ mkUser = function (role) { return __awaiter(void 0, void 0, void 0, function () {
176
+ var u;
177
+ return __generator(this, function (_a) {
178
+ switch (_a.label) {
179
+ case 0: return [4 /*yield*/, sdk.api.users.createOne({
180
+ email: "f0138-".concat(role, "-").concat(RAND(), "@tellescope.example"),
181
+ fname: 'F0138', lname: 'Staff',
182
+ verifiedEmail: true,
183
+ notificationEmailsDisabled: true,
184
+ roles: [role],
185
+ })];
186
+ case 1:
187
+ u = _a.sent();
188
+ userIds.push(u.id);
189
+ return [2 /*return*/, u];
190
+ }
191
+ });
192
+ }); };
193
+ return [4 /*yield*/, mkUser(readonlyRole)];
194
+ case 6:
195
+ readonlyUser = _y.sent();
196
+ return [4 /*yield*/, mkUser(fullUpdateRole)];
197
+ case 7:
198
+ fullUpdateUser = _y.sent();
199
+ return [4 /*yield*/, mkUser(assignedUpdateRole)
200
+ // Staff sessions from admin-minted tokens (avoids the password / login-rate-limit dance).
201
+ ];
202
+ case 8:
203
+ assignedUpdateUser = _y.sent();
204
+ staffSession = function (userId) { return __awaiter(void 0, void 0, void 0, function () {
205
+ var authToken;
206
+ return __generator(this, function (_a) {
207
+ switch (_a.label) {
208
+ case 0: return [4 /*yield*/, sdk.api.users.generate_auth_token({ id: userId })];
209
+ case 1:
210
+ authToken = (_a.sent()).authToken;
211
+ return [2 /*return*/, new sdk_1.Session({ host: host, authToken: authToken })];
212
+ }
213
+ });
214
+ }); };
215
+ return [4 /*yield*/, staffSession(readonlyUser.id)];
216
+ case 9:
217
+ readonlyStaff = _y.sent();
218
+ return [4 /*yield*/, staffSession(fullUpdateUser.id)];
219
+ case 10:
220
+ fullUpdateStaff = _y.sent();
221
+ return [4 /*yield*/, staffSession(assignedUpdateUser.id)
222
+ // Endusers: unassigned patient, a bystander, and one assigned to the assigned-update user.
223
+ ];
224
+ case 11:
225
+ assignedUpdateStaff = _y.sent();
226
+ return [4 /*yield*/, sdk.api.endusers.createOne({
227
+ email: "f0138-patient-".concat(RAND(), "@tellescope.example"), fname: 'F0138', lname: 'Patient',
228
+ })];
229
+ case 12:
230
+ enduser = _y.sent();
231
+ enduserIds.push(enduser.id);
232
+ return [4 /*yield*/, sdk.api.endusers.createOne({
233
+ email: "f0138-assigned-".concat(RAND(), "@tellescope.example"), fname: 'F0138', lname: 'Assigned',
234
+ assignedTo: [assignedUpdateUser.id],
235
+ })];
236
+ case 13:
237
+ assignedEnduser = _y.sent();
238
+ enduserIds.push(assignedEnduser.id);
239
+ return [4 /*yield*/, sdk.api.forms.createOne({ title: "F-0138 preview form ".concat(RAND()) })];
240
+ case 14:
241
+ form = _y.sent();
242
+ return [4 /*yield*/, sdk.api.form_responses.prepare_form_response({
243
+ formId: form.id, enduserId: enduser.id,
244
+ })
245
+ /* ===== A1: read-only staff mints a scoped preview token ===== */
246
+ ];
247
+ case 15:
248
+ formResponse = (_y.sent()).response;
249
+ return [4 /*yield*/, readonlyStaff.api.endusers.generate_auth_token({ id: enduser.id })];
250
+ case 16:
251
+ previewMint = _y.sent();
252
+ previewToken = previewMint.authToken;
253
+ record('A1: read-only staff receives an authToken', !!previewToken, "authToken present=".concat(!!previewToken));
254
+ record('A1: read-only staff mint is scoped to portal-preview', hasScope(previewMint.enduser), "sessionScopes=".concat(JSON.stringify((_m = previewMint.enduser) === null || _m === void 0 ? void 0 : _m.sessionScopes), " (F-0138 vuln if absent)"));
255
+ record('A1(E): scoped preview token carries denySocket', ((_o = previewMint.enduser) === null || _o === void 0 ? void 0 : _o.denySocket) === true, "denySocket=".concat((_p = previewMint.enduser) === null || _p === void 0 ? void 0 : _p.denySocket));
256
+ return [4 /*yield*/, sdk.api.endusers.generate_auth_token({ id: enduser.id })
257
+ /* ===== A2: writes blocked for the scoped session ===== */
258
+ ];
259
+ case 17:
260
+ baselineEnduserToken = (_y.sent()).authToken;
261
+ /* ===== A2: writes blocked for the scoped session ===== */
262
+ return [4 /*yield*/, expectDenied('A2: cannot self-update the patient record', previewToken, baselineEnduserToken, 'patch', "/enduser/".concat(enduser.id), { updates: { fname: 'Hacked' } })];
263
+ case 18:
264
+ /* ===== A2: writes blocked for the scoped session ===== */
265
+ _y.sent();
266
+ return [4 /*yield*/, expectDenied('A2: cannot set the patient portal password', previewToken, baselineEnduserToken, 'post', '/set-enduser-password', { id: enduser.id, password: "F0138Pw!".concat(RAND()) })];
267
+ case 19:
268
+ _y.sent();
269
+ return [4 /*yield*/, expectDenied('A2: cannot send a chat as the patient', previewToken, baselineEnduserToken, 'post', '/chat', { message: 'hi from preview' })];
270
+ case 20:
271
+ _y.sent();
272
+ return [4 /*yield*/, expectDenied('A2: cannot submit a form as the patient', previewToken, baselineEnduserToken, 'patch', '/submit-form-response', { id: formResponse.id, responses: [] })];
273
+ case 21:
274
+ _y.sent();
275
+ return [4 /*yield*/, expectDenied('A2: cannot book an appointment as the patient', previewToken, baselineEnduserToken, 'post', '/book-appointment', {})];
276
+ case 22:
277
+ _y.sent();
278
+ return [4 /*yield*/, expectDenied('A2: cannot start a file upload as the patient', previewToken, baselineEnduserToken, 'post', '/prepare-file-upload', { name: 'x.pdf', type: 'application/pdf', enduserId: enduser.id })
279
+ /* ===== B: no laundering via refresh ===== */
280
+ ];
281
+ case 23:
282
+ _y.sent();
283
+ return [4 /*yield*/, request(previewToken, 'post', '/refresh-enduser-session', {})];
284
+ case 24:
285
+ refreshed = _y.sent();
286
+ refreshedToken = (_q = refreshed.body) === null || _q === void 0 ? void 0 : _q.authToken;
287
+ record('B: refresh succeeds', refreshed.status === 200 && !!refreshedToken, "status=".concat(refreshed.status, " token=").concat(!!refreshedToken));
288
+ record('B: refreshed session still carries portal-preview scope', hasScope((_r = refreshed.body) === null || _r === void 0 ? void 0 : _r.enduser), "sessionScopes=".concat(JSON.stringify((_t = (_s = refreshed.body) === null || _s === void 0 ? void 0 : _s.enduser) === null || _t === void 0 ? void 0 : _t.sessionScopes), " (laundering if absent)"));
289
+ if (!refreshedToken) return [3 /*break*/, 26];
290
+ return [4 /*yield*/, expectDenied('B: refreshed token still cannot write', refreshedToken, baselineEnduserToken, 'patch', "/enduser/".concat(enduser.id), { updates: { fname: 'HackedAfterRefresh' } })];
291
+ case 25:
292
+ _y.sent();
293
+ _y.label = 26;
294
+ case 26: return [4 /*yield*/, expectAllowed('B: logout allowed', previewToken, 'post', '/logout-enduser')
295
+ /* ===== C: ungranted mutating "reads" blocked ===== */
296
+ // Re-mint (the previous token was just logged out).
297
+ ];
298
+ case 27:
299
+ _y.sent();
300
+ return [4 /*yield*/, readonlyStaff.api.endusers.generate_auth_token({ id: enduser.id })];
301
+ case 28:
302
+ cMint = _y.sent();
303
+ cToken = cMint.authToken;
304
+ return [4 /*yield*/, expectDenied('C: proxy_read blocked', cToken, baselineEnduserToken, 'get', '/integrations/proxy-read', { integration: 'Stripe', type: 'subscriptions' })];
305
+ case 29:
306
+ _y.sent();
307
+ return [4 /*yield*/, expectDenied('C: booking_info (mints a token) blocked', cToken, baselineEnduserToken, 'get', '/form-fields/booking-info', { bookingPageId: 'x' })];
308
+ case 30:
309
+ _y.sent();
310
+ return [4 /*yield*/, expectDenied('C: form_responses stripe_details blocked', cToken, baselineEnduserToken, 'get', '/form-responses/stripe-details', {})];
311
+ case 31:
312
+ _y.sent();
313
+ return [4 /*yield*/, expectDenied('C: products stripe-portal-session blocked', cToken, baselineEnduserToken, 'get', '/products/stripe-portal-session', {})
314
+ /* ===== D: granted portal reads still work ===== */
315
+ ];
316
+ case 32:
317
+ _y.sent();
318
+ /* ===== D: granted portal reads still work ===== */
319
+ return [4 /*yield*/, expectAllowed('D: portal-customizations', cToken, 'get', '/portal-customizations')];
320
+ case 33:
321
+ /* ===== D: granted portal reads still work ===== */
322
+ _y.sent();
323
+ return [4 /*yield*/, expectAllowed('D: calendar-events', cToken, 'get', '/calendar-events')];
324
+ case 34:
325
+ _y.sent();
326
+ return [4 /*yield*/, expectAllowed('D: my-meetings', cToken, 'get', '/my-meetings')];
327
+ case 35:
328
+ _y.sent();
329
+ return [4 /*yield*/, expectAllowed('D: chat-rooms', cToken, 'get', '/chat-rooms')];
330
+ case 36:
331
+ _y.sent();
332
+ return [4 /*yield*/, expectAllowed('D: care-plans', cToken, 'get', '/care-plans')];
333
+ case 37:
334
+ _y.sent();
335
+ return [4 /*yield*/, expectAllowed('D: form-responses', cToken, 'get', '/form-responses')];
336
+ case 38:
337
+ _y.sent();
338
+ return [4 /*yield*/, expectAllowed('D: self enduser read', cToken, 'get', "/enduser/".concat(enduser.id))];
339
+ case 39:
340
+ _y.sent();
341
+ return [4 /*yield*/, expectAllowed('D: file-download-URL', cToken, 'get', '/file-download-URL', { secureName: 'nonexistent' })];
342
+ case 40:
343
+ _y.sent();
344
+ return [4 /*yield*/, expectAllowed('D: enduser-session-info', cToken, 'get', '/enduser-session-info')
345
+ /* ===== E: zero behavior change controls ===== */
346
+ ];
347
+ case 41:
348
+ _y.sent();
349
+ return [4 /*yield*/, sdk.api.endusers.generate_auth_token({ id: enduser.id })];
350
+ case 42:
351
+ adminMint = _y.sent();
352
+ record('E: admin-minted token is NOT scoped', !hasScope(adminMint.enduser), "sessionScopes=".concat(JSON.stringify((_u = adminMint.enduser) === null || _u === void 0 ? void 0 : _u.sessionScopes)));
353
+ return [4 /*yield*/, expectAllowed('E: admin-minted token can write', adminMint.authToken, 'patch', "/enduser/".concat(enduser.id), { updates: { fname: 'AdminEditOk' } })];
354
+ case 43:
355
+ _y.sent();
356
+ return [4 /*yield*/, fullUpdateStaff.api.endusers.generate_auth_token({ id: enduser.id })];
357
+ case 44:
358
+ fullMint = _y.sent();
359
+ record('E: update:All staff mints an UNSCOPED token', !hasScope(fullMint.enduser), "sessionScopes=".concat(JSON.stringify((_v = fullMint.enduser) === null || _v === void 0 ? void 0 : _v.sessionScopes)));
360
+ return [4 /*yield*/, assignedUpdateStaff.api.endusers.generate_auth_token({ id: assignedEnduser.id })];
361
+ case 45:
362
+ assignedMint = _y.sent();
363
+ record('E: assigned-update staff UNSCOPED for an assigned patient', !hasScope(assignedMint.enduser), "sessionScopes=".concat(JSON.stringify((_w = assignedMint.enduser) === null || _w === void 0 ? void 0 : _w.sessionScopes)));
364
+ return [4 /*yield*/, assignedUpdateStaff.api.endusers.generate_auth_token({ id: enduser.id })];
365
+ case 46:
366
+ assignedMintUnassigned = _y.sent();
367
+ record('E: assigned-update staff SCOPED for an unassigned patient', hasScope(assignedMintUnassigned.enduser), "sessionScopes=".concat(JSON.stringify((_x = assignedMintUnassigned.enduser) === null || _x === void 0 ? void 0 : _x.sessionScopes)));
368
+ /* ===== report: print full table first, then assert (harness exits on first failure) ===== */
369
+ console.log('\n F-0138 check summary:');
370
+ for (_d = 0, checks_1 = checks; _d < checks_1.length; _d++) {
371
+ c = checks_1[_d];
372
+ console.log(" ".concat(c.pass ? 'PASS' : 'FAIL', " ").concat(c.label, " \u2014 ").concat(c.detail));
373
+ }
374
+ console.log('');
375
+ for (_e = 0, checks_2 = checks; _e < checks_2.length; _e++) {
376
+ c = checks_2[_e];
377
+ (0, testing_1.assert)(c.pass, "".concat(c.label, " \u2014 ").concat(c.detail), c.label);
378
+ }
379
+ return [3 /*break*/, 66];
380
+ case 47:
381
+ _f = 0, enduserIds_1 = enduserIds;
382
+ _y.label = 48;
383
+ case 48:
384
+ if (!(_f < enduserIds_1.length)) return [3 /*break*/, 53];
385
+ id = enduserIds_1[_f];
386
+ _y.label = 49;
387
+ case 49:
388
+ _y.trys.push([49, 51, , 52]);
389
+ return [4 /*yield*/, sdk.api.endusers.deleteOne(id)];
390
+ case 50:
391
+ _y.sent();
392
+ return [3 /*break*/, 52];
393
+ case 51:
394
+ _g = _y.sent();
395
+ return [3 /*break*/, 52];
396
+ case 52:
397
+ _f++;
398
+ return [3 /*break*/, 48];
399
+ case 53:
400
+ _h = 0, userIds_1 = userIds;
401
+ _y.label = 54;
402
+ case 54:
403
+ if (!(_h < userIds_1.length)) return [3 /*break*/, 59];
404
+ id = userIds_1[_h];
405
+ _y.label = 55;
406
+ case 55:
407
+ _y.trys.push([55, 57, , 58]);
408
+ return [4 /*yield*/, sdk.api.users.deleteOne(id)];
409
+ case 56:
410
+ _y.sent();
411
+ return [3 /*break*/, 58];
412
+ case 57:
413
+ _j = _y.sent();
414
+ return [3 /*break*/, 58];
415
+ case 58:
416
+ _h++;
417
+ return [3 /*break*/, 54];
418
+ case 59:
419
+ _k = 0, roleIds_1 = roleIds;
420
+ _y.label = 60;
421
+ case 60:
422
+ if (!(_k < roleIds_1.length)) return [3 /*break*/, 65];
423
+ id = roleIds_1[_k];
424
+ _y.label = 61;
425
+ case 61:
426
+ _y.trys.push([61, 63, , 64]);
427
+ return [4 /*yield*/, sdk.api.role_based_access_permissions.deleteOne(id)];
428
+ case 62:
429
+ _y.sent();
430
+ return [3 /*break*/, 64];
431
+ case 63:
432
+ _l = _y.sent();
433
+ return [3 /*break*/, 64];
434
+ case 64:
435
+ _k++;
436
+ return [3 /*break*/, 60];
437
+ case 65: return [7 /*endfinally*/];
438
+ case 66: return [2 /*return*/];
439
+ }
440
+ });
441
+ });
442
+ };
443
+ exports.readonly_enduser_preview_tests = readonly_enduser_preview_tests;
444
+ // Allow running this test file independently
445
+ if (require.main === module) {
446
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
447
+ var sdk_2 = new sdk_1.Session({ host: host });
448
+ var sdkNonAdmin_1 = new sdk_1.Session({ host: host });
449
+ var runTests = function () { return __awaiter(void 0, void 0, void 0, function () {
450
+ return __generator(this, function (_a) {
451
+ switch (_a.label) {
452
+ case 0: return [4 /*yield*/, (0, setup_1.setup_tests)(sdk_2, sdkNonAdmin_1)];
453
+ case 1:
454
+ _a.sent();
455
+ return [4 /*yield*/, (0, exports.readonly_enduser_preview_tests)({ sdk: sdk_2, sdkNonAdmin: sdkNonAdmin_1 })];
456
+ case 2:
457
+ _a.sent();
458
+ return [2 /*return*/];
459
+ }
460
+ });
461
+ }); };
462
+ runTests()
463
+ .then(function () {
464
+ console.log("✅ F-0138 portal-preview scope test suite completed successfully");
465
+ process.exit(0);
466
+ })
467
+ .catch(function (error) {
468
+ console.error("❌ F-0138 portal-preview scope test suite failed:", error);
469
+ process.exit(1);
470
+ });
471
+ }
472
+ //# sourceMappingURL=readonly-enduser-preview.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readonly-enduser-preview.test.js","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/readonly-enduser-preview.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { Session } from "../../../sdk";
2
+ /**
3
+ * Security regressions for the generateSuggestedReply journey step.
4
+ *
5
+ * `suggestedReply` holds a staff-facing draft that may be AI-generated and unreviewed, built from the
6
+ * patient's own chart context. Four defects were fixed:
7
+ *
8
+ * 1. emails / sms_messages / group_mms_conversations `suggestedReply` had no `redactions: ['enduser']`
9
+ * (only the sibling `suggestedReplyIsAIGenerated` flag did, so content leaked but the "machine
10
+ * generated" marker was stripped).
11
+ * 2. POST /v1/bulk-actions/read admitted enduser sessions via schema.users.enduserActions.readMany but
12
+ * never checked the REQUESTED model's enduserActions — so any model in the enduser query map was
13
+ * readable, regardless of whether it grants endusers read.
14
+ * 3. chat_rooms.mark_read spread the whole room with no applyRedactions, bypassing the redaction that
15
+ * *was* declared. The patient portal calls mark_read on every chat view.
16
+ * 4. generateSuggestedReply accepted an OUTBOUND message as its target (journeyContext carries outbound
17
+ * ids for the Message Delivery Failure and Message Link Clicked triggers).
18
+ *
19
+ * NO BEDROCK: drafts are written directly with a staff updateOne, and the fix-4 case returns from the
20
+ * inbound guard before the context load / Bedrock call are reached. Deterministic, consumes no AI credits.
21
+ */
22
+ export declare const suggested_reply_redaction_tests: ({ sdk }: {
23
+ sdk: Session;
24
+ sdkNonAdmin: Session;
25
+ }) => Promise<void>;
26
+ //# sourceMappingURL=suggested-reply-redaction.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggested-reply-redaction.test.d.ts","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/suggested-reply-redaction.test.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAStC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,+BAA+B;SAA2B,OAAO;iBAAe,OAAO;mBAmLnG,CAAA"}