@7365admin1/core 3.47.1-staging.137 → 3.47.1-staging.138

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,48 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope `/api/customer-sites` to the organisations on the record.
6
+
7
+ Every route on this module except the pre-login invite acceptance carried
8
+ `requireAuth` and nothing more, and took the organisation straight out of the
9
+ query string or the body (`customer-site.controller.ts`). Two things followed
10
+ from that, and both are closed here.
11
+
12
+ `POST /api/customer-sites` is not merely an engagement record: it CREATES A SITE
13
+ inside whatever `siteOrg` the body names, and writes that site onto the
14
+ organisation as its default when it has none
15
+ (`customer-site.service.ts:77-111`). Any signed-in account on the platform — a
16
+ resident, a cleaner, a guard — could therefore plant a site inside any client.
17
+
18
+ A `customer.sites` row is also the cross-organisation access grant itself: the
19
+ "engaged agency" branch of `resolveSiteAccess` reads it to decide who may reach
20
+ a site's cameras, HID readers and residents
21
+ (`camera-view.service.ts:325-341`). Being able to write, edit or delete those
22
+ rows by id was being able to grant yourself reach into another client's estate,
23
+ or revoke a contractor's.
24
+
25
+ The caller is now resolved from the session and the decision made before
26
+ anything is read or written:
27
+
28
+ - `POST /` requires `requireOrgAccess` on the body's `org`, and on `siteOrg`
29
+ too when it names a different organisation — because that is where the new
30
+ site lands. The Add / Edit Site form sends the same id for both
31
+ (`web-app-org components/SiteForm.vue:397-402`).
32
+ - `GET /` requires `requireOrgAccess` on the `org` filter the caller sends.
33
+ - `GET /:id`, `PUT /:id` and `DELETE /:id` load the row first and decide from
34
+ the row's own `org` and `siteOrg` — Seven365 staff, or a live member of
35
+ EITHER side of the engagement. Either, not one: the estate's manager reaches
36
+ the row through `siteOrg` and the contracted agency reaches the same row
37
+ through `org`, so scoping to one side would break the other's screen.
38
+ - `GET /service-provider/:id` takes a SITE id, and uses `entitleSite` — the
39
+ same site-reach rule the camera, HID and people modules already use, so an
40
+ agency contracted to a site still lists who else works there.
41
+
42
+ `POST /invite/:id` is unchanged and still reachable with no session: an invited
43
+ organisation accepts before it has an account (`web-app-main pages/sign-in.vue:182`).
44
+
45
+ No new authorization mechanism is introduced. `requireOrgAccess` and
46
+ `entitleSite` are the helpers the member, role, people, camera and HID paths
47
+ already use, and the "either side" test is `resolveInviteActor` asked about two
48
+ organisations in one round trip rather than two.
package/dist/index.js CHANGED
@@ -39607,6 +39607,15 @@ function useCustomerSiteService() {
39607
39607
  // src/controllers/customer-site.controller.ts
39608
39608
  var import_joi58 = __toESM(require("joi"));
39609
39609
  var import_node_server_utils116 = require("@7365admin1/node-server-utils");
39610
+ async function requireEitherOrg(req, ...orgs) {
39611
+ const wanted = orgs.map((org) => org?.toString?.() ?? "").filter((org) => Boolean(org));
39612
+ const actor = await resolveInviteActor(callerId(req));
39613
+ if (actor.isSuperAdmin)
39614
+ return;
39615
+ if (wanted.length === 0 || !wanted.some((org) => actor.orgIds.includes(org))) {
39616
+ throw new import_node_server_utils116.UnauthorizedError("Not authorized.");
39617
+ }
39618
+ }
39610
39619
  function useCustomerSiteController() {
39611
39620
  const {
39612
39621
  getAll: _getAll,
@@ -39624,6 +39633,10 @@ function useCustomerSiteController() {
39624
39633
  return;
39625
39634
  }
39626
39635
  try {
39636
+ await requireOrgAccess(req, value.org);
39637
+ if (value.siteOrg && value.siteOrg.toString() !== value.org?.toString()) {
39638
+ await requireOrgAccess(req, value.siteOrg);
39639
+ }
39627
39640
  const data = await _add(value);
39628
39641
  res.status(201).json(data);
39629
39642
  return;
@@ -39691,6 +39704,7 @@ function useCustomerSiteController() {
39691
39704
  }
39692
39705
  });
39693
39706
  try {
39707
+ await requireOrgAccess(req, org);
39694
39708
  const data = await _getAll({
39695
39709
  search,
39696
39710
  page,
@@ -39722,6 +39736,13 @@ function useCustomerSiteController() {
39722
39736
  return;
39723
39737
  }
39724
39738
  try {
39739
+ const actor = await resolveInviteActor(callerId(req));
39740
+ if (!actor.isSuperAdmin) {
39741
+ await useCameraViewService().entitleSite({
39742
+ siteId: site,
39743
+ userId: actor.id
39744
+ });
39745
+ }
39725
39746
  const data = await _getBySiteAsServiceProvider(site);
39726
39747
  res.json(data);
39727
39748
  return;
@@ -39743,6 +39764,7 @@ function useCustomerSiteController() {
39743
39764
  }
39744
39765
  try {
39745
39766
  const data = await _getById(id);
39767
+ await requireEitherOrg(req, data?.org, data?.siteOrg);
39746
39768
  res.json(data);
39747
39769
  return;
39748
39770
  } catch (error2) {
@@ -39761,6 +39783,8 @@ function useCustomerSiteController() {
39761
39783
  return;
39762
39784
  }
39763
39785
  try {
39786
+ const existing = await _getById(id);
39787
+ await requireEitherOrg(req, existing?.org, existing?.siteOrg);
39764
39788
  const data = await _updateCusSiteById(id, req.body);
39765
39789
  res.json(data);
39766
39790
  return;
@@ -39772,6 +39796,8 @@ function useCustomerSiteController() {
39772
39796
  async function deleteById(req, res, next) {
39773
39797
  const id = req.params.id;
39774
39798
  try {
39799
+ const existing = await _getById(id);
39800
+ await requireEitherOrg(req, existing?.org, existing?.siteOrg);
39775
39801
  const data = await _deleteById(id);
39776
39802
  res.json(data);
39777
39803
  return;