@7365admin1/core 3.47.1-staging.139 → 3.47.1-staging.141

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,51 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the estate-structure writes to the site they act on.
6
+
7
+ `POST/PATCH/PUT` on `/api/buildings`, `/api/building-levels` and
8
+ `/api/building-units` all carried `requireAuth` and nothing more. The site came
9
+ from the request body on the creates and the record id came from the URL on the
10
+ edits and deletes, and neither was checked against the caller. So any signed-in
11
+ account on the platform could add a block to another client's estate, rename its
12
+ levels in bulk, or delete its units — the records residents, visitor passes,
13
+ billing and access rights are all hung off.
14
+
15
+ Ten routes now decide from the site:
16
+
17
+ - `POST /api/buildings/`, `POST /api/building-levels/`,
18
+ `POST /api/building-units/` — from the site named in the body, which is where
19
+ the new record lands.
20
+ - `PATCH /api/buildings/id/:id`, `PUT /api/buildings/:id`,
21
+ `PUT /api/building-levels/id/:id`, `PUT /api/building-levels/:id`,
22
+ `PATCH /api/building-units/id/:id`, `PUT /api/building-units/:id` — the stored
23
+ record is loaded first and its own site decides. The id in the URL selects the
24
+ record; it never authorises it.
25
+ - `PATCH /api/building-levels/batch` — EVERY id in the batch is checked, not
26
+ just the first, and only the distinct sites are looked up so renaming twelve
27
+ levels of one block stays one check.
28
+
29
+ The rule is `entitleSite`, the site-reach rule the camera, HID and people
30
+ modules already use: a membership pinned to the site, an org-wide membership at
31
+ the site's owning organisation, or an ACTIVE `customer.sites` engagement. The
32
+ engagement branch is required, not incidental — the estate structure is
33
+ maintained from the property-management and service-provider apps, whose users
34
+ may hold no membership in the estate's own organisation.
35
+
36
+ The shared four lines live in a new `src/utils/site-reach.util.ts` so the same
37
+ check is not written out in three controllers. It introduces no second
38
+ mechanism: it resolves the caller with `resolveInviteActor` and defers to
39
+ `entitleSite`, exactly as `person.controller.ts` does.
40
+
41
+ A site the caller cannot reach answers as a site that does not exist, so site
42
+ ids cannot be enumerated by watching the difference. A record with no site at
43
+ all is refused rather than waved through.
44
+
45
+ The READS on these three routers are deliberately unchanged. Six of them share
46
+ a controller function with a pre-login signup alias
47
+ (`/buildings/list/site/resident/:siteId`, `/building-levels/list/site/resident/:siteId`,
48
+ `/building-units/resident/site/:site/block/:block/level/:level` and the three
49
+ `resident` variants), which the resident mobile app calls before an account
50
+ exists. Guarding the handler would break registration on a shipped client, so
51
+ those need the alias split first.
@@ -0,0 +1,44 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the organisation lookups so they stop mapping one client to another.
6
+
7
+ Four reads on `/api/organizations` carried `requireAuth` and nothing more, and
8
+ every one took its key straight from the URL:
9
+
10
+ - `GET /api/organizations/user/:user` — which organisations ANY person belongs to
11
+ - `GET /api/organizations/orgs/:email` — the same, keyed on ANY email address
12
+ - `GET /api/organizations/name/:name` — any organisation, by name
13
+ - `GET /api/organizations/email/:email` — any organisation, by email address
14
+
15
+ Together those are a cross-client directory and an account-enumeration oracle: a
16
+ resident of one estate could map which clients an address or a user id belongs
17
+ to across the whole platform.
18
+
19
+ Each is now decided from the session:
20
+
21
+ - `user/:user` — the caller's own id, or Seven365 staff. Every live caller asks
22
+ about itself: the seven service-provider app layouts, the org app, and the
23
+ resident mobile app all pass the signed-in user's own id.
24
+ - `orgs/:email` — the caller's own address, compared case-insensitively after
25
+ being resolved from the session, or Seven365 staff. All three live callers
26
+ pass the signed-in user's own address.
27
+ - `name/:name` — Seven365 staff. Its only caller is the staff console's client
28
+ list.
29
+ - `email/:email` — Seven365 staff. No caller exists in any repository in the
30
+ organisation.
31
+
32
+ A key that does not exist is refused exactly as one that does, so the refusal
33
+ itself is not an oracle.
34
+
35
+ `GET /api/organizations/id/:id` is deliberately NOT guarded here: the same
36
+ controller function is mounted a second time, unauthenticated, at
37
+ `/api/organizations/org/id/:id`, which the sign-up-invite screens call before an
38
+ account exists. Guarding the handler would guard both mounts and break
39
+ registration. That one needs the alias split first.
40
+
41
+ `GET /api/organizations/` (the full client list) is also unchanged and is
42
+ recorded as a decision for the leads: it is reached from a live signup path that
43
+ must move onto the organisation `POST /api/organizations/onboarding` already
44
+ returns before the list can be staff-gated.