@7365admin1/core 3.48.1-staging.152 → 3.48.1-staging.154
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/incident-report-site-scope.md +40 -0
- package/.changeset/manpower-monitoring-site-scope.md +37 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +39 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +46 -0
- package/test/e2e/incident-report-scope.e2e.test.mjs +444 -0
- package/test/e2e/manpower-monitoring-scope.e2e.test.mjs +486 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the incident report routes to the estate the report was written at.
|
|
6
|
+
|
|
7
|
+
Every route on `/api/incident-reports` carried `requireAuth` and nothing more, so
|
|
8
|
+
being signed in anywhere on the platform was enough to page another estate's
|
|
9
|
+
incident log — complainant name, contact, NRIC, unit, the guard's narrative and
|
|
10
|
+
the photographs — and to open, rewrite, approve, reject or delete any single
|
|
11
|
+
report, or file one at any estate.
|
|
12
|
+
|
|
13
|
+
All eight now go through `requireSiteReach`, the rule `/api/vehicles`,
|
|
14
|
+
`/api/documents` and `/api/service-providers` already use:
|
|
15
|
+
|
|
16
|
+
- the two lists (`GET /`, `GET /incident-report`) and the two creates
|
|
17
|
+
(`POST /`, `POST /summary`) name a site in the request; that site is checked
|
|
18
|
+
before it is used.
|
|
19
|
+
- the four id routes (`GET /id/:id`, `PUT /id/:id`, `PUT /review/:id`,
|
|
20
|
+
`PUT /:id`) name only a record, so the site is read off the **stored** record.
|
|
21
|
+
`PUT /id/:id` carries a `site` of its own in the body, and authorising on that
|
|
22
|
+
would have let a caller quote their own estate beside another estate's report
|
|
23
|
+
id — the shape already found on `/api/vehicles`.
|
|
24
|
+
|
|
25
|
+
`requireSiteReach` matters more here than anywhere else: a contracted security
|
|
26
|
+
agency holds no membership in the estate's organisation and reaches its own site
|
|
27
|
+
through `customer.sites` — and the agency's guards are who **write** these
|
|
28
|
+
reports. A flat organisation check would have locked them out of their own work.
|
|
29
|
+
|
|
30
|
+
A report the caller cannot reach answers exactly as one that does not exist
|
|
31
|
+
(404), so ids cannot be enumerated from the difference. Guards sit after each
|
|
32
|
+
handler's Joi validation, so a malformed id still answers 400 exactly as today.
|
|
33
|
+
|
|
34
|
+
Note on the count: the route inventory listed six handlers on this mount as
|
|
35
|
+
unguarded. There are eight, and `POST /` and `GET /` were open too — the
|
|
36
|
+
controller contains no authorization identifier of any kind at the `staging` tip.
|
|
37
|
+
All eight are closed here.
|
|
38
|
+
|
|
39
|
+
Nothing changes for a permitted caller: same response shape, same fields, same
|
|
40
|
+
status.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the manpower monitoring settings routes to the site they belong to.
|
|
6
|
+
|
|
7
|
+
Every route on `/api/manpower-monitoring` carried `requireAuth` and nothing more,
|
|
8
|
+
so being signed in anywhere on the platform was enough to read another client's
|
|
9
|
+
shift pattern and alert email recipients, to re-time their late-check-in alert, or
|
|
10
|
+
to switch their monitoring off entirely — which silences the alert that tells a
|
|
11
|
+
client nobody turned up for a shift.
|
|
12
|
+
|
|
13
|
+
Five of the six now go through `requireSiteReach`, the rule `/api/vehicles`,
|
|
14
|
+
`/api/documents`, `/api/service-providers` and `/api/incident-reports` already
|
|
15
|
+
use:
|
|
16
|
+
|
|
17
|
+
- `POST /`, `GET /sites/:id/:serviceProviderId` and `POST /multiple` name a site
|
|
18
|
+
in the request; that site is checked before it is used. `POST /multiple`
|
|
19
|
+
upserts **by** each item's own `siteId`, so every item is checked before any of
|
|
20
|
+
them is written.
|
|
21
|
+
- `PUT /:id` names only a record, so the site is read off the **stored** row —
|
|
22
|
+
a `siteId` in the caller's own body cannot stand in for it.
|
|
23
|
+
- `GET /search` names no site at all and returned every client's row on one page.
|
|
24
|
+
It is narrowed after the read, the way the people and vehicle lookups are.
|
|
25
|
+
|
|
26
|
+
`requireSiteReach` matters here because the contracted agency that actually runs
|
|
27
|
+
these shifts holds no membership in the estate's organisation and reaches its own
|
|
28
|
+
site through `customer.sites`.
|
|
29
|
+
|
|
30
|
+
`GET /sites/active` is deliberately unchanged. It reads nothing from our database
|
|
31
|
+
— it authenticates to the external HRM Labs directory and returns that system's
|
|
32
|
+
own site tree — and the only web caller sends a hard-coded service provider id
|
|
33
|
+
rather than the signed-in one, so scoping it on that id would close the screen for
|
|
34
|
+
its real users. It needs a product decision, not a guard chosen here.
|
|
35
|
+
|
|
36
|
+
A settings row the caller cannot reach answers exactly as one that does not exist,
|
|
37
|
+
so site ids cannot be enumerated by watching the difference.
|
package/dist/index.d.ts
CHANGED
|
@@ -7210,6 +7210,25 @@ declare function useIncidentReportService(): {
|
|
|
7210
7210
|
reviewIncidentReport: (id: string | ObjectId, value: Partial<TIncidentReport>) => Promise<string>;
|
|
7211
7211
|
};
|
|
7212
7212
|
|
|
7213
|
+
/**
|
|
7214
|
+
* Who may see, or change, a security incident record.
|
|
7215
|
+
*
|
|
7216
|
+
* Every route on this mount was `requireAuth` and nothing more, so being signed
|
|
7217
|
+
* in anywhere on the platform was enough to read another estate's incident
|
|
7218
|
+
* reports — names, unit numbers, photographs and the guard's narrative — and to
|
|
7219
|
+
* edit, review or delete them.
|
|
7220
|
+
*
|
|
7221
|
+
* The estate is the scope. Where the request names a site (the two lists, the
|
|
7222
|
+
* two creates) that site is checked before it is used; where it names only a
|
|
7223
|
+
* record id, the site is read off the STORED record, so a caller-supplied site
|
|
7224
|
+
* can never stand in for the real one.
|
|
7225
|
+
*
|
|
7226
|
+
* `requireSiteReach` is the rule `/api/vehicles`, `/api/documents` and
|
|
7227
|
+
* `/api/service-providers` already use, so a contracted security agency — which
|
|
7228
|
+
* holds no membership in the estate's organisation — reaches its own site
|
|
7229
|
+
* through `customer.sites` exactly as it does everywhere else. That matters more
|
|
7230
|
+
* here than anywhere: the agency's guards are who WRITE these reports.
|
|
7231
|
+
*/
|
|
7213
7232
|
declare function useIncidentReportController(): {
|
|
7214
7233
|
add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
7215
7234
|
getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
@@ -7874,6 +7893,7 @@ declare function useManpowerMonitoringRepo(): {
|
|
|
7874
7893
|
pageRange: string;
|
|
7875
7894
|
}>;
|
|
7876
7895
|
getManpowerSettingsBySiteId: (_id: string | ObjectId, serviceProviderId?: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
|
|
7896
|
+
getManpowerSettingsById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
|
|
7877
7897
|
updateManpowerMonitoringSettings: (_id: string | ObjectId, value: TManpowerMonitoringUpdate) => Promise<number>;
|
|
7878
7898
|
multipleManpowerMonitoringSettings: (value: TManpowerMonitoring[]) => Promise<{
|
|
7879
7899
|
updated: number;
|
|
@@ -7889,6 +7909,25 @@ declare function useManpowerMonitoringSrvc(): {
|
|
|
7889
7909
|
createManpowerMonitoringSettings: (payload: any) => Promise<mongodb.InsertOneResult<bson.Document>>;
|
|
7890
7910
|
};
|
|
7891
7911
|
|
|
7912
|
+
/**
|
|
7913
|
+
* Who may see, or change, a site's manpower monitoring settings.
|
|
7914
|
+
*
|
|
7915
|
+
* Every route on this mount carried `requireAuth` and nothing else, so being
|
|
7916
|
+
* signed in anywhere on the platform was enough to read another client's shift
|
|
7917
|
+
* pattern and alert recipients, to re-time their late-check-in alerts, or to
|
|
7918
|
+
* switch their monitoring off entirely — which silences the alert that tells a
|
|
7919
|
+
* client nobody turned up for a shift.
|
|
7920
|
+
*
|
|
7921
|
+
* The estate is the scope. Where the request names a site (`POST /`,
|
|
7922
|
+
* `GET /sites/:id/:serviceProviderId`, `POST /multiple`) that site is checked
|
|
7923
|
+
* before it is used; `PUT /:id` names only a record, so the site is read off
|
|
7924
|
+
* the STORED row and a `siteId` in the body cannot stand in for it.
|
|
7925
|
+
*
|
|
7926
|
+
* `requireSiteReach` is the rule `/api/vehicles`, `/api/documents`,
|
|
7927
|
+
* `/api/service-providers` and `/api/incident-reports` already use, so the
|
|
7928
|
+
* contracted agency that actually runs these shifts reaches its own site
|
|
7929
|
+
* through `customer.sites` exactly as it does everywhere else.
|
|
7930
|
+
*/
|
|
7892
7931
|
declare function useManpowerMonitoringCtrl(): {
|
|
7893
7932
|
createManpowerMonitoringSettings: (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>> | undefined>;
|
|
7894
7933
|
getAllManpowerSettings: (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>> | undefined>;
|
package/dist/index.js
CHANGED
|
@@ -70396,6 +70396,7 @@ function useIncidentReportController() {
|
|
|
70396
70396
|
return;
|
|
70397
70397
|
}
|
|
70398
70398
|
try {
|
|
70399
|
+
await requireSiteReach(req, payload.site);
|
|
70399
70400
|
const data = await _add(payload);
|
|
70400
70401
|
res.status(201).json({ data });
|
|
70401
70402
|
return;
|
|
@@ -70444,6 +70445,7 @@ function useIncidentReportController() {
|
|
|
70444
70445
|
}
|
|
70445
70446
|
});
|
|
70446
70447
|
try {
|
|
70448
|
+
await requireSiteReach(req, site);
|
|
70447
70449
|
const data = await _getAll({
|
|
70448
70450
|
search,
|
|
70449
70451
|
page,
|
|
@@ -70500,6 +70502,7 @@ function useIncidentReportController() {
|
|
|
70500
70502
|
}
|
|
70501
70503
|
});
|
|
70502
70504
|
try {
|
|
70505
|
+
await requireSiteReach(req, site);
|
|
70503
70506
|
const data = await _getAllForVirtualPatrolLogs({
|
|
70504
70507
|
search,
|
|
70505
70508
|
page,
|
|
@@ -70528,6 +70531,7 @@ function useIncidentReportController() {
|
|
|
70528
70531
|
}
|
|
70529
70532
|
try {
|
|
70530
70533
|
const data = await _getIncidentReportById(_id);
|
|
70534
|
+
await requireSiteReach(req, data?.site);
|
|
70531
70535
|
res.status(200).json(data);
|
|
70532
70536
|
return;
|
|
70533
70537
|
} catch (error2) {
|
|
@@ -70549,6 +70553,8 @@ function useIncidentReportController() {
|
|
|
70549
70553
|
return;
|
|
70550
70554
|
}
|
|
70551
70555
|
try {
|
|
70556
|
+
const existing = await _getIncidentReportById(_id);
|
|
70557
|
+
await requireSiteReach(req, existing?.site);
|
|
70552
70558
|
const result = await _updateIncidentReportById(_id, req.body);
|
|
70553
70559
|
res.status(200).json({ message: result });
|
|
70554
70560
|
return;
|
|
@@ -70568,6 +70574,8 @@ function useIncidentReportController() {
|
|
|
70568
70574
|
return;
|
|
70569
70575
|
}
|
|
70570
70576
|
try {
|
|
70577
|
+
const existing = await _getIncidentReportById(_id);
|
|
70578
|
+
await requireSiteReach(req, existing?.site);
|
|
70571
70579
|
await _deleteIncidentReportById(_id);
|
|
70572
70580
|
res.status(200).json({ message: "Successfully deleted incident report." });
|
|
70573
70581
|
return;
|
|
@@ -70589,6 +70597,7 @@ function useIncidentReportController() {
|
|
|
70589
70597
|
return;
|
|
70590
70598
|
}
|
|
70591
70599
|
try {
|
|
70600
|
+
await requireSiteReach(req, payload.site);
|
|
70592
70601
|
delete payload._id;
|
|
70593
70602
|
delete payload.createdAt;
|
|
70594
70603
|
delete payload.reasonForReject;
|
|
@@ -70623,6 +70632,8 @@ function useIncidentReportController() {
|
|
|
70623
70632
|
return;
|
|
70624
70633
|
}
|
|
70625
70634
|
try {
|
|
70635
|
+
const existing = await _getIncidentReportById(_id);
|
|
70636
|
+
await requireSiteReach(req, existing?.site);
|
|
70626
70637
|
const result = await _reviewIncidentReport(_id, req.body);
|
|
70627
70638
|
res.status(200).json({ message: result });
|
|
70628
70639
|
return;
|
|
@@ -75048,6 +75059,14 @@ function useManpowerMonitoringRepo() {
|
|
|
75048
75059
|
throw error;
|
|
75049
75060
|
}
|
|
75050
75061
|
}
|
|
75062
|
+
async function getManpowerSettingsById(_id) {
|
|
75063
|
+
try {
|
|
75064
|
+
_id = new import_mongodb144.ObjectId(_id);
|
|
75065
|
+
} catch (error) {
|
|
75066
|
+
throw new import_node_server_utils230.BadRequestError("Invalid ID format.");
|
|
75067
|
+
}
|
|
75068
|
+
return await collection.findOne({ _id });
|
|
75069
|
+
}
|
|
75051
75070
|
async function updateManpowerMonitoringSettings(_id, value) {
|
|
75052
75071
|
try {
|
|
75053
75072
|
_id = new import_mongodb144.ObjectId(_id);
|
|
@@ -75158,6 +75177,7 @@ function useManpowerMonitoringRepo() {
|
|
|
75158
75177
|
createManpowerMonitoringSettings,
|
|
75159
75178
|
getAllManpowerSettings,
|
|
75160
75179
|
getManpowerSettingsBySiteId,
|
|
75180
|
+
getManpowerSettingsById,
|
|
75161
75181
|
updateManpowerMonitoringSettings,
|
|
75162
75182
|
multipleManpowerMonitoringSettings,
|
|
75163
75183
|
getAllSites,
|
|
@@ -75470,6 +75490,7 @@ function useManpowerMonitoringCtrl() {
|
|
|
75470
75490
|
const {
|
|
75471
75491
|
getAllManpowerSettings: _getAllManpowerSettings,
|
|
75472
75492
|
getManpowerSettingsBySiteId: _getManpowerSettingsBySiteId,
|
|
75493
|
+
getManpowerSettingsById: _getManpowerSettingsById,
|
|
75473
75494
|
updateManpowerMonitoringSettings: _updateManpowerMonitoringSettings,
|
|
75474
75495
|
multipleManpowerMonitoringSettings: _multipleManpowerMonitoringSettings,
|
|
75475
75496
|
getAllSites: _getAllSites
|
|
@@ -75485,6 +75506,7 @@ function useManpowerMonitoringCtrl() {
|
|
|
75485
75506
|
next(new import_node_server_utils233.BadRequestError(error.message));
|
|
75486
75507
|
return;
|
|
75487
75508
|
}
|
|
75509
|
+
await requireSiteReach(req, payload.siteId);
|
|
75488
75510
|
const result = await _createManpowerMonitoringSettings(payload);
|
|
75489
75511
|
return res.json(result);
|
|
75490
75512
|
} catch (error) {
|
|
@@ -75511,7 +75533,17 @@ function useManpowerMonitoringCtrl() {
|
|
|
75511
75533
|
limit: Number(limit),
|
|
75512
75534
|
search
|
|
75513
75535
|
});
|
|
75514
|
-
|
|
75536
|
+
const { only } = await siteReachOf(req);
|
|
75537
|
+
const items = await only(
|
|
75538
|
+
(result?.items ?? []).map((row) => ({
|
|
75539
|
+
row,
|
|
75540
|
+
site: row?.siteId
|
|
75541
|
+
}))
|
|
75542
|
+
);
|
|
75543
|
+
return res.json({
|
|
75544
|
+
...result,
|
|
75545
|
+
items: items.map((entry) => entry.row)
|
|
75546
|
+
});
|
|
75515
75547
|
} catch (error) {
|
|
75516
75548
|
import_node_server_utils233.logger.log({ level: "error", message: error.message });
|
|
75517
75549
|
next(error);
|
|
@@ -75531,6 +75563,7 @@ function useManpowerMonitoringCtrl() {
|
|
|
75531
75563
|
next(new import_node_server_utils233.BadRequestError(error.message));
|
|
75532
75564
|
return;
|
|
75533
75565
|
}
|
|
75566
|
+
await requireSiteReach(req, _id);
|
|
75534
75567
|
const result = await _getManpowerSettingsBySiteId(_id, serviceProviderId);
|
|
75535
75568
|
return res.json(result);
|
|
75536
75569
|
} catch (error) {
|
|
@@ -75558,6 +75591,8 @@ function useManpowerMonitoringCtrl() {
|
|
|
75558
75591
|
next(new import_node_server_utils233.BadRequestError(error.message));
|
|
75559
75592
|
return;
|
|
75560
75593
|
}
|
|
75594
|
+
const existing = await _getManpowerSettingsById(_id);
|
|
75595
|
+
await requireSiteReach(req, existing?.siteId);
|
|
75561
75596
|
const result = await _updateManpowerMonitoringSettings(_id, payload);
|
|
75562
75597
|
return res.json(result);
|
|
75563
75598
|
} catch (error) {
|
|
@@ -75575,6 +75610,9 @@ function useManpowerMonitoringCtrl() {
|
|
|
75575
75610
|
next(new import_node_server_utils233.BadRequestError(error.message));
|
|
75576
75611
|
return;
|
|
75577
75612
|
}
|
|
75613
|
+
for (const item of payload) {
|
|
75614
|
+
await requireSiteReach(req, item?.siteId);
|
|
75615
|
+
}
|
|
75578
75616
|
const result = await _multipleManpowerMonitoringSettings(payload);
|
|
75579
75617
|
return res.json(result);
|
|
75580
75618
|
} catch (error) {
|