@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.
@@ -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("vehicleManagement:approve-vehicle"));
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
  }