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

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,27 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Scope the virtual patrol route routes to the estate
6
+
7
+ `/api/patrol-routes` carried `requireAuth` and nothing more on all six
8
+ handlers, and `patrol-route.controller.ts` held no authorization identifier of
9
+ any kind. Any signed-in account on the platform could read another client's
10
+ patrol schedule, re-time it, re-assign it, point it at different cameras, move
11
+ it to another estate, or delete it outright — and a deleted round simply stops
12
+ appearing in `scheduled-routes`, so nobody is told to walk it and nothing looks
13
+ wrong on the console.
14
+
15
+ `GET /` and `GET /scheduled-routes` were worse than "unscoped": `site` was an
16
+ OPTIONAL query parameter and the repository only applies it when it is a valid
17
+ id, so a request that simply omitted it was answered with EVERY estate's patrol
18
+ schedule in one page. `site` is now required, which is what both live callers
19
+ already send.
20
+
21
+ Adds `requirePatrolRouteReach`, built on the existing `requireSiteReach` rule,
22
+ plus an uncached `getRawPatrolRouteById` so the check is not decided from a
23
+ 15-minute cache entry. `PUT /id/:id` checks BOTH ends, because
24
+ `schemaUpdatePatrolRoute` accepts `site` and `updateById` writes it.
25
+
26
+ Behaviour change: `GET /id/:id` used to answer 200 with `data: null` for an id
27
+ matching nothing; it now refuses.
package/dist/index.d.ts CHANGED
@@ -4966,9 +4966,47 @@ declare function usePatrolRouteRepo(): {
4966
4966
  day: number;
4967
4967
  }, session?: ClientSession) => Promise<{}>;
4968
4968
  getById: (_id: string | ObjectId, session?: ClientSession) => Promise<mongodb.WithId<bson.Document> | TPatrolRoute | null>;
4969
+ getRawPatrolRouteById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
4969
4970
  createIndexes: () => Promise<string>;
4970
4971
  };
4971
4972
 
4973
+ /**
4974
+ * Who may see, or change, an estate's virtual patrol routes.
4975
+ *
4976
+ * A patrol route is the round a guard is required to walk: which cameras, in
4977
+ * what order, at what times, on which days, and which staff are assigned to it.
4978
+ * Every route on `/api/patrol-routes` carried `requireAuth` and nothing more,
4979
+ * and this controller held no authorization identifier of any kind, so being
4980
+ * signed in ANYWHERE on the platform was enough to read another client's patrol
4981
+ * schedule, re-time it, re-assign it, point it at different cameras, or delete
4982
+ * it outright. Deleting a route is the sharp one: the round then simply stops
4983
+ * appearing in `scheduled-routes`, so nobody is told to walk it and nothing
4984
+ * looks wrong on the console.
4985
+ *
4986
+ * The estate is the scope. `requireSiteReach` is the rule `/api/feedbacks`,
4987
+ * `/api/work-orders`, `/api/occurrence-books`, `/api/nfc-patrol-log`,
4988
+ * `/api/attendances`, `/api/remarks` and `/api/incident-reports` already use,
4989
+ * so the contracted security agency that walks these rounds reaches the estate
4990
+ * through `customer.sites` and needs no membership in its organisation. That
4991
+ * branch is not incidental here - the assignees on a route are the agency's
4992
+ * guards, not the estate's own staff.
4993
+ *
4994
+ * - `POST /` already REQUIRES a site in the body, so the guard checks the site
4995
+ * the request already names.
4996
+ * - `GET /` and `GET /scheduled-routes` took `site` as an OPTIONAL query
4997
+ * parameter, and the repository only applies it when it is a valid id
4998
+ * (`ObjectId.isValid(site)`). Omitting it therefore returned EVERY estate's
4999
+ * routes in one page. `site` is now required, which is what both live
5000
+ * callers already send.
5001
+ * - The three record routes name only a record, so the site is read off the
5002
+ * STORED row via `requirePatrolRouteReach`, and a `site` in the caller's own
5003
+ * body cannot stand in for it.
5004
+ *
5005
+ * `PUT /id/:id` CAN move a route: `schemaUpdatePatrolRoute` accepts `site` and
5006
+ * `updateById` writes it, so a caller who reaches site A could otherwise hand
5007
+ * one of A's routes to site B. Both ends are checked - the route's current site
5008
+ * and, when the body names one, the destination.
5009
+ */
4972
5010
  declare function usePatrolRouteController(): {
4973
5011
  add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
4974
5012
  getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
package/dist/index.js CHANGED
@@ -52217,6 +52217,14 @@ function usePatrolRouteRepo() {
52217
52217
  throw error;
52218
52218
  }
52219
52219
  }
