@7365admin1/core 3.47.1-staging.143 → 3.47.1-staging.144

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,44 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the single-value people lookups to a site the caller can reach.
6
+
7
+ Seven endpoints on `/api/people` carried `requireAuth` and nothing else and were
8
+ not scoped at all:
9
+
10
+ | Route | What it answered |
11
+ |---|---|
12
+ | `GET /nric/:nric` | a `findOne` on NRIC across the **whole platform** |
13
+ | `GET /contact/:contact` | the same, on contact number |
14
+ | `GET /plateNumber/:plateNumber` | every person with that plate, anywhere |
15
+ | `GET /unit/:unit` | every person at that unit id |
16
+ | `GET /user/:userId` | the person record behind any user id |
17
+ | `GET /company` | company names aggregated across every client |
18
+ | `GET /all-nric` | a paged NRIC list at a caller-named site |
19
+
20
+ So any signed-in account could type an NRIC or a car plate and read the matching
21
+ resident of any estate — name, unit, contact number, plates. #1907 scoped the
22
+ paged list (`GET /api/people`); these are the lookups beside it.
23
+
24
+ None of them takes a site — `layer-common usePeople.findPersonByNRIC`,
25
+ `searchCompanyList` and `getPeopleByUnit` pass only the value being looked up —
26
+ so the scope is decided **after** the read, on the record, with the same
27
+ site-reach rule the camera, HID and people-list already use. A record the caller
28
+ cannot reach is left out, which for these endpoints is exactly what "no such
29
+ person" already looked like: `null`, or an empty list. **No client sees a new
30
+ response shape or a new status code.** `entitleSite` is asked once per distinct
31
+ site, not once per record.
32
+
33
+ `/all-nric` requires a site in the query, so that one is checked before the read
34
+ and answers `404` for a site the caller cannot reach — the same answer a site
35
+ that does not exist gives, so ids cannot be probed.
36
+
37
+ `/company` has no site or unit to scope on, so it is narrowed to the caller's
38
+ own organisations — the fallback `getAll` already uses. `person.repo.getCompany`
39
+ gains an optional `orgs` filter; absent means no restriction, which is what
40
+ Seven365 staff get.
41
+
42
+ `GET /user/:userId` also always answers about **your own** record, whatever your
43
+ memberships say: the resident app reads it during sign-up and when resubmitting
44
+ a rejected registration.
package/dist/index.d.ts CHANGED
@@ -4612,7 +4612,7 @@ declare function usePersonRepo(): {
4612
4612
  type?: ("resident" | "walk-in" | "drop-off" | "contractor" | "delivery" | "pick-up" | "guest" | "tenant")[] | undefined;
4613
4613
  unit?: string | undefined;
4614
4614
  }, session?: ClientSession) => Promise<TPerson[]>;
4615
- getCompany: (search?: string) => Promise<any[]>;
4615
+ getCompany: (search?: string, orgs?: string[]) => Promise<any[]>;
4616
4616
  getPeopleByPlateNumber: (plateNumber: string) => Promise<TPerson[]>;
4617
4617
  getPeopleByNRIC: ({ page, limit, nric, sort, site, }: {
4618
4618
  page?: number | undefined;
package/dist/index.js CHANGED
@@ -17735,7 +17735,7 @@ function usePersonRepo() {
17735
17735
  );
17736
17736
  }
17737
17737
  }
