@7365admin1/core 3.32.2-staging.68 → 3.32.2-staging.69
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/camera-engagement-site-scope.md +15 -0
- package/dist/index.d.ts +35 -6
- package/dist/index.js +19 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +19 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/camera-entitlement-wiring.test.mjs +46 -0
- package/test/camera-view.util.test.mjs +106 -6
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Camera entitlement: an engagement grants a site only to an org-level membership, and permissions resolve against the granting role
|
|
6
|
+
|
|
7
|
+
Two follow-ups to the camera entitlement change:
|
|
8
|
+
|
|
9
|
+
- the engagement branch of `cameraGrant` ignored `members.siteId`, so a member
|
|
10
|
+
pinned to one site could reach every site their organisation is engaged at.
|
|
11
|
+
It now requires an org-level membership, matching the owning-org branch above
|
|
12
|
+
it: a membership pinned to a site never grants a different site.
|
|
13
|
+
- `getUserPermissions` accepts the granting membership's `role`, so a caller
|
|
14
|
+
holding several memberships in one organisation is no longer resolved by
|
|
15
|
+
whichever member document Mongo returns first.
|
package/dist/index.d.ts
CHANGED
|
@@ -2658,6 +2658,12 @@ declare function hasAnyPermission(permissions: unknown, allowed: Array<string>):
|
|
|
2658
2658
|
type CameraMembership = {
|
|
2659
2659
|
siteId?: unknown;
|
|
2660
2660
|
org?: unknown;
|
|
2661
|
+
/**
|
|
2662
|
+
* The role this membership carries. Returned with the grant so permissions
|
|
2663
|
+
* are read off the role that actually granted access — see
|
|
2664
|
+
* `permissionsForGrant` in the service.
|
|
2665
|
+
*/
|
|
2666
|
+
role?: unknown;
|
|
2661
2667
|
};
|
|
2662
2668
|
/**
|
|
2663
2669
|
* WHICH membership lets this caller reach this site, or `null` for none.
|
|
@@ -2669,12 +2675,35 @@ type CameraMembership = {
|
|
|
2669
2675
|
* 2. **A membership at the site's OWNING organisation, with no site on it** —
|
|
2670
2676
|
* an org-wide role. 90 rows, 56 people, are recorded this way.
|
|
2671
2677
|
* 3. **An engagement** — an ACTIVE `customer.sites` row saying one of the
|
|
2672
|
-
* caller's organisations is contracted to serve this site
|
|
2673
|
-
*
|
|
2674
|
-
*
|
|
2675
|
-
* (`useCustomerSite().getAll()` in the
|
|
2676
|
-
* camera path read it before, so a
|
|
2677
|
-
* was refused a camera the switcher
|
|
2678
|
+
* caller's organisations is contracted to serve this site, AND a membership
|
|
2679
|
+
* that is not pinned to some other site. This is how the product routes a
|
|
2680
|
+
* security agency to a property manager's site, and it is the list the web
|
|
2681
|
+
* apps' own site switcher is built from (`useCustomerSite().getAll()` in the
|
|
2682
|
+
* Security app's layout). Nothing in the camera path read it before, so a
|
|
2683
|
+
* guard whose agency is engaged at a site was refused a camera the switcher
|
|
2684
|
+
* had just offered them.
|
|
2685
|
+
*
|
|
2686
|
+
* ## Why the engagement branch requires an ORG-LEVEL membership
|
|
2687
|
+
*
|
|
2688
|
+
* **Product decision, owner, 2026-08-10, settled:** an agency engaged at several
|
|
2689
|
+
* sites gives its staff access to **the site each person is assigned to, and no
|
|
2690
|
+
* other**. It matches how the rest of the product is described — modules are set
|
|
2691
|
+
* up per site.
|
|
2692
|
+
*
|
|
2693
|
+
* So `!idOf(membership.siteId)` is required here exactly as branch 2 requires
|
|
2694
|
+
* it. Before, this branch matched on organisation alone, which made a person
|
|
2695
|
+
* pinned to site X reach every site their agency serves — while somebody pinned
|
|
2696
|
+
* to site X of the site's OWNING organisation could not reach its other sites.
|
|
2697
|
+
* The two branches now say the same thing: **a membership pinned to a site never
|
|
2698
|
+
* grants a different site**, and a membership with no site is an org-wide role
|
|
2699
|
+
* within whichever organisation holds it.
|
|
2700
|
+
*
|
|
2701
|
+
* Measured on staging before the change: this removes 68 (user, camera-site)
|
|
2702
|
+
* grants, all of them cross-organisation engagements held by a membership pinned
|
|
2703
|
+
* elsewhere; every one of those 68 belongs to somebody whose organisation is
|
|
2704
|
+
* engaged at the site they ARE pinned to, so nobody is left without their own
|
|
2705
|
+
* site. 0 grants through branch 2 are affected — **estate-side org-level access
|
|
2706
|
+
* is a different path and is untouched.**
|
|
2678
2707
|
*
|
|
2679
2708
|
* The membership is RETURNED, not just a yes/no, because the caller's
|
|
2680
2709
|
* permissions must then be resolved against the organisation that actually
|
package/dist/index.js
CHANGED
|
@@ -27401,6 +27401,7 @@ var import_mongodb49 = require("mongodb");
|
|
|
27401
27401
|
async function getUserPermissions(params) {
|
|
27402
27402
|
const user = params.user?.toString();
|
|
27403
27403
|
const org = params.org?.toString();
|
|
27404
|
+
const grantingRole = params.role?.toString();
|
|
27404
27405
|
if (!user || !import_mongodb49.ObjectId.isValid(user)) {
|
|
27405
27406
|
return [];
|
|
27406
27407
|
}
|
|
@@ -27408,19 +27409,23 @@ async function getUserPermissions(params) {
|
|
|
27408
27409
|
if (!db) {
|
|
27409
27410
|
return [];
|
|
27410
27411
|
}
|
|
27411
|
-
|
|
27412
|
-
|
|
27413
|
-
|
|
27414
|
-
|
|
27415
|
-
|
|
27416
|
-
|
|
27417
|
-
|
|
27418
|
-
|
|
27419
|
-
|
|
27420
|
-
|
|
27412
|
+
let roleId = grantingRole;
|
|
27413
|
+
if (!roleId || !import_mongodb49.ObjectId.isValid(roleId)) {
|
|
27414
|
+
const memberQuery = {
|
|
27415
|
+
user: new import_mongodb49.ObjectId(user),
|
|
27416
|
+
status: { $ne: "deleted" }
|
|
27417
|
+
};
|
|
27418
|
+
if (org && import_mongodb49.ObjectId.isValid(org)) {
|
|
27419
|
+
memberQuery.org = new import_mongodb49.ObjectId(org);
|
|
27420
|
+
}
|
|
27421
|
+
const member = await db.collection("members").findOne(memberQuery);
|
|
27422
|
+
if (!member?.role || !import_mongodb49.ObjectId.isValid(member.role)) {
|
|
27423
|
+
return [];
|
|
27424
|
+
}
|
|
27425
|
+
roleId = member.role.toString();
|
|
27421
27426
|
}
|
|
27422
27427
|
const role = await db.collection("roles").findOne({
|
|
27423
|
-
_id: new import_mongodb49.ObjectId(
|
|
27428
|
+
_id: new import_mongodb49.ObjectId(roleId),
|
|
27424
27429
|
status: "active"
|
|
27425
27430
|
});
|
|
27426
27431
|
if (!role || !Array.isArray(role.permissions)) {
|
|
@@ -32349,7 +32354,7 @@ function cameraGrant(params) {
|
|
|
32349
32354
|
if (atOrg)
|
|
32350
32355
|
return atOrg;
|
|
32351
32356
|
const engaged = params.engagedOrgs?.size ? params.memberships.find(
|
|
32352
|
-
(membership) => params.engagedOrgs.has(idOf(membership.org))
|
|
32357
|
+
(membership) => !idOf(membership.siteId) && params.engagedOrgs.has(idOf(membership.org))
|
|
32353
32358
|
) : void 0;
|
|
32354
32359
|
return engaged ?? null;
|
|
32355
32360
|
}
|
|
@@ -33691,7 +33696,8 @@ function useCameraViewService() {
|
|
|
33691
33696
|
async function permissionsForGrant(userId, grant, memberships) {
|
|
33692
33697
|
return getUserPermissions({
|
|
33693
33698
|
user: userId,
|
|
33694
|
-
org: grant?.org ?? memberships[0]?.org
|
|
33699
|
+
org: grant?.org ?? memberships[0]?.org,
|
|
33700
|
+
role: grant?.role
|
|
33695
33701
|
});
|
|
33696
33702
|
}
|
|
33697
33703
|
async function authorizeCamera(params) {
|