@7365admin1/core 3.48.1-staging.161 → 3.48.1-staging.162

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,58 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the occurrence subject routes to the estate the subject belongs to, and
6
+ take the author from the session.
7
+
8
+ An occurrence subject is one of the named headings the guards pick from when
9
+ they write a line into an estate's daily security log — "Lift breakdown",
10
+ "Trespasser", "Fire alarm". The list belongs to one site. Every route on
11
+ `/api/occurrence-subjects` carried `requireAuth` and nothing more, and
12
+ `occurrence-subject.controller.ts` held no authorization identifier of any kind.
13
+ `requireAuth` proves a session exists; it makes no organisation or site
14
+ decision. So being signed in anywhere on the platform was enough to read another
15
+ client's subject list, add a heading to it, rename one, or delete one out from
16
+ under the guards who are picking from it.
17
+
18
+ Two defects, not one:
19
+
20
+ 1. **No scope.** All five handlers now decide before any work is done. The route
21
+ count was verified at the registration site — `occurrence-subject.route.ts`
22
+ mounts **five** routes, not the three the earlier inventory recorded.
23
+ 2. **Identity from the caller's body.** `POST /` took `addedBy` straight out of
24
+ the request body and never compared it to the session, so a heading could be
25
+ filed under somebody else's name. It comes from `callerId(req)` now. The
26
+ shipped mobile client's own comment records the old behaviour — "`addedBy` is
27
+ sent because the model stores it; the server does not take it from the
28
+ session". Same defect already fixed on `/api/attendances`,
29
+ `/api/site-entrypass-settings` and `/api/feedbacks`.
30
+
31
+ - `POST /` and `GET /` already REQUIRE a site (`schemaOccurrenceSubject` and the
32
+ query schema both have `site` required), so the guard checks a site the
33
+ request already names and no caller's contract changes.
34
+ - `GET /id/:id`, `PUT /id/:id` and `DELETE /id/:id` name only a record, so the
35
+ site is read off the STORED row through the new
36
+ `requireOccurrenceSubjectReach`, built on a new raw reader
37
+ `getRawOccurrenceSubjectById` (the existing `getOccurrenceSubjectById` is
38
+ CACHED, so a guard built on it would decide reach from a cache entry).
39
+
40
+ `GET /`'s `site` is genuinely applied by the repository — it is part of the
41
+ `baseQuery` and of the cache key — so the list needed a reach check on the site
42
+ it names, not a narrowing pass after the read.
43
+
44
+ The rule is the existing one, `requireSiteReach` -> `entitleSite`. The
45
+ `customer.sites` branch is load-bearing: the guards who maintain this list are
46
+ the contracted security agency's staff and hold no membership in the estate's
47
+ own organisation.
48
+
49
+ Each guard sits AFTER the handler's own Joi validation, so a malformed id still
50
+ answers 400 exactly as before. A row that does not exist has no site, and
51
+ `requireSiteReach` refuses a missing site, so an id matching nothing is refused
52
+ rather than falling through.
53
+
54
+ `PUT /id/:id` cannot MOVE a subject — `schemaUpdateOccurrenceSubject` accepts
55
+ only `_id` and `subject` — so there is no destination to check.
56
+
57
+ This closes the occurrence-book family: `/occurrence-books`,
58
+ `/occurrence-entries` and `/occurrence-subjects`.
package/dist/index.d.ts CHANGED
@@ -7765,6 +7765,7 @@ declare function useOccurrenceSubjectRepo(): {
7765
7765
  pageRange: string;
7766
7766
  } | TOccurrenceSubject>;
7767
7767
  getOccurrenceSubjectById: (_id: string | ObjectId, session?: ClientSession) => Promise<mongodb.WithId<bson.Document> | TOccurrenceSubject>;
