@7365admin1/core 3.48.1-staging.151 → 3.48.1-staging.153
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/service-provider-record-scope.md +39 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +32 -0
- package/test/e2e/incident-report-scope.e2e.test.mjs +444 -0
- package/test/e2e/service-provider-record-scope.e2e.test.mjs +281 -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,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the service provider record routes to the site the engagement works on.
|
|
6
|
+
|
|
7
|
+
A `site.service-providers` row is an ENGAGEMENT: a provider organisation attached
|
|
8
|
+
to one client's site. Two routes took its id straight out of the URL with
|
|
9
|
+
`requireAuth` and nothing more, so any signed-in account could use them against
|
|
10
|
+
any client:
|
|
11
|
+
|
|
12
|
+
- `GET /api/service-providers/:id` returned any client's engagement — provider
|
|
13
|
+
name, category, contact email and the site it works on.
|
|
14
|
+
- `PATCH /api/service-providers/:id/status` suspended or reactivated it.
|
|
15
|
+
Suspending an engagement stops that provider's guards and cleaners reaching the
|
|
16
|
+
estate; reactivating one puts them back.
|
|
17
|
+
|
|
18
|
+
Both now decide from the **site stored on the row**, through the existing
|
|
19
|
+
`requireSiteReach` rule that `/api/vehicles` and `/api/documents` already use.
|
|
20
|
+
That rule admits both sides of the engagement: the client, and the contracted
|
|
21
|
+
provider whose reach comes from `customer.sites` rather than from a membership in
|
|
22
|
+
the client's organisation.
|
|
23
|
+
|
|
24
|
+
An engagement the caller cannot reach answers exactly as one that does not exist
|
|
25
|
+
(404), so ids cannot be enumerated from the difference. A row carrying no site at
|
|
26
|
+
all is refused, not waved through. Guards sit after each handler's Joi
|
|
27
|
+
validation, so a malformed id or status still answers 400 exactly as today.
|
|
28
|
+
|
|
29
|
+
Scope note — deliberately NOT changed here: the four list routes
|
|
30
|
+
(`GET /api/service-providers`, `/name`, `/category`,
|
|
31
|
+
`/org/:serviceProviderOrgId/type/:type`) and `GET /provider/:provider/org/:org`.
|
|
32
|
+
Their `orgId`, `siteId` and `serviceProviderOrgId` are optional filters the
|
|
33
|
+
caller sends, and `layer-common useServiceProvider.ts` defaults every one of them
|
|
34
|
+
to an empty string, so it is not yet established which callers depend on the
|
|
35
|
+
unfiltered list. That is the same open question as the site-less
|
|
36
|
+
`GET /api/vehicles` and needs a product decision rather than a guess.
|
|
37
|
+
|
|
38
|
+
Nothing changes for a permitted caller: same response shape, same fields, same
|
|
39
|
+
status.
|
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>;
|
package/dist/index.js
CHANGED
|
@@ -32562,6 +32562,7 @@ function useServiceProviderController() {
|
|
|
32562
32562
|
}
|
|
32563
32563
|
try {
|
|
32564
32564
|
const data = await _getServiceProviderById(_id);
|
|
32565
|
+
await requireSiteReach(req, data?.siteId?.toString());
|
|
32565
32566
|
res.json(data);
|
|
32566
32567
|
return;
|
|
32567
32568
|
} catch (error2) {
|
|
@@ -32625,6 +32626,8 @@ function useServiceProviderController() {
|
|
|
32625
32626
|
return;
|
|
32626
32627
|
}
|
|
32627
32628
|
try {
|
|
32629
|
+
const existing = await _getServiceProviderById(value.id);
|
|
32630
|
+
await requireSiteReach(req, existing?.siteId?.toString());
|
|
32628
32631
|
await _updateStatusById(value.id, value.status);
|
|
32629
32632
|
res.json({ message: "Service provider status updated successfully." });
|
|
32630
32633
|
return;
|
|
@@ -70393,6 +70396,7 @@ function useIncidentReportController() {
|
|
|
70393
70396
|
return;
|
|
70394
70397
|
}
|
|
70395
70398
|
try {
|
|
70399
|
+
await requireSiteReach(req, payload.site);
|
|
70396
70400
|
const data = await _add(payload);
|
|
70397
70401
|
res.status(201).json({ data });
|
|
70398
70402
|
return;
|
|
@@ -70441,6 +70445,7 @@ function useIncidentReportController() {
|
|
|
70441
70445
|
}
|
|
70442
70446
|
});
|
|
70443
70447
|
try {
|
|
70448
|
+
await requireSiteReach(req, site);
|
|
70444
70449
|
const data = await _getAll({
|
|
70445
70450
|
search,
|
|
70446
70451
|
page,
|
|
@@ -70497,6 +70502,7 @@ function useIncidentReportController() {
|
|
|
70497
70502
|
}
|
|
70498
70503
|
});
|
|
70499
70504
|
try {
|
|
70505
|
+
await requireSiteReach(req, site);
|
|
70500
70506
|
const data = await _getAllForVirtualPatrolLogs({
|
|
70501
70507
|
search,
|
|
70502
70508
|
page,
|
|
@@ -70525,6 +70531,7 @@ function useIncidentReportController() {
|
|
|
70525
70531
|
}
|
|
70526
70532
|
try {
|
|
70527
70533
|
const data = await _getIncidentReportById(_id);
|
|
70534
|
+
await requireSiteReach(req, data?.site);
|
|
70528
70535
|
res.status(200).json(data);
|
|
70529
70536
|
return;
|
|
70530
70537
|
} catch (error2) {
|
|
@@ -70546,6 +70553,8 @@ function useIncidentReportController() {
|
|
|
70546
70553
|
return;
|
|
70547
70554
|
}
|
|
70548
70555
|
try {
|
|
70556
|
+
const existing = await _getIncidentReportById(_id);
|
|
70557
|
+
await requireSiteReach(req, existing?.site);
|
|
70549
70558
|
const result = await _updateIncidentReportById(_id, req.body);
|
|
70550
70559
|
res.status(200).json({ message: result });
|
|
70551
70560
|
return;
|
|
@@ -70565,6 +70574,8 @@ function useIncidentReportController() {
|
|
|
70565
70574
|
return;
|
|
70566
70575
|
}
|
|
70567
70576
|
try {
|
|
70577
|
+
const existing = await _getIncidentReportById(_id);
|
|
70578
|
+
await requireSiteReach(req, existing?.site);
|
|
70568
70579
|
await _deleteIncidentReportById(_id);
|
|
70569
70580
|
res.status(200).json({ message: "Successfully deleted incident report." });
|
|
70570
70581
|
return;
|
|
@@ -70586,6 +70597,7 @@ function useIncidentReportController() {
|
|
|
70586
70597
|
return;
|
|
70587
70598
|
}
|
|
70588
70599
|
try {
|
|
70600
|
+
await requireSiteReach(req, payload.site);
|
|
70589
70601
|
delete payload._id;
|
|
70590
70602
|
delete payload.createdAt;
|
|
70591
70603
|
delete payload.reasonForReject;
|
|
@@ -70620,6 +70632,8 @@ function useIncidentReportController() {
|
|
|
70620
70632
|
return;
|
|
70621
70633
|
}
|
|
70622
70634
|
try {
|
|
70635
|
+
const existing = await _getIncidentReportById(_id);
|
|
70636
|
+
await requireSiteReach(req, existing?.site);
|
|
70623
70637
|
const result = await _reviewIncidentReport(_id, req.body);
|
|
70624
70638
|
res.status(200).json({ message: result });
|
|
70625
70639
|
return;
|