@7365admin1/core 3.64.5-staging.304 → 3.64.5-staging.306
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/.changeset/module-gate-kill-switch-and-read.md +26 -0
- package/.changeset/people-create-reach-gate.md +31 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/module-access.test.mjs +27 -0
- package/test/module-gate-kill-switch.test.mjs +48 -0
- package/test/people-create-gate.test.mjs +71 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Module gate: the kill switch now stops the 409, and the check stops reading when there is nothing to decide
|
|
6
|
+
|
|
7
|
+
Two follow-ups from the module-gate server review, both behaviour-neutral for
|
|
8
|
+
anybody who is not using the kill switch.
|
|
9
|
+
|
|
10
|
+
**`MODULE_LIST_GATE=off` now means off.** Every other part of the gate checked the
|
|
11
|
+
master switch; `mustAcknowledge` did not. So with the gate switched off, a save that
|
|
12
|
+
narrowed a client's module list was still refused with a 409 "acknowledge the impact
|
|
13
|
+
first" — the one lever a lead would reach for in an emergency, not working for the
|
|
14
|
+
one path that blocks a save from going through. Both the organisation and the site
|
|
15
|
+
save paths go through that function, so both are fixed. With the switch at its
|
|
16
|
+
default the 409 behaves exactly as before, and that is asserted.
|
|
17
|
+
|
|
18
|
+
**`moduleDeniedForCaller` no longer reads memberships when no list is enforced.** It
|
|
19
|
+
runs on every governed request and read the caller's memberships before finding out
|
|
20
|
+
whether there was anything to decide — and `members.user` has no index to use. It now
|
|
21
|
+
returns early unless the site or its organisation has an enforced list, which is what
|
|
22
|
+
the notification filter already did. No verdict changes: a client that really has an
|
|
23
|
+
enforced list is still measured exactly as before, and that is asserted too.
|
|
24
|
+
|
|
25
|
+
Also corrects the 3.65.0 changelog, which claimed `MODULE_LIST_GATE` switched all of
|
|
26
|
+
the gate off at once. It did not, for the reason above.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
POST /api/people: count cross-client person creation (gate ships switched off)
|
|
6
|
+
|
|
7
|
+
The signed-in branch of `POST /api/people` checked only that somebody was signed
|
|
8
|
+
in. The site and organisation came straight from the request body, and where that
|
|
9
|
+
body carries an e-mail, a password and a resident/tenant type, the service goes on
|
|
10
|
+
to create a **user account** and an **active membership** as well as the person row.
|
|
11
|
+
|
|
12
|
+
`site.people` is the collection every site-reach decision on the platform reads its
|
|
13
|
+
verdict from, so a row written there is not an ordinary record — it manufactures
|
|
14
|
+
reach into another client's estate.
|
|
15
|
+
|
|
16
|
+
This adds the site-reach check the camera, HID and people-list paths already use,
|
|
17
|
+
behind `PEOPLE_CREATE_REACH_MODE`, and **ships it in `log` mode**: it records what
|
|
18
|
+
it would refuse and refuses nothing. `enforce` refuses; `off` does not look. Any
|
|
19
|
+
unexpected error allows the request — this must never be why a real registration
|
|
20
|
+
fails.
|
|
21
|
+
|
|
22
|
+
It ships off because of RP-5. A person row carries no `createdBy`, so who created
|
|
23
|
+
the existing rows cannot be established retrospectively and the true refusal count
|
|
24
|
+
is only knowable from live traffic. Measured read-only on staging: 372
|
|
25
|
+
`site.people` rows, of which exactly **1** is self-signup shaped (`platform:
|
|
26
|
+
"mobile"`, `status: "pending"`) and 371 were written by a signed-in caller, across
|
|
27
|
+
3 of 256 sites.
|
|
28
|
+
|
|
29
|
+
Pre-login self-signup is untouched in every mode: a request with no session takes
|
|
30
|
+
the other branch of `add` before this runs, and that route is already constrained
|
|
31
|
+
by its own allow-list schema with `status` and `platform` pinned server-side.
|
package/dist/index.js
CHANGED
|
@@ -15855,6 +15855,8 @@ function deniedForMembership({
|
|
|
15855
15855
|
return { denied, since: [orgTier.since, siteTier.since].sort().pop() ?? "" };
|
|
15856
15856
|
}
|
|
15857
15857
|
function mustAcknowledge(preview, acknowledged) {
|
|
15858
|
+
if (!moduleListGateOn())
|
|
15859
|
+
return false;
|
|
15858
15860
|
if (acknowledged === true || !preview.newlyDenied.length)
|
|
15859
15861
|
return false;
|
|
15860
15862
|
return !preview.impact || preview.impact.memberCount > 0;
|
|
@@ -62476,6 +62478,21 @@ var import_joi72 = __toESM(require("joi"));
|
|
|
62476
62478
|
var import_node_server_utils148 = require("@7365admin1/node-server-utils");
|
|
62477
62479
|
var import_node_server_utils149 = require("@7365admin1/node-server-utils");
|
|
62478
62480
|
|
|
62481
|
+
// src/utils/people-create-gate.util.ts
|
|
62482
|
+
var PEOPLE_CREATE_REACH_MODE_DEFAULT = "log";
|
|
62483
|
+
function peopleCreateReachMode(env2 = process.env) {
|
|
62484
|
+
return gateMode(
|
|
62485
|
+
env2.PEOPLE_CREATE_REACH_MODE,
|
|
62486
|
+
["log", "enforce", "off"],
|
|
62487
|
+
PEOPLE_CREATE_REACH_MODE_DEFAULT
|
|
62488
|
+
);
|
|
62489
|
+
}
|
|
62490
|
+
function peopleCreateVerdict(params) {
|
|
62491
|
+
if (params.mode === "off" || params.reachable)
|
|
62492
|
+
return { refuse: false, record: false };
|
|
62493
|
+
return { refuse: params.mode === "enforce", record: true };
|
|
62494
|
+
}
|
|
62495
|
+
|
|
62479
62496
|
// src/utils/self-signup-files.util.ts
|
|
62480
62497
|
function dropUnsentFiles(body) {
|
|
62481
62498
|
if (!Array.isArray(body?.files))
|
|
@@ -62585,6 +62602,41 @@ function usePersonController() {
|
|
|
62585
62602
|
next(new import_node_server_utils148.BadRequestError(messages));
|
|
62586
62603
|
return;
|
|
62587
62604
|
}
|
|
62605
|
+
try {
|
|
62606
|
+
const mode = peopleCreateReachMode();
|
|
62607
|
+
if (mode !== "off") {
|
|
62608
|
+
const actor = await resolveInviteActor(callerId(req));
|
|
62609
|
+
let reachable = true;
|
|
62610
|
+
if (!staffMayBypass(actor)) {
|
|
62611
|
+
const site = payload?.site ? String(payload.site) : "";
|
|
62612
|
+
const org = payload?.org ? String(payload.org) : "";
|
|
62613
|
+
if (site) {
|
|
62614
|
+
reachable = await useCameraViewService().entitleSite({ siteId: site, userId: actor.id }).then(
|
|
62615
|
+
() => true,
|
|
62616
|
+
() => false
|
|
62617
|
+
);
|
|
62618
|
+
} else if (org) {
|
|
62619
|
+
reachable = actor.orgIds.includes(org);
|
|
62620
|
+
}
|
|
62621
|
+
}
|
|
62622
|
+
const verdict = peopleCreateVerdict({ mode, reachable });
|
|
62623
|
+
if (verdict.record) {
|
|
62624
|
+
import_node_server_utils148.logger.log({
|
|
62625
|
+
level: "info",
|
|
62626
|
+
message: `[people-create] ${verdict.refuse ? "refused" : "would-refuse"} caller=${actor.id} site=${payload?.site ?? ""} org=${payload?.org ?? ""} mode=${mode}`
|
|
62627
|
+
});
|
|
62628
|
+
}
|
|
62629
|
+
if (verdict.refuse) {
|
|
62630
|
+
next(new import_node_server_utils149.UnauthorizedError("Not authorized."));
|
|
62631
|
+
return;
|
|
62632
|
+
}
|
|
62633
|
+
}
|
|
62634
|
+
} catch (gateError) {
|
|
62635
|
+
import_node_server_utils148.logger.log({
|
|
62636
|
+
level: "warn",
|
|
62637
|
+
message: `[people-create] reach check failed open: ${gateError?.message}`
|
|
62638
|
+
});
|
|
62639
|
+
}
|
|
62588
62640
|
try {
|
|
62589
62641
|
const data = await _add(payload);
|
|
62590
62642
|
res.status(201).json({ message: data });
|
|
@@ -96883,6 +96935,8 @@ async function moduleDeniedForCaller({
|
|
|
96883
96935
|
if (!user || !siteId)
|
|
96884
96936
|
return ALLOW;
|
|
96885
96937
|
const siteDoc = typeof site === "string" ? await data.site(siteId).catch(() => null) : site ?? null;
|
|
96938
|
+
if (!await siteHasEnforcedList(siteDoc, data))
|
|
96939
|
+
return ALLOW;
|
|
96886
96940
|
const gate = await gateAtSite(await data.membershipsOf([user]), siteDoc, siteId, data);
|
|
96887
96941
|
if (gate.exempt)
|
|
96888
96942
|
return ALLOW;
|