@7365admin1/core 3.53.3 → 3.53.4
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/CHANGELOG.md +20 -0
- package/dist/index.d.ts +127 -1
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/sp-approvals-console-gate.test.mjs +117 -0
package/package.json
CHANGED
|
@@ -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
|
+
});
|