17738
- async function getCompany(search) {
17738
+ async function getCompany(search, orgs) {
17739
17739
  try {
17740
17740
  const cacheKey = (0, import_node_server_utils40.makeCacheKey)(site_people_namespace_collection, {
17741
17741
  company: search
@@ -17747,6 +17747,11 @@ function usePersonRepo() {
17747
17747
  if (search) {
17748
17748
  query2.companyName = { $regex: search, $options: "i" };
17749
17749
  }
17750
+ if (orgs) {
17751
+ query2.org = {
17752
+ $in: orgs.filter(import_mongodb36.ObjectId.isValid).map((o) => new import_mongodb36.ObjectId(o))
17753
+ };
17754
+ }
17750
17755
  let data = [];
17751
17756
  data = await collection.aggregate([
17752
17757
  { $match: query2 },
@@ -50365,6 +50370,38 @@ function usePersonController() {
50365
50370
  }
50366
50371
  return actor;
50367
50372
  }
50373
+ async function siteReach(req) {
50374
+ const actor = await resolveInviteActor(callerId(req));
50375
+ const reachable = /* @__PURE__ */ new Map();
50376
+ function canReach(person) {
50377
+ if (actor.isSuperAdmin)
50378
+ return Promise.resolve(true);
50379
+ if (!person)
50380
+ return Promise.resolve(false);
50381
+ const site = person.site?.toString() ?? "";
50382
+ if (!site) {
50383
+ const org = person.org?.toString() ?? "";
50384
+ return Promise.resolve(Boolean(org) && actor.orgIds.includes(org));
50385
+ }
50386
+ if (!reachable.has(site)) {
50387
+ reachable.set(
50388
+ site,
50389
+ useCameraViewService().entitleSite({ siteId: site, userId: actor.id }).then(
50390
+ () => true,
50391
+ () => false
50392
+ )
50393
+ );
50394
+ }
50395
+ return reachable.get(site);
50396
+ }
50397
+ async function only(people) {
50398
+ if (actor.isSuperAdmin)
50399
+ return people;
50400
+ const verdicts = await Promise.all(people.map((p) => canReach(p)));
50401
+ return people.filter((_, i) => verdicts[i]);
50402
+ }
50403
+ return { actor, canReach, only };
50404
+ }
50368
50405
  async function add(req, res, next) {
50369
50406
  const payload = { ...req.body };
50370
50407
  const { error } = schemaPerson.validate(payload, {
@@ -50539,7 +50576,8 @@ function usePersonController() {
50539
50576
  }
50540
50577
  try {
50541
50578
  const data = await _getByNRIC(nric);
50542
- res.json(data);
50579
+ const { canReach } = await siteReach(req);
50580
+ res.json(await canReach(data) ? data : null);
50543
50581
  return;
50544
50582
  } catch (error2) {
50545
50583
  import_node_server_utils138.logger.log({ level: "error", message: error2.message });
@@ -50558,7 +50596,8 @@ function usePersonController() {
50558
50596
  }
50559
50597
  try {
50560
50598
  const data = await _getPersonByPhoneNumber(contact);
50561
- res.json(data);
50599
+ const { canReach } = await siteReach(req);
50600
+ res.json(await canReach(data) ? data : null);
50562
50601
  return;
50563
50602
  } catch (error2) {
50564
50603
  import_node_server_utils138.logger.log({ level: "error", message: error2.message });
@@ -50602,7 +50641,8 @@ function usePersonController() {
50602
50641
  type,
50603
50642
  unit
50604
50643
  });
50605
- res.json(data);
50644
+ const { only } = await siteReach(req);
50645
+ res.json(await only(data));
50606
50646
  return;
50607
50647
  } catch (error) {
50608
50648
  import_node_server_utils138.logger.log({ level: "error", message: error.message });
@@ -50622,7 +50662,11 @@ function usePersonController() {
50622
50662
  }
50623
50663
  const { search } = value;
50624
50664
  try {
50625
- const data = await _getCompany(search);
50665
+ const { actor } = await siteReach(req);
50666
+ const data = await _getCompany(
50667
+ search,
50668
+ actor.isSuperAdmin ? void 0 : actor.orgIds
50669
+ );
50626
50670
  res.status(200).json(data);
50627
50671
  return;
50628
50672
  } catch (error2) {
@@ -50642,7 +50686,8 @@ function usePersonController() {
50642
50686
  }
50643
50687
  try {
50644
50688
  const data = await _getPeopleByPlateNumber(plateNumber);
50645
- res.json({ data });
50689
+ const { only } = await siteReach(req);
50690
+ res.json({ data: await only(data) });
50646
50691
  return;
50647
50692
  } catch (error2) {
50648
50693
  import_node_server_utils138.logger.log({ level: "error", message: error2.message });
@@ -50682,6 +50727,10 @@ function usePersonController() {
50682
50727
  }
50683
50728
  });
50684
50729
  try {
50730
+ const { actor } = await siteReach(req);
50731
+ if (!actor.isSuperAdmin) {
50732
+ await useCameraViewService().entitleSite({ siteId: site, userId: actor.id });
50733
+ }
50685
50734
  const data = await _getPeopleByNRIC({
50686
50735
  nric,
50687
50736
  page,
@@ -50708,7 +50757,9 @@ function usePersonController() {
50708
50757
  }
50709
50758
  try {
50710
50759
  const data = await _getByUserId(userId);
50711
- res.json(data);
50760
+ const { actor, canReach } = await siteReach(req);
50761
+ const isSelf = Boolean(actor.id) && data?.user?.toString() === actor.id;
50762
+ res.json(isSelf || await canReach(data) ? data : null);
50712
50763
  return;
50713
50764
  } catch (error2) {
50714
50765
  import_node_server_utils138.logger.log({ level: "error", message: error2.message });