@7365admin1/core 3.48.1-staging.164 → 3.48.1-staging.165

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,22 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Scope the virtual patrol question routes to the estate
6
+
7
+ `/api/patrol-questions` carried `requireAuth` and nothing more on all four
8
+ handlers, and `patrol-question.controller.ts` held no authorization identifier
9
+ of any kind. Any signed-in account on the platform could add a question to
10
+ another client's estate, reword one, change the answers it accepts, or delete
11
+ it. The reword is the quiet one: the guard's console still shows a question and
12
+ still records an answer, so nothing looks wrong, but the estate is no longer
13
+ checking what it believes it is checking.
14
+
15
+ `GET /` was worse than "unscoped": `site` was an OPTIONAL query parameter and
16
+ the repository only applies it when it is a valid id, so a request that simply
17
+ omitted it was answered with EVERY estate's patrol questions in one page.
18
+ `site` is now required, which is what both live callers already send.
19
+
20
+ Adds `requirePatrolQuestionReach`, built on the existing `requireSiteReach`
21
+ rule, plus an uncached `getRawPatrolQuestionById` so the check is not decided
22
+ from a 15-minute cache entry.
package/dist/index.d.ts CHANGED
@@ -4876,6 +4876,7 @@ declare function MPatrolQuestion(value: TPatrolQuestion): {
4876
4876
  };
4877
4877
 
4878
4878
  declare function usePatrolQuestionRepo(): {
4879
+ getRawPatrolQuestionById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
4879
4880
  createIndexes: () => Promise<string>;
4880
4881
  createTextIndex: () => Promise<string>;
4881
4882
  add: (value: TPatrolQuestion, session?: ClientSession) => Promise<ObjectId>;
@@ -4892,6 +4893,41 @@ declare function usePatrolQuestionRepo(): {
4892
4893
  getById: (_id: string | ObjectId, session?: ClientSession) => Promise<mongodb.WithId<bson.Document> | TPatrolQuestion | null>;
4893
4894
  };
4894
4895
 
4896
+ /**
4897
+ * Who may see, or change, an estate's virtual patrol questions.
4898
+ *
4899
+ * A patrol question is what a guard is asked at a camera on their round - "Is
4900
+ * the gate secured?", with the answers they may give. The questions are pinned
4901
+ * to a route's cameras, so they are part of what the estate's patrol actually
4902
+ * checks and what the log later shows was answered.
4903
+ *
4904
+ * All four routes on `/api/patrol-questions` carried `requireAuth` and nothing
4905
+ * more, and this controller held no authorization identifier of any kind, so
4906
+ * being signed in ANYWHERE on the platform was enough to add a question to
4907
+ * another client's estate, reword one, change its allowed answers, or delete
4908
+ * it. Rewording is the sharp one: the guard's console still shows a question
4909
+ * and still records an answer, so nothing looks wrong, but the estate is no
4910
+ * longer checking what it believes it is checking.
4911
+ *
4912
+ * The estate is the scope. `requireSiteReach` is the rule `/api/patrol-routes`,
4913
+ * `/api/feedbacks`, `/api/work-orders`, `/api/occurrence-books` and
4914
+ * `/api/nfc-patrol-log` already use, so the contracted security agency that
4915
+ * runs these rounds reaches the estate through `customer.sites` and needs no
4916
+ * membership in its organisation.
4917
+ *
4918
+ * - `POST /` already REQUIRES a site in the body, so the guard checks the site
4919
+ * the request already names.
4920
+ * - `GET /` took `site` as an OPTIONAL query parameter, and the repository
4921
+ * only applies it when it is a valid id (`ObjectId.isValid(site)`).
4922
+ * Omitting it therefore returned EVERY estate's questions in one page.
4923
+ * `site` is now required, which is what both live callers already send.
4924
+ * - The two record routes name only a record, so the site is read off the
4925
+ * STORED row via `requirePatrolQuestionReach`, and a `site` in the caller's
4926
+ * own body cannot stand in for it.
4927
+ *
4928
+ * `PUT /id/:id` cannot MOVE a question: `schemaUpdatePatrolQuestion` does not
4929
+ * accept `site` at all, so there is no destination to check.
4930
+ */
4895
4931
  declare function usePatrolQuestionController(): {
4896
4932
  add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
4897
4933
  getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
package/dist/index.js CHANGED
@@ -51575,7 +51575,16 @@ function usePatrolQuestionRepo() {
51575
51575
  });
51576
51576
  });
51577
51577
  }
51578
+ async function getRawPatrolQuestionById(_id) {
51579
+ try {
51580
+ _id = new import_mongodb90.ObjectId(_id);
51581
+ } catch (error) {
51582
+ throw new import_node_server_utils145.BadRequestError("Invalid patrol question ID format.");
51583
+ }
51584
+ return await collection.findOne({ _id });
51585
+ }
51578
51586
  return {
51587
+ getRawPatrolQuestionById,
51579
51588
  createIndexes,
51580
51589
  createTextIndex,
51581
51590
  add,
@@ -51594,8 +51603,13 @@ function usePatrolQuestionController() {
51594
51603
  add: _add,
51595
51604
  getAll: _getAll,
51596
51605
  updateById: _updateById,
51597
- deleteById: _deleteById
51606
+ deleteById: _deleteById,
51607
+ getRawPatrolQuestionById: _getRawPatrolQuestionById
51598
51608
  } = usePatrolQuestionRepo();
51609
+ async function requirePatrolQuestionReach(req, _id) {
51610
+ const row = await _getRawPatrolQuestionById(_id);
51611
+ await requireSiteReach(req, row?.site);
51612
+ }
51599
51613
  async function add(req, res, next) {
51600
51614
  const data = req.body;
51601
51615
  const { error, value } = schemaPatrolQuestion.validate(data);
@@ -51604,6 +51618,7 @@ function usePatrolQuestionController() {
51604
51618
  return;
51605
51619
  }
51606
51620
  try {
51621
+ await requireSiteReach(req, value.site);
51607
51622
  const question = await _add(value);
51608
51623
  res.status(201).json({
51609
51624
  message: "Patrol Question added successfully.",
@@ -51621,7 +51636,7 @@ function usePatrolQuestionController() {
51621
51636
  sort: import_joi72.default.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
51622
51637
  status: import_joi72.default.string().optional().allow(null, ""),
51623
51638
  search: import_joi72.default.string().optional().allow(null, ""),
51624
- site: import_joi72.default.string().optional().allow(null, "")
51639
+ site: import_joi72.default.string().hex().required()
51625
51640
  });
51626
51641
  const query2 = { ...req.query };
51627
51642
  const { error } = validation.validate(query2, {
@@ -51648,6 +51663,7 @@ function usePatrolQuestionController() {
51648
51663
  }
51649
51664
  });
51650
51665
  try {
51666
+ await requireSiteReach(req, site);
51651
51667
  const data = await _getAll({
51652
51668
  search,
51653
51669
  page,
@@ -51677,6 +51693,7 @@ function usePatrolQuestionController() {
51677
51693
  return;
51678
51694
  }
51679
51695
  try {
51696
+ await requirePatrolQuestionReach(req, _id);
51680
51697
  const result = await _updateById(_id, req.body);
51681
51698
  res.status(200).json({ message: result });
51682
51699
  return;
@@ -51696,6 +51713,7 @@ function usePatrolQuestionController() {
51696
51713
  return;
51697
51714
  }
51698
51715
  try {
51716
+ await requirePatrolQuestionReach(req, _id);
51699
51717
  await _deleteById(_id);
51700
51718
  res.status(200).json({ message: "Successfully deleted patrol question." });
51701
51719
  return;