@7365admin1/core 3.47.1-staging.134 → 3.47.1-staging.136
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/vehicle-approve-permission-prefix.md +22 -0
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +19 -1
- 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/member-scope.test.mjs +112 -0
- package/test/member-staff-gate.test.mjs +1 -1
- package/test/vehicle-approve-permission.test.mjs +47 -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,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Check the vehicle-approval permission under the resource name the catalogues
|
|
6
|
+
actually grant.
|
|
7
|
+
|
|
8
|
+
When a security agency adds a vehicle, the vehicle is parked at `PENDING` unless
|
|
9
|
+
the caller may approve one. `vehicle.service.ts:125` asked for
|
|
10
|
+
`vehicleManagement:approve-vehicle`, but every catalogue that can grant a
|
|
11
|
+
permission spells that resource `vehicle-mgmt` — `web-app-security`
|
|
12
|
+
`composables/useSecurityPermission.ts:87,111`, `web-app-org`
|
|
13
|
+
`composables/usePropertyPermission.ts:312`, `ma-mobile-app`
|
|
14
|
+
`src/features/permissions/permission.catalog.ts:7`, `isecure365-mobile-app`
|
|
15
|
+
`src/features/permissions/permission.catalog.ts:32`. Only the prefix differed,
|
|
16
|
+
so the string checked here was one no role could hold and the bypass could never
|
|
17
|
+
be reached by granting the permission — the agency's vehicles stayed pending
|
|
18
|
+
however the role was set up. `vehicleManagement:` appears nowhere else in any
|
|
19
|
+
repository in the organisation.
|
|
20
|
+
|
|
21
|
+
The `*` wildcard branch is untouched, so a role holding all permissions behaves
|
|
22
|
+
exactly as before; this only makes the specific grant work as intended.
|
package/dist/index.js
CHANGED
|
@@ -19042,6 +19042,14 @@ function useMemberService() {
|
|
|
19042
19042
|
}
|
|
19043
19043
|
|
|
19044
19044
|
// src/controllers/member.controller.ts
|
|
19045
|
+
async function requireMemberOrg(req, org) {
|
|
19046
|
+
const orgId = org?.toString() ?? "";
|
|
19047
|
+
if (!orgId) {
|
|
19048
|
+
await requirePlatformStaff(req);
|
|
19049
|
+
return;
|
|
19050
|
+
}
|
|
19051
|
+
await requireOrgAccess(req, orgId);
|
|
19052
|
+
}
|
|
19045
19053
|
function useMemberController() {
|
|
19046
19054
|
const {
|
|
19047
19055
|
getByUserId: _getByUserId,
|
|
@@ -19049,6 +19057,7 @@ function useMemberController() {
|
|
|
19049
19057
|
getOrgsByMembership: _getOrgsByMembership,
|
|
19050
19058
|
getByUserIdType: _getByUserIdType,
|
|
19051
19059
|
getByUserIdTypeOrg: _getByUserIdTypeOrg,
|
|
19060
|
+
getById: _getMemberById,
|
|
19052
19061
|
updateMemberStatus: _updateMemberStatus,
|
|
19053
19062
|
updateStatusByUserId: _updateStatusByUserId,
|
|
19054
19063
|
updateSiteById: _updateSiteById
|
|
@@ -19176,6 +19185,13 @@ function useMemberController() {
|
|
|
19176
19185
|
return;
|
|
19177
19186
|
}
|
|
19178
19187
|
try {
|
|
19188
|
+
if (type === "admin") {
|
|
19189
|
+
await requirePlatformStaff(req);
|
|
19190
|
+
} else if (org) {
|
|
19191
|
+
await requireOrgAccess(req, org);
|
|
19192
|
+
} else if (user && user !== callerId(req)) {
|
|
19193
|
+
await requirePlatformStaff(req);
|
|
19194
|
+
}
|
|
19179
19195
|
const items = await _getAll({
|
|
19180
19196
|
search,
|
|
19181
19197
|
page,
|
|
@@ -19242,6 +19258,8 @@ function useMemberController() {
|
|
|
19242
19258
|
const _id = req.params.id;
|
|
19243
19259
|
const status = req.params.status;
|
|
19244
19260
|
try {
|
|
19261
|
+
const existing = await _getMemberById(_id);
|
|
19262
|
+
await requireMemberOrg(req, existing?.org);
|
|
19245
19263
|
await _updateMemberStatus(_id, { status });
|
|
19246
19264
|
res.json({ message: "Successfully updated member status." });
|
|
19247
19265
|
return;
|
|
@@ -32169,7 +32187,7 @@ function useVehicleService() {
|
|
|
32169
32187
|
user,
|
|
32170
32188
|
org: value.org
|
|
32171
32189
|
});
|
|
32172
|
-
const hasSecurityBypass = addedBySecurity && Array.isArray(userPermissions) && (userPermissions.includes("*") || userPermissions.includes("
|
|
32190
|
+
const hasSecurityBypass = addedBySecurity && Array.isArray(userPermissions) && (userPermissions.includes("*") || userPermissions.includes("vehicle-mgmt:approve-vehicle"));
|
|
32173
32191
|
if (hasSecurityBypass) {
|
|
32174
32192
|
addedByPM = true;
|
|
32175
32193
|
}
|