@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.
@@ -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
- const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
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;