@7365admin1/core 3.48.1-staging.159 → 3.48.1-staging.160

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,50 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the daily occurrence book routes to the estate the book belongs to.
6
+
7
+ The daily occurrence book is one estate's security log for one day — the record
8
+ the guards write every shift, and the record an estate is asked to produce when
9
+ something goes wrong. Every route on `/api/occurrence-books` carried
10
+ `requireAuth` and nothing more, and `occurrence-book.controller.ts` held no
11
+ authorization identifier of any kind. `requireAuth` proves a session exists; it
12
+ makes no organisation or site decision. So being signed in anywhere on the
13
+ platform was enough to read another client's log, re-date it, re-open or close
14
+ it, or delete the day outright.
15
+
16
+ All five handlers now decide before any work is done. The route count was
17
+ verified at the registration site — `occurrence-book.route.ts` mounts **five**
18
+ routes, not the three the earlier inventory recorded.
19
+
20
+ - `POST /` and `GET /` already REQUIRE a site (in the body and the query
21
+ respectively), so the guard checks a site the request already names and no
22
+ caller's contract changes.
23
+ - `GET /id/:id`, `PUT /id/:id` and `DELETE /id/:id` name only a record, so the
24
+ site is read off the STORED row through the new `requireOccurrenceBookReach`.
25
+ A `site` in the caller's own body or query cannot stand in for it.
26
+
27
+ `requireOccurrenceBookReach` is built on a new raw reader,
28
+ `getRawOccurrenceBookById`. The existing `getOccurrenceBookById` is CACHED and
29
+ throws `NotFoundError`, so a reach check built on it would have decided who may
30
+ touch a book from a cache entry, and would have answered 404 with the wrong
31
+ message before the guard ever ran.
32
+
33
+ The rule is the existing one, `requireSiteReach` -> `entitleSite`, already used
34
+ by `/api/feedbacks`, `/api/work-orders`, `/api/manpower-monitoring`,
35
+ `/api/attendances`, `/api/remarks`, `/api/vehicles`, `/api/documents` and
36
+ `/api/incident-reports`. The `customer.sites` branch is load-bearing here: the
37
+ guards who WRITE this book are the contracted security agency's staff and hold
38
+ no membership in the estate's own organisation, so a flat organisation check
39
+ would lock the security app out of the log it exists to keep.
40
+
41
+ Each guard sits AFTER the handler's own Joi validation, so a malformed id still
42
+ answers 400 exactly as before. A row that does not exist has no site, and
43
+ `requireSiteReach` refuses a missing site, so an id matching nothing is refused
44
+ rather than falling through.
45
+
46
+ `PUT /id/:id` cannot MOVE a book — `schemaUpdateOccurrenceBook` does not accept
47
+ `site` at all — so there is no destination to check.
48
+
49
+ `/api/occurrence-entries` and `/api/occurrence-subjects` are separate mounts and
50
+ are not touched here; each gets its own change.
package/dist/index.d.ts CHANGED
@@ -6810,6 +6810,7 @@ declare function useOccurrenceBookRepo(): {
6810
6810
  pageRange: string;
6811
6811
  } | TOccurrenceBook>;
6812
6812
  getOccurrenceBookById: (_id: string | ObjectId, session?: ClientSession) => Promise<mongodb.WithId<bson.Document> | TOccurrenceBook>;
