@7365admin1/core 3.32.2-staging.67 → 3.32.2-staging.68

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,42 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Scope `GET /site-cameras` to the caller's own site.
6
+
7
+ The site was an OPTIONAL query parameter and nothing on the handler looked at
8
+ the session beyond `requireAuth`. So any signed-in account, in any organisation,
9
+ could page the whole estate's cameras - on staging that is all 15 active IP
10
+ cameras across 3 owner organisations, each record carrying its `host` and its
11
+ `username`. It is the plainest cross-tenant read left in the camera module.
12
+
13
+ `site` is now required, and the caller is authorised against it with
14
+ `entitleSite`: membership of the site, of its owning organisation, or an ACTIVE
15
+ `customer.sites` engagement with it. "Not yours" and "does not exist" answer
16
+ identically, so site ids cannot be enumerated from the difference.
17
+
18
+ **Every caller in the estate already sends `site`** - checked across every
19
+ repository, not assumed:
20
+
21
+ | caller | sends |
22
+ |---|---|
23
+ | `iservice365-mobile-app-security` `pages/cctv/index.vue` (live) | `site`, `type: ip`, `page` |
24
+ | `iservice365-mobile-app-security` `useAnprEnabled` (live) | `site`, `type: anpr` |
25
+ | `iservice365-layer-common` `CameraMain.vue` (Site Settings) | `site`, `type`, `page` |
26
+ | `iservice365-layer-common` `CameraWall.vue` | `site`, `type: ip`, `page` |
27
+ | `iservice365-web-app-security` `VirtualPatrolRoutesForm.vue` | `site`, `type: ip`, `page: 1` |
28
+ | `isecure365-mobile-app` `site.service.ts` `getAnprCameras` | `site`, `type: anpr` |
29
+
30
+ So no running client breaks.
31
+
32
+ Deliberately NOT gated on a camera permission. This is the same list the site's
33
+ own Settings panel and the guard's app have always read; on staging only 25 of
34
+ the 118 people entitled to a site with a camera hold a camera-view permission,
35
+ so a permission gate here would strand people who are legitimately at the site.
36
+ Restricting the read to the caller's own sites is the fix; who among them may
37
+ look is a separate decision that needs its own measurement.
38
+
39
+ The repository's 15-minute cache key already carries the site, so a cached page
40
+ can only be served to callers authorised for that same site. No caller id was
41
+ added to the key - it would make the cache per-person for an answer that is
42
+ identical per site.
package/dist/index.js CHANGED
@@ -34228,7 +34228,7 @@ function useSiteCameraController() {
34228
34228
  deleteById: _deleteById,
34229
34229
  findOne: _findOne
34230
34230
  } = useSiteCameraRepo();
34231
- const { authorizeSite } = useCameraViewService();
34231
+ const { authorizeSite, entitleSite } = useCameraViewService();
34232
34232
  function callerId(req) {
34233
34233
  const user = req.user;
34234
34234
  return user?.user?.toString?.() || user?._id?.toString?.();
@@ -34300,10 +34300,9 @@ function useSiteCameraController() {
34300
34300
  }
34301
34301
  async function getAll(req, res, next) {
34302
34302
  const query2 = req.query;
34303
- const allowedTypes = ["ip", "exit", "entry", "both"];
34304
34303
  const validation = import_joi49.default.object({
34305
34304
  type: import_joi49.default.string().optional(),
34306
- site: import_joi49.default.string().optional(),
34305
+ site: import_joi49.default.string().hex().length(24).required(),
34307
34306
  page: import_joi49.default.number().min(1).optional(),
34308
34307
  limit: import_joi49.default.number().min(10).optional()
34309
34308
  });
@@ -34313,10 +34312,18 @@ function useSiteCameraController() {
34313
34312
  return;
34314
34313
  }
34315
34314
  const type = value.type ?? null;
34316
- const site = value.site ?? "";
34315
+ const site = value.site;
34317
34316
  const page = value.page ?? 1;
34318
34317
  const limit = value.limit ? parseInt(value.limit) : 10;
34319
34318
  try {
34319
+ try {
34320
+ await entitleSite({ siteId: site, userId: callerId(req) });
34321
+ } catch (error2) {
34322
+ if (error2 instanceof import_node_server_utils95.NotFoundError) {
34323
+ throw new import_node_server_utils95.NotFoundError("Site not found.");
34324
+ }
34325
+ throw error2;
34326
+ }
34320
34327
  const siteCameras = await _getAll({ type, site, page, limit });
34321
34328
  res.json(siteCameras);
34322
34329
  return;