@7365admin1/core 3.47.1-staging.142 → 3.47.1-staging.143
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/person-write-scope.md +38 -0
- package/dist/index.js +25 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +108 -89
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +5 -1
- package/test/e2e/person-write-scope.e2e.test.mjs +336 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the resident and visitor WRITES to a site the caller can actually reach.
|
|
6
|
+
|
|
7
|
+
`PUT /api/people/id/:id` (edit), `PUT /api/people/:id` (delete),
|
|
8
|
+
`PATCH /api/people/:id` (approve / reject / ask to resubmit) and the exported
|
|
9
|
+
`suspendResidentById` identify the person by an id in the URL and carried
|
|
10
|
+
`requireAuth` and nothing more. Any signed-in account on the platform — a
|
|
11
|
+
resident, a cleaner, a guard at a different estate — could rename, delete,
|
|
12
|
+
approve or reject any other client's resident. #1907 closed the read
|
|
13
|
+
(`GET /api/people`); these are the writes behind it.
|
|
14
|
+
|
|
15
|
+
The scope is resolved from the **stored record**, never from the request, and
|
|
16
|
+
that is not a stylistic choice: **not one live caller sends a site.**
|
|
17
|
+
`layer-common usePeople.deleteById` sends no body at all,
|
|
18
|
+
`reviewResidentPerson` sends only `{ status, remarks }`, and both
|
|
19
|
+
`web-app-property-management pages/[org]/[site]/people-mgmt/index.vue:915-917`
|
|
20
|
+
and `components/PeopleFormMgmt.vue:618-620` explicitly `delete payload.site`
|
|
21
|
+
before calling `updateById`. A guard that read the site out of the body would
|
|
22
|
+
have refused every real caller and protected nobody.
|
|
23
|
+
|
|
24
|
+
The rule is the site-reach rule the camera, HID and people-list already use —
|
|
25
|
+
membership of the site, an org-wide membership at the site's owner, or an active
|
|
26
|
+
`customer.sites` engagement — so an agency contracted to an estate keeps
|
|
27
|
+
working. A record carrying an organisation but no site falls back to that
|
|
28
|
+
organisation. Seven365 staff are unchanged.
|
|
29
|
+
|
|
30
|
+
A resident resubmitting their own registration from the mobile app still works:
|
|
31
|
+
`person.service.ts:155-166` writes a `members` row pinned to the site at
|
|
32
|
+
sign-up, before any approval, so a rejected resident still reaches their own
|
|
33
|
+
site. There is an end-to-end case for exactly that.
|
|
34
|
+
|
|
35
|
+
Also: **who approved a resident now comes from the session.** It was read out of
|
|
36
|
+
the `user` cookie the browser sends, falling back to whatever `approvedBy` the
|
|
37
|
+
request body claimed, so an approval could be attributed to anybody. No caller
|
|
38
|
+
sends `approvedBy`, so nothing is lost.
|
package/dist/index.js
CHANGED
|
@@ -50338,7 +50338,8 @@ function usePersonController() {
|
|
|
50338
50338
|
getCompany: _getCompany,
|
|
50339
50339
|
getPeopleByPlateNumber: _getPeopleByPlateNumber,
|
|
50340
50340
|
getPeopleByNRIC: _getPeopleByNRIC,
|
|
50341
|
-
getByUserId: _getByUserId
|
|
50341
|
+
getByUserId: _getByUserId,
|
|
50342
|
+
getById: _getById
|
|
50342
50343
|
} = usePersonRepo();
|
|
50343
50344
|
const {
|
|
50344
50345
|
add: _add,
|
|
@@ -50346,6 +50347,24 @@ function usePersonController() {
|
|
|
50346
50347
|
reviewResidentPerson: _reviewResidentPerson,
|
|
50347
50348
|
suspendResidentById: _suspendResidentById
|
|
50348
50349
|
} = usePersonService();
|
|
50350
|
+
async function entitlePerson(req, _id) {
|
|
50351
|
+
const actor = await resolveInviteActor(callerId(req));
|
|
50352
|
+
if (actor.isSuperAdmin)
|
|
50353
|
+
return actor;
|
|
50354
|
+
const person = await _getById(_id);
|
|
50355
|
+
if (!person)
|
|
50356
|
+
throw new import_node_server_utils139.NotFoundError("Person not found.");
|
|
50357
|
+
const site = person.site?.toString() ?? "";
|
|
50358
|
+
if (site) {
|
|
50359
|
+
await useCameraViewService().entitleSite({ siteId: site, userId: actor.id });
|
|
50360
|
+
return actor;
|
|
50361
|
+
}
|
|
50362
|
+
const org = person.org?.toString() ?? "";
|
|
50363
|
+
if (!org || !actor.orgIds.includes(org)) {
|
|
50364
|
+
throw new import_node_server_utils139.UnauthorizedError("Not authorized.");
|
|
50365
|
+
}
|
|
50366
|
+
return actor;
|
|
50367
|
+
}
|
|
50349
50368
|
async function add(req, res, next) {
|
|
50350
50369
|
const payload = { ...req.body };
|
|
50351
50370
|
const { error } = schemaPerson.validate(payload, {
|
|
@@ -50479,6 +50498,7 @@ function usePersonController() {
|
|
|
50479
50498
|
return;
|
|
50480
50499
|
}
|
|
50481
50500
|
try {
|
|
50501
|
+
await entitlePerson(req, _id);
|
|
50482
50502
|
const result = await _updateById(_id, req.body);
|
|
50483
50503
|
res.status(200).json({ message: result });
|
|
50484
50504
|
return;
|
|
@@ -50498,6 +50518,7 @@ function usePersonController() {
|
|
|
50498
50518
|
return;
|
|
50499
50519
|
}
|
|
50500
50520
|
try {
|
|
50521
|
+
await entitlePerson(req, _id);
|
|
50501
50522
|
await _deleteById(_id);
|
|
50502
50523
|
res.status(200).json({ message: "Successfully deleted visitor guest." });
|
|
50503
50524
|
return;
|
|
@@ -50696,11 +50717,7 @@ function usePersonController() {
|
|
|
50696
50717
|
}
|
|
50697
50718
|
}
|
|
50698
50719
|
async function reviewResidentPerson(req, res, next) {
|
|
50699
|
-
|
|
50700
|
-
(acc, [key, value]) => ({ ...acc, [key]: value }),
|
|
50701
|
-
{}
|
|
50702
|
-
);
|
|
50703
|
-
req.body.approvedBy = cookies?.["user"] ? cookies["user"].toString() : req.body.approvedBy;
|
|
50720
|
+
req.body.approvedBy = callerId(req) || req.body.approvedBy;
|
|
50704
50721
|
const _id = req.params.id;
|
|
50705
50722
|
const payload = { _id, ...req.body };
|
|
50706
50723
|
const schema2 = import_joi68.default.object({
|
|
@@ -50717,6 +50734,7 @@ function usePersonController() {
|
|
|
50717
50734
|
return;
|
|
50718
50735
|
}
|
|
50719
50736
|
try {
|
|
50737
|
+
await entitlePerson(req, _id);
|
|
50720
50738
|
req.body.approvedBy = { id: req.body.approvedBy, name: "" };
|
|
50721
50739
|
const result = await _reviewResidentPerson(_id, req.body);
|
|
50722
50740
|
res.status(200).json({ message: result });
|
|
@@ -50738,6 +50756,7 @@ function usePersonController() {
|
|
|
50738
50756
|
return;
|
|
50739
50757
|
}
|
|
50740
50758
|
try {
|
|
50759
|
+
await entitlePerson(req, _id);
|
|
50741
50760
|
const result = await _suspendResidentById(_id);
|
|
50742
50761
|
res.status(200).json({ message: result });
|
|
50743
50762
|
return;
|