6813
+ getRawOccurrenceBookById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
6813
6814
  updateOccurrenceBookById: (_id: ObjectId | string, value: Partial<TOccurrenceBook>, session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
6814
6815
  deleteOccurrenceBookById: (_id: string | ObjectId) => Promise<number>;
6815
6816
  closeOccurrenceBooks: (session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
@@ -6827,6 +6828,35 @@ declare function useOccurrenceBookService(): {
6827
6828
  processCloseDOB: () => Promise<void>;
6828
6829
  };
6829
6830
 
6831
+ /**
6832
+ * Who may see, or change, a site's daily occurrence book.
6833
+ *
6834
+ * The daily occurrence book is one estate's security log for one day - the
6835
+ * record the guards write every shift, and the record an estate is asked to
6836
+ * produce when something goes wrong. Every route on `/api/occurrence-books`
6837
+ * carried `requireAuth` and nothing more, and this controller held no
6838
+ * authorization identifier of any kind, so being signed in anywhere on the
6839
+ * platform was enough to read another client's log, re-date it, re-open or
6840
+ * close it, or delete the day outright.
6841
+ *
6842
+ * The estate is the scope. `requireSiteReach` is the rule `/api/feedbacks`,
6843
+ * `/api/work-orders`, `/api/manpower-monitoring`, `/api/attendances`,
6844
+ * `/api/remarks`, `/api/vehicles`, `/api/documents` and `/api/incident-reports`
6845
+ * already use, so the contracted security agency that keeps this log reaches
6846
+ * the estate through `customer.sites` and needs no membership in its
6847
+ * organisation. That branch is not incidental here - the guards who write the
6848
+ * book are the agency's staff, not the estate's.
6849
+ *
6850
+ * - `POST /` and `GET /` already REQUIRE a site, in the body and the query
6851
+ * respectively, so the guard checks the site the request already names and
6852
+ * no caller's contract changes.
6853
+ * - The three record routes name only a record, so the site is read off the
6854
+ * STORED row via `requireOccurrenceBookReach` and a `site` in the caller's
6855
+ * own body cannot stand in for it.
6856
+ *
6857
+ * `PUT /id/:id` cannot MOVE a book: `schemaUpdateOccurrenceBook` does not
6858
+ * accept `site` at all, so there is no destination to check.
6859
+ */
6830
6860
  declare function useOccurrenceBookController(): {
6831
6861
  add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
6832
6862
  getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
package/dist/index.js CHANGED
@@ -66160,6 +66160,14 @@ function useOccurrenceBookRepo() {
66160
66160
  throw error;
66161
66161
  }
66162
66162
  }
66163
+ async function getRawOccurrenceBookById(_id) {
66164
+ try {
66165
+ _id = new import_mongodb122.ObjectId(_id);
66166
+ } catch (error) {
66167
+ throw new import_node_server_utils192.BadRequestError("Invalid occurrence book ID format.");
66168
+ }
66169
+ return await collection.findOne({ _id });
66170
+ }
66163
66171
  async function updateOccurrenceBookById(_id, value, session) {
66164
66172
  value.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
66165
66173
  try {
@@ -66296,6 +66304,7 @@ function useOccurrenceBookRepo() {
66296
66304
  add,
66297
66305
  getAll,
66298
66306
  getOccurrenceBookById,
66307
+ getRawOccurrenceBookById,
66299
66308
  updateOccurrenceBookById,
66300
66309
  deleteOccurrenceBookById,
66301
66310
  closeOccurrenceBooks,
@@ -66428,8 +66437,13 @@ function useOccurrenceBookController() {
66428
66437
  const {
66429
66438
  getAll: _getAll,
66430
66439
  getOccurrenceBookById: _getOccurrenceBookById,
66440
+ getRawOccurrenceBookById: _getRawOccurrenceBookById,
66431
66441
  deleteOccurrenceBookById: _deleteOccurrenceBookById
66432
66442
  } = useOccurrenceBookRepo();
66443
+ async function requireOccurrenceBookReach(req, _id) {
66444
+ const row = await _getRawOccurrenceBookById(_id);
66445
+ await requireSiteReach(req, row?.site);
66446
+ }
66433
66447
  async function add(req, res, next) {
66434
66448
  const payload = { ...req.body };
66435
66449
  const { error } = schemaOccurrenceBook.validate(payload, {
@@ -66442,6 +66456,7 @@ function useOccurrenceBookController() {
66442
66456
  return;
66443
66457
  }
66444
66458
  try {
66459
+ await requireSiteReach(req, payload.site);
66445
66460
  const data = await _add(payload);
66446
66461
  res.status(201).json(data);
66447
66462
  return;
@@ -66493,6 +66508,7 @@ function useOccurrenceBookController() {
66493
66508
  }
66494
66509
  });
66495
66510
  try {
66511
+ await requireSiteReach(req, site);
66496
66512
  const data = await _getAll({
66497
66513
  search,
66498
66514
  page,
@@ -66520,6 +66536,7 @@ function useOccurrenceBookController() {
66520
66536
  return;
66521
66537
  }
66522
66538
  try {
66539
+ await requireOccurrenceBookReach(req, _id);
66523
66540
  const data = await _getOccurrenceBookById(_id);
66524
66541
  res.status(200).json(data);
66525
66542
  return;
@@ -66542,6 +66559,7 @@ function useOccurrenceBookController() {
66542
66559
  return;
66543
66560
  }
66544
66561
  try {
66562
+ await requireOccurrenceBookReach(req, _id);
66545
66563
  const result = await _updateOccurrenceBookById(_id, req.body);
66546
66564
  res.status(200).json({ message: result });
66547
66565
  return;
@@ -66561,6 +66579,7 @@ function useOccurrenceBookController() {
66561
66579
  return;
66562
66580
  }
66563
66581
  try {
66582
+ await requireOccurrenceBookReach(req, _id);
66564
66583
  await _deleteOccurrenceBookById(_id);
66565
66584
  res.status(200).json({ message: "Successfully deleted occurrence book." });
66566
66585
  return;