@7365admin1/core 3.53.1 → 3.53.3

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.
@@ -47,7 +47,7 @@ test("the type=admin bypass is closed, and closed to staff only", () => {
47
47
 
48
48
  assert.match(
49
49
  body,
50
- /if \(type === "admin"\)[\s\S]{0,120}await requirePlatformStaff\(req\)/,
50
+ /if \(type === "admin"\)[\s\S]{0,120}await requireConsolePermission\(req/,
51
51
  "type=admin must be staff-only, not merely allowed through",
52
52
  );
53
53
  });
@@ -68,14 +68,14 @@ test("asking about your own memberships stays open; about somebody else's does n
68
68
  const body = fn(controller, "getAll");
69
69
  assert.match(
70
70
  body,
71
- /else if \(user && user !== callerId\(req\)\)[\s\S]{0,600}await requirePlatformStaff\(req\)/,
71
+ /else if \(user && user !== callerId\(req\)\)[\s\S]{0,600}await requireConsolePermission\(req/,
72
72
  );
73
73
  });
74
74
 
75
75
  test("the scope decision happens before the query runs", () => {
76
76
  const body = fn(controller, "getAll");
77
77
  const gate = Math.min(
78
- ...["requirePlatformStaff", "requireOrgAccess"]
78
+ ...["requireConsolePermission", "requireOrgAccess"]
79
79
  .map((s) => body.indexOf(s))
80
80
  .filter((i) => i !== -1),
81
81
  );
@@ -107,6 +107,6 @@ test("a membership with no organisation is platform-staff only", () => {
107
107
  // read as protection while passing everyone — and it would pass them on
108
108
  // precisely the most privileged row in the collection.
109
109
  const helper = controller.slice(controller.indexOf("async function requireMemberOrg"));
110
- assert.match(helper.slice(0, 500), /if \(!orgId\)[\s\S]{0,120}requirePlatformStaff\(req\)/);
110
+ assert.match(helper.slice(0, 500), /if \(!orgId\)[\s\S]{0,120}requireConsolePermission\(req/);
111
111
  assert.match(helper.slice(0, 500), /requireOrgAccess\(req, orgId\)/);
112
112
  });
@@ -162,8 +162,8 @@ test("suspending and reactivating a client is OWNER-only, not staff", () => {
162
162
  assert.ok(at !== -1, "organization.controller no longer defines updateStatus");
163
163
  const body = ORG_CONTROLLER.slice(at);
164
164
 
165
- assert.match(body, /await requirePlatformOwner\(req\)/);
166
- const guard = body.indexOf("await requirePlatformOwner(req)");
165
+ assert.match(body, /await requirePlatformOwner\(req/);
166
+ const guard = body.search(/await requirePlatformOwner\(req/);
167
167
  const write = body.indexOf("await _updateStatusById(");
168
168
  assert.ok(guard !== -1 && write !== -1 && guard < write);
169
169
 
@@ -181,11 +181,26 @@ test("suspending and reactivating a client is OWNER-only, not staff", () => {
181
181
  test("the owner is told apart from ordinary staff by a marker no caller can set", () => {
182
182
  assert.match(SUPER_ADMIN, /export async function isPlatformOwner\(/);
183
183
 
184
- // The staff tier is unchanged: role type only.
184
+ // The staff tier is unchanged: role type only. Both tiers now read the SAME
185
+ // resolved role document (`resolveStaffRole`, added for the console module
186
+ // gate) instead of each running its own copy of the two queries — so the
187
+ // admin-type test is asserted where it now lives.
188
+ const resolver = fn(SUPER_ADMIN, "export async function resolveStaffRole(");
185
189
  const staff = fn(SUPER_ADMIN, "export async function isSuperAdmin(");
186
190
  const owner = fn(SUPER_ADMIN, "export async function isPlatformOwner(");
187
191
 
188
- assert.match(staff, /return role\?\.type === "admin";/);
192
+ assert.match(resolver, /role\?\.type === "admin"/);
193
+ assert.match(resolver, /type: "admin"/, "the staff MEMBERSHIP test is gone");
194
+ // `default` appears in the resolver's RETURN TYPE — it hands the field to
195
+ // `isPlatformOwner`. What must never appear is a test ON it: the resolver
196
+ // returns every staff role, and only the owner tier reads the marker.
197
+ assert.doesNotMatch(
198
+ resolver,
199
+ /default === true|default: true/,
200
+ "the shared resolver must not have quietly become the owner gate",
201
+ );
202
+
203
+ assert.match(staff, /resolveStaffRole\(userId\)\) !== null/);
189
204
  assert.doesNotMatch(
190
205
  staff,
191
206
  /default/,
@@ -193,8 +208,8 @@ test("the owner is told apart from ordinary staff by a marker no caller can set"
193
208
  );
194
209
 
195
210
  // The owner tier requires BOTH: an admin-typed role AND roles.default.
196
- assert.match(owner, /role\?\.type === "admin"/);
197
- assert.match(owner, /role\?\.default === true/);
211
+ assert.match(owner, /resolveStaffRole\(userId\)/);
212
+ assert.match(owner, /\.default === true/);
198
213
  });
199
214
 
200
215
  test("roles.default cannot be set through the role API", () => {
@@ -210,7 +225,7 @@ test("roles.default cannot be set through the role API", () => {
210
225
 
211
226
  test("the owner guard is beside the staff guard, not a second mechanism", () => {
212
227
  assert.match(CONSOLE_AUTHZ, /export async function requirePlatformOwner\(/);
213
- assert.match(CONSOLE_AUTHZ, /isPlatformOwner, isSuperAdmin/);
228
+ assert.match(CONSOLE_AUTHZ, /isPlatformOwner, resolveStaffRole/);
214
229
  // Same session-only resolution as the staff gate.
215
230
  assert.match(
216
231
  CONSOLE_AUTHZ,
@@ -46,7 +46,7 @@ test("the three 'my memberships' reads compare the URL id to the session", () =>
46
46
  const body = fn(member, name);
47
47
  assert.match(
48
48
  body,
49
- new RegExp(`requireSelfOrPlatformStaff\\(req, ${param}\\)`),
49
+ new RegExp(`requireSelfOrPlatformStaff\\(req, ${param}[,)]`),
50
50
  `${name} no longer scopes the user id in the URL to the caller`,
51
51
  );
52
52
  // The guard has to run BEFORE the read, not after it.
@@ -68,7 +68,7 @@ test("no org still means the org-less platform roles, not everybody's", () => {
68
68
  const body = fn(roleController, "getRoles");
69
69
  assert.doesNotMatch(
70
70
  body,
71
- /if \(!org\)[\s\S]{0,80}requirePlatformStaff/,
71
+ /if \(!org\)[\s\S]{0,80}requireConsolePermission/,
72
72
  "an org-less role list must stay reachable",
73
73
  );
74
74
  });
@@ -95,10 +95,10 @@ test("createRole refuses a platform role from a non-staff caller", () => {
95
95
  const body = fn(controller, "createRole");
96
96
  assert.match(
97
97
  body,
98
- /if \(payload\.type === PLATFORM_STAFF_ROLE_TYPE\)[\s\S]{0,200}await requirePlatformStaff\(req\)/,
98
+ /if \(payload\.type === PLATFORM_STAFF_ROLE_TYPE\)[\s\S]{0,200}await requireConsolePermission\(req/,
99
99
  );
100
100
 
101
- const gate = body.indexOf("requirePlatformStaff");
101
+ const gate = body.search(/requireConsolePermission\(/);
102
102
  const write = body.indexOf("_createRole(");
103
103
  assert.ok(gate !== -1 && write !== -1 && gate < write,
104
104
  "the caller is checked before the role is written");
@@ -108,7 +108,7 @@ test("/api/roles/v2 is gated too — the same hole with /v2 on the URL", () => {
108
108
  const body = fn(controllerV2, "createRole");
109
109
  assert.match(
110
110
  body,
111
- /if \(value\.type === PLATFORM_STAFF_ROLE_TYPE\)[\s\S]{0,200}await requirePlatformStaff\(req\)/,
111
+ /if \(value\.type === PLATFORM_STAFF_ROLE_TYPE\)[\s\S]{0,200}await requireConsolePermission\(req/,
112
112
  );
113
113
  });
114
114
 
@@ -159,7 +159,7 @@ test("an org-less role is platform-staff only", () => {
159
159
  // "no organisation to check, therefore allowed", the gate would be worse than
160
160
  // none — it would read as protection while passing everyone.
161
161
  const helper = controller.slice(controller.indexOf("async function requireRoleOrg"));
162
- assert.match(helper.slice(0, 500), /if \(!orgId\)[\s\S]{0,120}requirePlatformStaff\(req\)/);
162
+ assert.match(helper.slice(0, 500), /if \(!orgId\)[\s\S]{0,120}requireConsolePermission\(req/);
163
163
  assert.match(helper.slice(0, 500), /requireOrgAccess\(req, orgId\)/);
164
164
  });
165
165
 
@@ -93,7 +93,7 @@ function body(file, name) {
93
93
  */
94
94
  const GUARDED = [
95
95
  // [file, handler, guard, the call it protects]
96
- ["organization.controller.ts", "add", "requirePlatformStaff", "_add("],
96
+ ["organization.controller.ts", "add", "requireConsolePermission", "_add("],
97
97
  // REACH, not membership. Step 1 of the client onboarding wizard is this
98
98
  // write and the owner has no `members` row yet - theirs is created at the
99
99
  // last step. `requireOrgAccess` here 401'd every new client on the first
@@ -108,14 +108,14 @@ const GUARDED = [
108
108
  "requirePlatformOwner",
109
109
  "_updateStatusById(",
110
110
  ],
111
- ["organization-v2.controller.ts", "getAll", "requirePlatformStaff", "_getAll("],
111
+ ["organization-v2.controller.ts", "getAll", "requireConsolePermission", "_getAll("],
112
112
  [
113
113
  "organization-v2.controller.ts",
114
114
  "getOrganizationsWithSubscription",
115
- "requirePlatformStaff",
115
+ "requireConsolePermission",
116
116
  "_getOrganizationsWithSubscription(",
117
117
  ],
118
- ["subscription.controller.ts", "add", "requirePlatformStaff", "_add("],
118
+ ["subscription.controller.ts", "add", "requireConsolePermission", "_add("],
119
119
  [
120
120
  "subscription.controller.ts",
121
121
  "updateSubscriptionSeats",
@@ -128,66 +128,66 @@ const GUARDED = [
128
128
  [
129
129
  "subscription.controller.ts",
130
130
  "createConsoleSubscription",
131
- "requirePlatformStaff",
131
+ "requireConsolePermission",
132
132
  "_add(",
133
133
  ],
134
134
  [
135
135
  "subscription.controller.ts",
136
136
  "updateConsoleSubscription",
137
- "requirePlatformStaff",
137
+ "requireConsolePermission",
138
138
  "_updateConsoleFieldsById(",
139
139
  ],
140
140
  [
141
141
  "subscription.controller.ts",
142
142
  "getConsoleSubscription",
143
- "requirePlatformStaff",
143
+ "requireConsolePermission",
144
144
  "_getByOrgId(",
145
145
  ],
146
- ["subscription-plan.controller.ts", "add", "requirePlatformStaff", "_add("],
147
- ["subscription-plan.controller.ts", "update", "requirePlatformStaff", "_update("],
146
+ ["subscription-plan.controller.ts", "add", "requireConsolePermission", "_add("],
147
+ ["subscription-plan.controller.ts", "update", "requireConsolePermission", "_update("],
148
148
  [
149
149
  "subscription-plan.controller.ts",
150
150
  "updateStatus",
151
- "requirePlatformStaff",
151
+ "requireConsolePermission",
152
152
  "_updateStatusById(",
153
153
  ],
154
- ["promo-code.controller.ts", "add", "requirePlatformStaff", "_add("],
154
+ ["promo-code.controller.ts", "add", "requireConsolePermission", "_add("],
155
155
  // Editing, disabling and removing a code are the same power as minting one:
156
156
  // a discount is money off our own invoices whichever way it is written.
157
- ["promo-code.controller.ts", "update", "requirePlatformStaff", "_updateById("],
157
+ ["promo-code.controller.ts", "update", "requireConsolePermission", "_updateById("],
158
158
  [
159
159
  "promo-code.controller.ts",
160
160
  "updateStatus",
161
- "requirePlatformStaff",
161
+ "requireConsolePermission",
162
162
  "_updateStatusById(",
163
163
  ],
164
164
  [
165
165
  "promo-code.controller.ts",
166
166
  "remove",
167
- "requirePlatformStaff",
167
+ "requireConsolePermission",
168
168
  "_softDeleteById(",
169
169
  ],
170
- ["platform-terms.controller.ts", "add", "requirePlatformStaff", "_add("],
170
+ ["platform-terms.controller.ts", "add", "requireConsolePermission", "_add("],
171
171
  // The acceptance record as a list - every account on the platform and
172
172
  // whether it has consented. The fourth cross-tenant read, gated like the
173
173
  // others; the per-user `getStatus` below cannot answer it.
174
174
  [
175
175
  "platform-terms.controller.ts",
176
176
  "getAcceptance",
177
- "requirePlatformStaff",
177
+ "requireConsolePermission",
178
178
  "_listAcceptance(",
179
179
  ],
180
180
  // The console's own history. A read, and gated like the two cross-tenant
181
181
  // reads above: it is every client's commercial history in one list.
182
- ["console-audit.controller.ts", "getAll", "requirePlatformStaff", "_list("],
182
+ ["console-audit.controller.ts", "getAll", "requireConsolePermission", "_list("],
183
183
  // Every account on the platform in one list - not filtered by org or site,
184
184
  // so it spans every client's residents, guards and cleaners at once. The
185
185
  // third cross-tenant read, gated like the other two.
186
- ["user.controller.ts", "getUsers", "requirePlatformStaff", "_getUsers("],
186
+ ["user.controller.ts", "getUsers", "requireConsolePermission", "_getUsers("],
187
187
  // /api/users/v2 is a separate mount onto a separate controller with its own
188
188
  // copy of the same unfiltered list. Gating only v1 leaves the gate
189
189
  // bypassable by adding /v2 to the URL.
190
- ["user-v2.controller.ts", "getUsers", "requirePlatformStaff", "_getUsers("],
190
+ ["user-v2.controller.ts", "getUsers", "requireConsolePermission", "_getUsers("],
191
191
  // The user is named in the URL, so without a check any signed-in account
192
192
  // could read - or record - somebody else's acceptance by changing the id.
193
193
  [
@@ -310,7 +310,7 @@ test("platform Terms are signed by the session, never by the request body", () =
310
310
  "the author is back to being whatever the caller typed",
311
311
  );
312
312
 
313
- const guard = b.indexOf("await requirePlatformStaff(req)");
313
+ const guard = b.search(/await requireConsolePermission\(req/);
314
314
  assert.ok(guard !== -1 && guard < b.indexOf("createdBy:"));
315
315
  });
316
316
 
@@ -327,7 +327,7 @@ test("a promo code is removed, never deleted", () => {
327
327
  test("the code a staff member removed is attributed to them, not to the body", () => {
328
328
  const b = body("promo-code.controller.ts", "remove");
329
329
 
330
- assert.match(b, /const staffId = await requirePlatformStaff\(req\)/);
330
+ assert.match(b, /const staffId = await requireConsolePermission\(req, "promo-codes", "delete-promo-code"\)/);
331
331
  assert.match(b, /_softDeleteById\(req\.params\.id, staffId\)/);
332
332
  });
333
333
 
@@ -365,14 +365,14 @@ test("the customer checkout is NOT staff-gated", () => {
365
365
  // purpose; this test exists so nobody closes it by reflex in a later sweep.
366
366
  const b = body("subscription.controller.ts", "createOrgSubscription");
367
367
 
368
- assert.doesNotMatch(b, /requirePlatformStaff/);
368
+ assert.doesNotMatch(b, /requirePlatformStaff|requireConsolePermission/);
369
369
  assert.doesNotMatch(b, /requireOrgAccess/);
370
370
  });
371
371
 
372
372
  test("self-signup can still create its organisation", () => {
373
373
  const b = body("organization.controller.ts", "addOnboardingOrg");
374
374
 
375
- assert.doesNotMatch(b, /requirePlatformStaff/);
375
+ assert.doesNotMatch(b, /requirePlatformStaff|requireConsolePermission/);
376
376
  assert.doesNotMatch(b, /requireOrgAccess/);
377
377
  });
378
378
 
@@ -396,7 +396,7 @@ test("gating the platform-wide user list does not gate a person's own profile",
396
396
  for (const file of ALSO_READ) {
397
397
  assert.doesNotMatch(
398
398
  body(file, name),
399
- /requirePlatformStaff|requirePlatformOwner/,
399
+ /requirePlatformStaff|requirePlatformOwner|requireConsolePermission/,
400
400
  `${file} ${name} is staff-gated — that locks users out of their own ` +
401
401
  `account. Only the platform-wide list is a staff read.`,
402
402
  );
@@ -414,7 +414,7 @@ test("no new endpoint arrives on this surface without a decision", () => {
414
414
  assert.ok(
415
415
  guarded.includes(name) || open.includes(name),
416
416
  `${file} ${name} is new and is in neither list — gate it with ` +
417
- `requirePlatformStaff/requireOrgAccess, or add it to OPEN_BY_DESIGN ` +
417
+ `requireConsolePermission/requireOrgAccess, or add it to OPEN_BY_DESIGN ` +
418
418
  `with the reason it stays open`,
419
419
  );
420
420
  }
@@ -47,11 +47,11 @@ test("the acceptance list is staff-gated, from the session, before it reads", ()
47
47
 
48
48
  assert.match(
49
49
  handler,
50
- /await requirePlatformStaff\(req\)/,
50
+ /await requireConsolePermission\(req, "platform-terms", "see-platform-terms"\)/,
51
51
  "the acceptance list is not behind the platform staff gate",
52
52
  );
53
53
 
54
- const gate = handler.indexOf("requirePlatformStaff(req)");
54
+ const gate = handler.search(/requireConsolePermission\(req/);
55
55
  const reads = handler.indexOf("_listAcceptance(");
56
56
 
57
57
  assert.ok(reads !== -1, "getAcceptance no longer reads the list");