@7365admin1/core 3.47.1-staging.133 → 3.47.1-staging.135
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/member-read-and-status-scope.md +40 -0
- package/.changeset/people-read-scope.md +40 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +2283 -2237
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +56 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/member-cross-tenant.e2e.test.mjs +256 -0
- package/test/e2e/people-scope.e2e.test.mjs +230 -0
- package/test/member-scope.test.mjs +112 -0
- package/test/member-staff-gate.test.mjs +1 -1
- package/test/people-scope.test.mjs +90 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the member list and the member suspend/reactivate write to their own organisation.
|
|
6
|
+
|
|
7
|
+
`GET /api/members` took its `org` and `siteId` straight out of the query string,
|
|
8
|
+
and `PUT /api/members/:id/:status` took the member id straight out of the URL.
|
|
9
|
+
Both carried `requireAuth` and nothing more. So any signed-in account on the
|
|
10
|
+
platform could list another client's staff, and suspend or reactivate any member
|
|
11
|
+
of any client.
|
|
12
|
+
|
|
13
|
+
`GET /api/members` also had a hole in its own scope requirement. Line 170 refused
|
|
14
|
+
a query that named neither an organisation nor a user — **unless**
|
|
15
|
+
`type === "admin"`, which then listed Seven365's own staff to anybody signed in.
|
|
16
|
+
|
|
17
|
+
Both now decide from the session:
|
|
18
|
+
|
|
19
|
+
- `type=admin` requires Seven365 staff. That query belongs to the staff console
|
|
20
|
+
(`web-app-org pages/super-admin`), so it is gated rather than removed.
|
|
21
|
+
- a caller-supplied `org` is checked with `requireOrgAccess` before it is used —
|
|
22
|
+
Seven365 staff, or a live member of that organisation.
|
|
23
|
+
- `user` on its own stays open for the caller's OWN id, because "which
|
|
24
|
+
organisations am I a member of" is every service app's landing page
|
|
25
|
+
(`web-app-security pages/index.vue:118` and its siblings). Asking it about
|
|
26
|
+
somebody else now requires staff.
|
|
27
|
+
- `PUT /api/members/:id/:status` loads the stored membership and authorises on
|
|
28
|
+
the organisation recorded on it — never one from the request. A membership with
|
|
29
|
+
no organisation is a platform staff row, and only Seven365 staff may touch it;
|
|
30
|
+
a missing org is refused, not waved through.
|
|
31
|
+
|
|
32
|
+
Service providers are not special-cased and do not need to be: a provider holds a
|
|
33
|
+
membership in the organisation whose site it works on — that is how
|
|
34
|
+
`GET /api/members/user/:id/app/:type?org=` finds its own row at sign-in — so the
|
|
35
|
+
guard app's member list (`isecure365-mobile-app
|
|
36
|
+
src/features/patrol/use-patrol.ts:180`, which sends `org: site.org` with a
|
|
37
|
+
`siteId`) passes on the ordinary rule.
|
|
38
|
+
|
|
39
|
+
Nothing changes for a permitted caller: same response shape, same fields, same
|
|
40
|
+
status.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope `GET /api/people` to a site the caller can actually reach.
|
|
6
|
+
|
|
7
|
+
The endpoint returns resident and visitor records — name, NRIC, contact number,
|
|
8
|
+
unit, plate numbers — and filters on NRIC, contact and plate number. It carried
|
|
9
|
+
`requireAuth` and nothing more, and took its `org` and `site` from the query
|
|
10
|
+
string (`person.controller.ts:79-80, 93`). So any signed-in account on the
|
|
11
|
+
platform could page through another estate's residents, or look a person up by
|
|
12
|
+
NRIC across every client at once.
|
|
13
|
+
|
|
14
|
+
The caller is now resolved from the session and the scope decided before
|
|
15
|
+
anything is read:
|
|
16
|
+
|
|
17
|
+
- a named `site` is checked with `entitleSite` — the same site-reach rule the
|
|
18
|
+
camera and HID modules already use: a membership at the site, an org-wide
|
|
19
|
+
membership at the site's owning organisation, or an ACTIVE `customer.sites`
|
|
20
|
+
engagement. Reusing it matters rather than being tidy: an agency contracted to
|
|
21
|
+
an estate holds no membership in that estate's organisation, so an
|
|
22
|
+
org-membership check would have locked every guard out of the site they work
|
|
23
|
+
at. A site the caller cannot reach answers exactly as a site that does not
|
|
24
|
+
exist, so site ids cannot be enumerated.
|
|
25
|
+
- a named `org` is checked against the caller's own memberships.
|
|
26
|
+
- naming neither — the NRIC, contact and plate lookups — is narrowed to the
|
|
27
|
+
caller's own organisations instead of searching the whole platform.
|
|
28
|
+
- Seven365 staff keep cross-tenant reach, unchanged.
|
|
29
|
+
|
|
30
|
+
Every live caller passes a site: `layer-common` `VisitorForm.vue`,
|
|
31
|
+
`MemberInformation.vue` and `VehicleForm.vue` all pass the site the screen is
|
|
32
|
+
open on, and the People list passes org and site from its route. The two
|
|
33
|
+
composable functions that pass neither — `usePeople().findPersonByContact` and
|
|
34
|
+
`findUsersByPlateNumber` — have no caller in any repository.
|
|
35
|
+
|
|
36
|
+
`person.repo.getAll` gains an optional `orgs` filter for the last case. It
|
|
37
|
+
applies only when no explicit `org` was given, so it cannot fight one, and its
|
|
38
|
+
absence means "no restriction", which is what staff get.
|
|
39
|
+
|
|
40
|
+
No response shape, status code or field changed for a permitted caller.
|
package/dist/index.d.ts
CHANGED
|
@@ -4570,7 +4570,7 @@ declare function useGuestManagementController(): {
|
|
|
4570
4570
|
declare const site_people_namespace_collection = "site.people";
|
|
4571
4571
|
declare function usePersonRepo(): {
|
|
4572
4572
|
add: (value: TPerson, session?: ClientSession) => Promise<ObjectId>;
|
|
4573
|
-
getAll: ({ search, page, limit, sort, status, dateFrom, dateTo, org, site, type, nric, contact, plateNumber, }: {
|
|
4573
|
+
getAll: ({ search, page, limit, sort, status, dateFrom, dateTo, org, orgs, site, type, nric, contact, plateNumber, }: {
|
|
4574
4574
|
search?: string | undefined;
|
|
4575
4575
|
page?: number | undefined;
|
|
4576
4576
|
limit?: number | undefined;
|
|
@@ -4579,6 +4579,13 @@ declare function usePersonRepo(): {
|
|
|
4579
4579
|
dateFrom?: string | undefined;
|
|
4580
4580
|
dateTo?: string | undefined;
|
|
4581
4581
|
org?: string | undefined;
|
|
4582
|
+
/**
|
|
4583
|
+
* The organisations the CALLER may see, when they have named neither an
|
|
4584
|
+
* organisation nor a site. Resolved from the session by the controller,
|
|
4585
|
+
* never from the request. Absent means "no restriction" — which is what
|
|
4586
|
+
* Seven365 staff get.
|
|
4587
|
+
*/
|
|
4588
|
+
orgs?: string[] | undefined;
|
|
4582
4589
|
site?: string | undefined;
|
|
4583
4590
|
type?: string | undefined;
|
|
4584
4591
|
nric?: string | undefined;
|