@7365admin1/core 3.64.5-staging.305 → 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/people-create-reach-gate.md +31 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/people-create-gate.test.mjs +71 -0
|
@@ -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
|
@@ -62478,6 +62478,21 @@ var import_joi72 = __toESM(require("joi"));
|
|
|
62478
62478
|
var import_node_server_utils148 = require("@7365admin1/node-server-utils");
|
|
62479
62479
|
var import_node_server_utils149 = require("@7365admin1/node-server-utils");
|
|
62480
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
|
+
|
|
62481
62496
|
// src/utils/self-signup-files.util.ts
|
|
62482
62497
|
function dropUnsentFiles(body) {
|
|
62483
62498
|
if (!Array.isArray(body?.files))
|
|
@@ -62587,6 +62602,41 @@ function usePersonController() {
|
|
|
62587
62602
|
next(new import_node_server_utils148.BadRequestError(messages));
|
|
62588
62603
|
return;
|
|
62589
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
|
+
}
|
|
62590
62640
|
try {
|
|
62591
62641
|
const data = await _add(payload);
|
|
62592
62642
|
res.status(201).json({ message: data });
|