@7365admin1/core 3.53.3 → 3.53.5

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@7365admin1/core",
3
3
  "license": "MIT",
4
- "version": "3.53.3",
4
+ "version": "3.53.5",
5
5
  "author": "7365admin1",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -106,6 +106,12 @@ test("the two service-provider invites still resolve their caller themselves", (
106
106
  for (const source of [verificationService, verificationServiceV2]) {
107
107
  const body = fn(source, "createServiceProviderInvite");
108
108
  assert.match(body, /resolveInviteActor\(invitedBy\)/);
109
- assert.match(body, /actor\.isSuperAdmin[\s\S]{0,80}actor\.orgIds\.includes\(orgId\)/);
109
+ assert.match(
110
+ body,
111
+ // Widened to the property this pinned — a staff clause resolved from the
112
+ // session, ahead of the caller's own orgs. The staff clause is now the
113
+ // cross-client grant (`staffMayBypass`), not identity alone.
114
+ /staffMayBypass\(actor\)[\s\S]{0,80}actor\.orgIds\.includes\(orgId\)/,
115
+ );
110
116
  }
111
117
  });
@@ -103,7 +103,10 @@ test("the membership is authorised before it is written", () => {
103
103
 
104
104
  test("reach is staff, or membership, or an invitation — and nothing else", () => {
105
105
  const body = fn(authz, "requireOrgReach");
106
- assert.match(body, /if \(actor\.isSuperAdmin\) return id;/);
106
+ // The staff road is now the cross-client GRANT, not identity alone
107
+ // (`staffMayBypass`) — still one road, asked one notch harder. Widened to the
108
+ // property this pinned, never weakened: a staff clause must still be first.
109
+ assert.match(body, /if \(staffMayBypass\(actor\)\) return id;/);
107
110
  assert.match(body, /actor\.orgIds\.includes\(org\)/);
108
111
  assert.match(body, /await hasOrgInvitation\(id, org\)/);
109
112
  assert.match(body, /throw new UnauthorizedError\("Not authorized\."\)/);
@@ -0,0 +1,117 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { readFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ /**
7
+ * SP Approvals — the console module, on the server.
8
+ *
9
+ * `service-provider-invite.service.ts assertSuperAdmin` answers "is this
10
+ * Seven365 staff" and nothing else, so a staff role ticked for Promo Codes
11
+ * alone could approve or reject any client's service-provider application. The
12
+ * four staff-only handlers now also ask which console module the role holds.
13
+ *
14
+ * The decision reads `members`/`roles` and cannot run without Atlas, so what is
15
+ * asserted here is the WIRING, off the source: that each of the four asks, that
16
+ * it asks BEFORE it acts, that the strings it asks for exist in the catalogue,
17
+ * and — the part that matters later — that the SIX org-side handlers still ask
18
+ * nothing, because a property manager reaches those holding no console module.
19
+ */
20
+
21
+ const controller = readFileSync(
22
+ fileURLToPath(
23
+ new URL(
24
+ "../src/controllers/service-provider-invite.controller.ts",
25
+ import.meta.url,
26
+ ),
27
+ ),
28
+ "utf8",
29
+ );
30
+
31
+ /** The body of one `async function <name>(` in the controller. */
32
+ const handler = (name) => {
33
+ const start = controller.indexOf(`async function ${name}(`);
34
+ assert.notEqual(start, -1, `handler ${name} is gone — rename or removal`);
35
+ const next = controller.indexOf("\n async function ", start + 1);
36
+ return controller.slice(start, next === -1 ? controller.length : next);
37
+ };
38
+
39
+ const GATED = {
40
+ approvals: ["sp-approvals", "see-all-sp-approvals"],
41
+ pendingApprovalCount: ["sp-approvals", "see-all-sp-approvals"],
42
+ approve: ["sp-approvals", "approve-sp"],
43
+ reject: ["sp-approvals", "reject-sp"],
44
+ };
45
+
46
+ // The org road. `assertOwnerOrSuperAdmin` lets a property manager act on their
47
+ // own organisation's invitation, so a console module here would lock them out.
48
+ const UNGATED = ["list", "cancel", "resend", "remove", "decline", "getOne"];
49
+
50
+ test("the four staff-only handlers ask for their console module", () => {
51
+ for (const [name, [resource, action]] of Object.entries(GATED)) {
52
+ const body = handler(name);
53
+ assert.ok(
54
+ body.includes("requireConsolePermission"),
55
+ `${name} does not ask at all`,
56
+ );
57
+ assert.ok(body.includes(`"${resource}"`), `${name} misses ${resource}`);
58
+ assert.ok(body.includes(`"${action}"`), `${name} misses ${action}`);
59
+ }
60
+ });
61
+
62
+ test("it asks before it acts", () => {
63
+ for (const name of Object.keys(GATED)) {
64
+ const body = handler(name);
65
+ const asks = body.indexOf("requireConsolePermission");
66
+ const acts = body.indexOf("res.json(");
67
+ assert.ok(asks !== -1 && acts !== -1, `${name} lost a marker`);
68
+ assert.ok(asks < acts, `${name} acts before it asks`);
69
+ }
70
+ });
71
+
72
+ test("every string asked for is one the role editor can grant", async () => {
73
+ const { CONSOLE_PERMISSIONS, consoleRoleAllows } = await import(
74
+ "./.build/utils/console-permission.util.mjs"
75
+ );
76
+
77
+ for (const [resource, action] of Object.values(GATED)) {
78
+ assert.ok(
79
+ CONSOLE_PERMISSIONS[resource]?.includes(action),
80
+ `${resource}:${action} is not in the console catalogue — ungrantable`,
81
+ );
82
+ // A governed role holding the string passes; one holding a sibling module
83
+ // does not. This is the control: without it the test would pass on an
84
+ // always-true gate.
85
+ assert.equal(
86
+ consoleRoleAllows([`${resource}:${action}`], resource, action),
87
+ true,
88
+ );
89
+ assert.equal(
90
+ consoleRoleAllows(["promo-codes:create-promo-code"], resource, action),
91
+ false,
92
+ );
93
+ // The empty list and "*" still reach everything — no role document is
94
+ // written for this to ship.
95
+ assert.equal(consoleRoleAllows([], resource, action), true);
96
+ assert.equal(consoleRoleAllows(["*"], resource, action), true);
97
+ }
98
+ });
99
+
100
+ test("the org-side handlers stay open to a property manager", () => {
101
+ for (const name of UNGATED) {
102
+ assert.ok(
103
+ !handler(name).includes("requireConsolePermission"),
104
+ `${name} now demands a console module — that locks out property managers`,
105
+ );
106
+ }
107
+ });
108
+
109
+ test("the console gate is exported for API-core to reuse", () => {
110
+ const index = readFileSync(
111
+ fileURLToPath(new URL("../src/index.ts", import.meta.url)),
112
+ "utf8",
113
+ );
114
+ assert.ok(index.includes("requireConsolePermission,"));
115
+ assert.ok(index.includes('./utils/console-authz.util"'));
116
+ assert.ok(index.includes('export * from "./utils/console-permission.util";'));
117
+ });
@@ -0,0 +1,38 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ const { CROSS_CLIENT_GRANT, staffMayBypass } = await import(
5
+ "./.build/utils/console-permission.util.mjs"
6
+ );
7
+
8
+ /**
9
+ * The bypass is what let platform identity alone skip tenant scoping. These
10
+ * pin the ZERO-LOCKOUT property: every spelling a real staff role carries today
11
+ * still bypasses, so nobody loses access on deploy.
12
+ */
13
+ const staff = (permissions) => ({ isSuperAdmin: true, staffPermissions: permissions });
14
+
15
+ test("the two spellings a live staff role carries today still bypass", () => {
16
+ // `createDefaultUser` seeds `permissions: []`; the live staff account holds ["*"].
17
+ assert.equal(staffMayBypass(staff([])), true, "empty list must mean everything");
18
+ assert.equal(staffMayBypass(staff(["*"])), true, "wildcard must mean everything");
19
+ });
20
+
21
+ test("a partial staff role is governed, and the grant is a real catalogue string", () => {
22
+ assert.equal(staffMayBypass(staff(["promo-codes:create-promo-code"])), false);
23
+ assert.equal(staffMayBypass(staff(["organizations:see-all-organizations"])), true);
24
+ assert.equal(staffMayBypass(staff(["organizations:see-organization-details"])), true);
25
+ });
26
+
27
+ test("a non-staff caller never bypasses, however their list reads", () => {
28
+ assert.equal(staffMayBypass({ isSuperAdmin: false, staffPermissions: [] }), false);
29
+ assert.equal(staffMayBypass({ isSuperAdmin: false, staffPermissions: ["*"] }), false);
30
+ assert.equal(staffMayBypass({ isSuperAdmin: false }), false);
31
+ });
32
+
33
+ test("the grant is the organizations module — the control must not pass", () => {
34
+ assert.equal(CROSS_CLIENT_GRANT.resource, "organizations");
35
+ // Control: a resource that is NOT the grant must be refused for a partial
36
+ // role, or this test would pass against any implementation.
37
+ assert.equal(staffMayBypass(staff(["users:see-all-users"])), false);
38
+ });