@7365admin1/core 3.48.1-staging.166 → 3.48.1-staging.167
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.
- package/.changeset/patrol-log-site-scope.md +29 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +14 -0
- package/test/e2e/patrol-log-scope.e2e.test.mjs +508 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the virtual patrol log routes to the estate, and take the author from the session
|
|
6
|
+
|
|
7
|
+
`/api/patrol-logs` carried `requireAuth` and nothing more on all five handlers,
|
|
8
|
+
and `patrol-log.controller.ts` held no authorization identifier of any kind. A
|
|
9
|
+
patrol log is the proof a round was walked, so any signed-in account on the
|
|
10
|
+
platform could read another client's patrol history, rewrite a round's outcome,
|
|
11
|
+
detach its incident report, move it to another estate, or destroy it. The delete
|
|
12
|
+
is a HARD delete (`collection.deleteOne`), so there is no tombstone to notice.
|
|
13
|
+
|
|
14
|
+
`GET /` was worse than "unscoped": `site` was an OPTIONAL query parameter and
|
|
15
|
+
the repository only applies it when it is a valid id, so a request that simply
|
|
16
|
+
omitted it was answered with EVERY estate's patrol history in one page. `site`
|
|
17
|
+
is now required, which is what both live callers already send.
|
|
18
|
+
|
|
19
|
+
`createdBy` was accepted from the request body and never compared to the
|
|
20
|
+
session, so a round could be filed under another guard's name. It now comes from
|
|
21
|
+
`callerId(req)`.
|
|
22
|
+
|
|
23
|
+
Adds `requirePatrolLogReach`, built on the existing `requireSiteReach` rule,
|
|
24
|
+
plus an uncached `getRawPatrolLogById` so the check is not decided from a
|
|
25
|
+
15-minute cache entry. `PUT /id/:id` checks BOTH ends, because
|
|
26
|
+
`schemaUpdatePatrolLog` accepts `site` and `updateById` writes it.
|
|
27
|
+
|
|
28
|
+
Behaviour change: `GET /id/:id` used to answer 200 with `data: null` for an id
|
|
29
|
+
matching nothing; it now refuses.
|
package/dist/index.d.ts
CHANGED
|
@@ -5118,6 +5118,7 @@ declare function MPatrolLog(value: TPatrolLog): {
|
|
|
5118
5118
|
};
|
|
5119
5119
|
|
|
5120
5120
|
declare function usePatrolLogRepo(): {
|
|
5121
|
+
getRawPatrolLogById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
|
|
5121
5122
|
createIndexes: () => Promise<string>;
|
|
5122
5123
|
createTextIndex: () => Promise<string>;
|
|
5123
5124
|
add: (value: TPatrolLog, session?: ClientSession) => Promise<ObjectId>;
|
|
@@ -5139,6 +5140,52 @@ declare function usePatrolLogService(): {
|
|
|
5139
5140
|
add: (value: TPatrolLog) => Promise<bson.ObjectId>;
|
|
5140
5141
|
};
|
|
5141
5142
|
|
|
5143
|
+
/**
|
|
5144
|
+
* Who may see, or change, an estate's virtual patrol logs.
|
|
5145
|
+
*
|
|
5146
|
+
* A patrol log is the PROOF a round was walked: the route, when it started and
|
|
5147
|
+
* ended, who was assigned, every camera visited with the questions answered and
|
|
5148
|
+
* the photos and videos attached, and whether it came back complete, late or
|
|
5149
|
+
* incomplete. It is the document a client is shown when they ask whether their
|
|
5150
|
+
* estate was patrolled, and it is what an incident report is attached to.
|
|
5151
|
+
*
|
|
5152
|
+
* All five routes on `/api/patrol-logs` carried `requireAuth` and nothing more,
|
|
5153
|
+
* and this controller held no authorization identifier of any kind, so being
|
|
5154
|
+
* signed in ANYWHERE on the platform was enough to read another client's patrol
|
|
5155
|
+
* history, rewrite a round's outcome, detach its incident report, or delete the
|
|
5156
|
+
* proof outright.
|
|
5157
|
+
*
|
|
5158
|
+
* Two of those are sharp. `PUT /id/:id` can rewrite `status`, so a round that
|
|
5159
|
+
* came back "incomplete" can be made to read "complete" and the console shows
|
|
5160
|
+
* a clean patrol. `DELETE /id/:id` is a HARD delete - `collection.deleteOne`,
|
|
5161
|
+
* not a status flag - so the proof is gone with no tombstone to notice.
|
|
5162
|
+
*
|
|
5163
|
+
* The estate is the scope. `requireSiteReach` is the rule `/api/patrol-routes`,
|
|
5164
|
+
* `/api/patrol-questions`, `/api/feedbacks`, `/api/work-orders`,
|
|
5165
|
+
* `/api/occurrence-books` and `/api/nfc-patrol-log` already use, so the
|
|
5166
|
+
* contracted security agency whose guards walk these rounds reaches the estate
|
|
5167
|
+
* through `customer.sites` and needs no membership in its organisation.
|
|
5168
|
+
*
|
|
5169
|
+
* - `POST /` already REQUIRES a site in the body, so the guard checks the site
|
|
5170
|
+
* the request already names.
|
|
5171
|
+
* - `GET /` took `site` as an OPTIONAL query parameter, and the repository
|
|
5172
|
+
* only applies it when it is a valid id (`ObjectId.isValid(site)`).
|
|
5173
|
+
* Omitting it therefore returned EVERY estate's patrol history in one page.
|
|
5174
|
+
* `site` is now required, which is what both live callers already send.
|
|
5175
|
+
* - The three record routes name only a record, so the site is read off the
|
|
5176
|
+
* STORED row via `requirePatrolLogReach`, and a `site` in the caller's own
|
|
5177
|
+
* body cannot stand in for it.
|
|
5178
|
+
*
|
|
5179
|
+
* `PUT /id/:id` CAN move a log: `schemaUpdatePatrolLog` accepts `site` and
|
|
5180
|
+
* `updateById` writes it, so a caller who reaches site A could otherwise file
|
|
5181
|
+
* one of A's rounds under site B - or pull one of B's rounds into A. Both ends
|
|
5182
|
+
* are checked.
|
|
5183
|
+
*
|
|
5184
|
+
* `createdBy` was also accepted from the request BODY and never compared to the
|
|
5185
|
+
* session, so a caller could file a patrol under another guard's name. It now
|
|
5186
|
+
* comes from `callerId(req)`. The field stays optional in the schema because
|
|
5187
|
+
* existing clients do not send it.
|
|
5188
|
+
*/
|
|
5142
5189
|
declare function usePatrolLogController(): {
|
|
5143
5190
|
add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
5144
5191
|
getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -52940,7 +52940,16 @@ function usePatrolLogRepo() {
|
|
|
52940
52940
|
});
|
|
52941
52941
|
});
|
|
52942
52942
|
}
|
|
52943
|
+
async function getRawPatrolLogById(_id) {
|
|
52944
|
+
try {
|
|
52945
|
+
_id = new import_mongodb94.ObjectId(_id);
|
|
52946
|
+
} catch (error) {
|
|
52947
|
+
throw new import_node_server_utils152.BadRequestError("Invalid patrol log ID format.");
|
|
52948
|
+
}
|
|
52949
|
+
return await collection.findOne({ _id });
|
|
52950
|
+
}
|
|
52943
52951
|
return {
|
|
52952
|
+
getRawPatrolLogById,
|
|
52944
52953
|
createIndexes,
|
|
52945
52954
|
createTextIndex,
|
|
52946
52955
|
add,
|
|
@@ -53031,9 +53040,14 @@ function usePatrolLogController() {
|
|
|
53031
53040
|
getAll: _getAll,
|
|
53032
53041
|
updateById: _updateById,
|
|
53033
53042
|
deleteById: _deleteById,
|
|
53034
|
-
getById: _getById
|
|
53043
|
+
getById: _getById,
|
|
53044
|
+
getRawPatrolLogById: _getRawPatrolLogById
|
|
53035
53045
|
} = usePatrolLogRepo();
|
|
53036
53046
|
const { add: _add } = usePatrolLogService();
|
|
53047
|
+
async function requirePatrolLogReach(req, _id) {
|
|
53048
|
+
const row = await _getRawPatrolLogById(_id);
|
|
53049
|
+
await requireSiteReach(req, row?.site);
|
|
53050
|
+
}
|
|
53037
53051
|
async function add(req, res, next) {
|
|
53038
53052
|
const data = req.body;
|
|
53039
53053
|
const { error, value } = schemaPatrolLog.validate(data);
|
|
@@ -53042,7 +53056,8 @@ function usePatrolLogController() {
|
|
|
53042
53056
|
return;
|
|
53043
53057
|
}
|
|
53044
53058
|
try {
|
|
53045
|
-
|
|
53059
|
+
await requireSiteReach(req, value.site);
|
|
53060
|
+
const question = await _add({ ...value, createdBy: callerId(req) });
|
|
53046
53061
|
res.status(201).json({
|
|
53047
53062
|
message: "Patrol Log added successfully.",
|
|
53048
53063
|
data: question
|
|
@@ -53059,7 +53074,7 @@ function usePatrolLogController() {
|
|
|
53059
53074
|
sort: import_joi76.default.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
|
|
53060
53075
|
status: import_joi76.default.string().optional().allow(null, ""),
|
|
53061
53076
|
search: import_joi76.default.string().optional().allow(null, ""),
|
|
53062
|
-
site: import_joi76.default.string().
|
|
53077
|
+
site: import_joi76.default.string().hex().required(),
|
|
53063
53078
|
dateFrom: import_joi76.default.string().optional().allow(null, "")
|
|
53064
53079
|
});
|
|
53065
53080
|
const query2 = { ...req.query };
|
|
@@ -53088,6 +53103,7 @@ function usePatrolLogController() {
|
|
|
53088
53103
|
}
|
|
53089
53104
|
});
|
|
53090
53105
|
try {
|
|
53106
|
+
await requireSiteReach(req, site);
|
|
53091
53107
|
const data = await _getAll({
|
|
53092
53108
|
search,
|
|
53093
53109
|
page,
|
|
@@ -53118,6 +53134,9 @@ function usePatrolLogController() {
|
|
|
53118
53134
|
return;
|
|
53119
53135
|
}
|
|
53120
53136
|
try {
|
|
53137
|
+
await requirePatrolLogReach(req, _id);
|
|
53138
|
+
if (req.body?.site)
|
|
53139
|
+
await requireSiteReach(req, req.body.site);
|
|
53121
53140
|
const result = await _updateById(_id, req.body);
|
|
53122
53141
|
res.status(200).json({ message: result });
|
|
53123
53142
|
return;
|
|
@@ -53137,6 +53156,7 @@ function usePatrolLogController() {
|
|
|
53137
53156
|
return;
|
|
53138
53157
|
}
|
|
53139
53158
|
try {
|
|
53159
|
+
await requirePatrolLogReach(req, _id);
|
|
53140
53160
|
await _deleteById(_id);
|
|
53141
53161
|
res.status(200).json({ message: "Successfully deleted patrol log." });
|
|
53142
53162
|
return;
|
|
@@ -53157,6 +53177,7 @@ function usePatrolLogController() {
|
|
|
53157
53177
|
return;
|
|
53158
53178
|
}
|
|
53159
53179
|
try {
|
|
53180
|
+
await requirePatrolLogReach(req, id);
|
|
53160
53181
|
const logs = await _getById(id);
|
|
53161
53182
|
res.json({
|
|
53162
53183
|
message: "Successfully retrieved patrol log.",
|