7768
+ getRawOccurrenceSubjectById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
7768
7769
  updateOccurrenceSubjectById: (_id: ObjectId | string, value: Partial<TOccurrenceSubject>, session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
7769
7770
  deleteOccurrenceSubjectById: (_id: string | ObjectId) => Promise<number>;
7770
7771
  createIndexes: () => Promise<void>;
@@ -7776,6 +7777,41 @@ declare function useOccurrenceSubjectService(): {
7776
7777
  updateOccurrenceSubjectById: (id: string | ObjectId, value: Partial<TOccurrenceSubject>) => Promise<string>;
7777
7778
  };
7778
7779
 
7780
+ /**
7781
+ * Who may see, or change, the list of subjects an estate logs against.
7782
+ *
7783
+ * An occurrence subject is one of the named headings the guards pick from when
7784
+ * they write a line into the estate's daily security log - "Lift breakdown",
7785
+ * "Trespasser", "Fire alarm". The list belongs to one site. Every route on
7786
+ * `/api/occurrence-subjects` carried `requireAuth` and nothing more, and this
7787
+ * controller held no authorization identifier of any kind, so being signed in
7788
+ * anywhere on the platform was enough to read another client's subject list,
7789
+ * add a heading to it, rename one, or delete one out from under the guards
7790
+ * who are picking from it.
7791
+ *
7792
+ * Two defects, not one:
7793
+ *
7794
+ * 1. **No scope.** The estate is the scope, and the rule is the existing
7795
+ * `requireSiteReach` -> `entitleSite` that `/api/occurrence-books`,
7796
+ * `/api/occurrence-entries`, `/api/work-orders`, `/api/feedbacks` and the
7797
+ * rest already use. The `customer.sites` branch is load-bearing: the guards
7798
+ * who maintain this list are the contracted security agency's staff and
7799
+ * hold no membership in the estate's own organisation.
7800
+ * 2. **Identity from the caller's body.** `POST /` took `addedBy` straight out
7801
+ * of the request body and never compared it to the session, so a subject
7802
+ * could be filed under somebody else's name. It comes from `callerId(req)`
7803
+ * now. This is the same defect already fixed on `/api/attendances`,
7804
+ * `/api/site-entrypass-settings` and `/api/feedbacks`.
7805
+ *
7806
+ * - `POST /` and `GET /` already REQUIRE a site (`schemaOccurrenceSubject` and
7807
+ * the query schema both have `site` required), so the guard checks a site
7808
+ * the request already names and no caller's contract changes.
7809
+ * - `GET /id/:id`, `PUT /id/:id` and `DELETE /id/:id` name only a record, so
7810
+ * the site is read off the STORED row via `requireOccurrenceSubjectReach`.
7811
+ *
7812
+ * `PUT /id/:id` cannot MOVE a subject: `schemaUpdateOccurrenceSubject` accepts
7813
+ * only `_id` and `subject`, so there is no destination to check.
7814
+ */
7779
7815
  declare function useOccurrenceSubjectController(): {
7780
7816
  add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
7781
7817
  getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
package/dist/index.js CHANGED
@@ -71298,6 +71298,14 @@ function useOccurrenceSubjectRepo() {
71298
71298
  throw error;
71299
71299
  }
71300
71300
  }
71301
+ async function getRawOccurrenceSubjectById(_id) {
71302
+ try {
71303
+ _id = new import_mongodb137.ObjectId(_id);
71304
+ } catch (error) {
71305
+ throw new import_node_server_utils218.BadRequestError("Invalid occurrence subject ID format.");
71306
+ }
71307
+ return await collection.findOne({ _id });
71308
+ }
71301
71309
  async function updateOccurrenceSubjectById(_id, value, session) {
71302
71310
  value.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
71303
71311
  try {
@@ -71362,6 +71370,7 @@ function useOccurrenceSubjectRepo() {
71362
71370
  add,
71363
71371
  getAll,
71364
71372
  getOccurrenceSubjectById,
71373
+ getRawOccurrenceSubjectById,
71365
71374
  updateOccurrenceSubjectById,
71366
71375
  deleteOccurrenceSubjectById,
71367
71376
  createIndexes,
@@ -72268,8 +72277,13 @@ function useOccurrenceSubjectController() {
72268
72277
  const {
72269
72278
  getAll: _getAll,
72270
72279
  getOccurrenceSubjectById: _getOccurrenceSubjectById,
72280
+ getRawOccurrenceSubjectById: _getRawOccurrenceSubjectById,
72271
72281
  deleteOccurrenceSubjectById: _deleteOccurrenceSubjectById
72272
72282
  } = useOccurrenceSubjectRepo();
72283
+ async function requireOccurrenceSubjectReach(req, _id) {
72284
+ const row = await _getRawOccurrenceSubjectById(_id);
72285
+ await requireSiteReach(req, row?.site);
72286
+ }
72273
72287
  async function add(req, res, next) {
72274
72288
  try {
72275
72289
  const { error, value } = schemaOccurrenceSubject.validate(req.body, {
@@ -72281,7 +72295,8 @@ function useOccurrenceSubjectController() {
72281
72295
  next(new import_node_server_utils224.BadRequestError(messages));
72282
72296
  return;
72283
72297
  }
72284
- const data = await _add(value);
72298
+ await requireSiteReach(req, value.site);
72299
+ const data = await _add({ ...value, addedBy: callerId(req) });
72285
72300
  res.status(201).json(data);
72286
72301
  return;
72287
72302
  } catch (error) {
@@ -72310,6 +72325,7 @@ function useOccurrenceSubjectController() {
72310
72325
  return;
72311
72326
  }
72312
72327
  const { search, page, limit, site, sort, order } = value;
72328
+ await requireSiteReach(req, site);
72313
72329
  const sortObj = {
72314
72330
  [sort ? sort : "_id" /* ID */]: order === "asc" /* ASC */ ? 1 : -1
72315
72331
  };
@@ -72340,6 +72356,7 @@ function useOccurrenceSubjectController() {
72340
72356
  return;
72341
72357
  }
72342
72358
  const { _id } = value;
72359
+ await requireOccurrenceSubjectReach(req, _id);
72343
72360
  const data = await _getOccurrenceSubjectById(_id);
72344
72361
  res.status(200).json(data);
72345
72362
  return;
@@ -72364,6 +72381,7 @@ function useOccurrenceSubjectController() {
72364
72381
  return;
72365
72382
  }
72366
72383
  const { _id, ...rest } = value;
72384
+ await requireOccurrenceSubjectReach(req, _id);
72367
72385
  const result = await _updateOccurrenceSubjectById(_id, rest);
72368
72386
  res.status(200).json({ message: result });
72369
72387
  return;
@@ -72383,6 +72401,7 @@ function useOccurrenceSubjectController() {
72383
72401
  return;
72384
72402
  }
72385
72403
  try {
72404
+ await requireOccurrenceSubjectReach(req, _id);
72386
72405
  await _deleteOccurrenceSubjectById(_id);
72387
72406
  res.status(200).json({ message: "Successfully deleted occurrence subject." });
72388
72407
  return;