52220
+ async function getRawPatrolRouteById(_id) {
52221
+ try {
52222
+ _id = new import_mongodb92.ObjectId(_id);
52223
+ } catch (error) {
52224
+ throw new import_node_server_utils149.BadRequestError("Invalid patrol route ID format.");
52225
+ }
52226
+ return await collection.findOne({ _id });
52227
+ }
52220
52228
  return {
52221
52229
  createTextIndex,
52222
52230
  add,
@@ -52225,6 +52233,7 @@ function usePatrolRouteRepo() {
52225
52233
  deleteById,
52226
52234
  getScheduledRoute,
52227
52235
  getById,
52236
+ getRawPatrolRouteById,
52228
52237
  createIndexes
52229
52238
  };
52230
52239
  }
@@ -52239,8 +52248,13 @@ function usePatrolRouteController() {
52239
52248
  updateById: _updateById,
52240
52249
  deleteById: _deleteById,
52241
52250
  getScheduledRoute: _getScheduledRoute,
52242
- getById: _getById
52251
+ getById: _getById,
52252
+ getRawPatrolRouteById: _getRawPatrolRouteById
52243
52253
  } = usePatrolRouteRepo();
52254
+ async function requirePatrolRouteReach(req, _id) {
52255
+ const row = await _getRawPatrolRouteById(_id);
52256
+ await requireSiteReach(req, row?.site);
52257
+ }
52244
52258
  async function add(req, res, next) {
52245
52259
  const data = { ...req.body };
52246
52260
  const { error, value } = schemaPatrolRoute.validate(data);
@@ -52249,6 +52263,7 @@ function usePatrolRouteController() {
52249
52263
  return;
52250
52264
  }
52251
52265
  try {
52266
+ await requireSiteReach(req, value.site);
52252
52267
  const question = await _add(value);
52253
52268
  res.status(201).json({
52254
52269
  message: "Patrol Route added successfully.",
@@ -52266,7 +52281,7 @@ function usePatrolRouteController() {
52266
52281
  sort: import_joi74.default.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
52267
52282
  status: import_joi74.default.string().optional().allow(null, ""),
52268
52283
  search: import_joi74.default.string().optional().allow(null, ""),
52269
- site: import_joi74.default.string().optional().allow(null, "")
52284
+ site: import_joi74.default.string().hex().required()
52270
52285
  });
52271
52286
  const query2 = { ...req.query };
52272
52287
  const { error } = validation.validate(query2, {
@@ -52293,6 +52308,7 @@ function usePatrolRouteController() {
52293
52308
  }
52294
52309
  });
52295
52310
  try {
52311
+ await requireSiteReach(req, site);
52296
52312
  const data = await _getAll({
52297
52313
  search,
52298
52314
  page,
@@ -52322,6 +52338,9 @@ function usePatrolRouteController() {
52322
52338
  return;
52323
52339
  }
52324
52340
  try {
52341
+ await requirePatrolRouteReach(req, _id);
52342
+ if (req.body?.site)
52343
+ await requireSiteReach(req, req.body.site);
52325
52344
  const result = await _updateById(_id, req.body);
52326
52345
  res.status(200).json({ message: result });
52327
52346
  return;
@@ -52341,6 +52360,7 @@ function usePatrolRouteController() {
52341
52360
  return;
52342
52361
  }
52343
52362
  try {
52363
+ await requirePatrolRouteReach(req, _id);
52344
52364
  await _deleteById(_id);
52345
52365
  res.status(200).json({ message: "Successfully deleted patrol route." });
52346
52366
  return;
@@ -52358,7 +52378,7 @@ function usePatrolRouteController() {
52358
52378
  sort: import_joi74.default.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
52359
52379
  status: import_joi74.default.string().optional().allow(null, ""),
52360
52380
  search: import_joi74.default.string().optional().allow(null, ""),
52361
- site: import_joi74.default.string().optional().allow(null, ""),
52381
+ site: import_joi74.default.string().hex().required(),
52362
52382
  start: import_joi74.default.string().optional().required(),
52363
52383
  day: import_joi74.default.string().optional().required()
52364
52384
  });
@@ -52389,6 +52409,7 @@ function usePatrolRouteController() {
52389
52409
  }
52390
52410
  });
52391
52411
  try {
52412
+ await requireSiteReach(req, site);
52392
52413
  const data = await _getScheduledRoute({
52393
52414
  search,
52394
52415
  page,
@@ -52418,6 +52439,7 @@ function usePatrolRouteController() {
52418
52439
  return;
52419
52440
  }
52420
52441
  try {
52442
+ await requirePatrolRouteReach(req, id);
52421
52443
  const routes = await _getById(id);
52422
52444
  res.json({
52423
52445
  message: "Successfully retrieved patrol routes.",