@7365admin1/core 3.142.0 → 3.144.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@7365admin1/core",
3
3
  "license": "MIT",
4
- "version": "3.142.0",
4
+ "version": "3.144.0",
5
5
  "author": "7365admin1",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -13,7 +13,7 @@
13
13
  "build": "tsup src/index.ts --format cjs,esm --dts && shx cp -r src/public dist && shx cp -r src/utils/handlebars dist",
14
14
  "release": "yarn run build && changeset publish",
15
15
  "lint": "tsc",
16
- "test": "tsup src/config.ts src/utils/building-map.util.ts src/utils/robot-cloud.util.ts src/utils/robot-adapter.util.ts src/utils/png-encoder.util.ts src/services/demo.adapter.ts src/services/ounitech.adapter.ts src/utils/ounitech-codes.util.ts src/services/robot-cloud.service.ts src/services/robot-fleet.service.ts src/services/padbot.adapter.ts src/utils/padbot-sign.util.ts src/utils/padbot-mqtt.util.ts src/services/open-rmf.adapter.ts src/utils/robot-credentials.util.ts src/utils/rmf-building.util.ts src/utils/door-contract.util.ts src/utils/robot-fault.util.ts src/utils/robot-tracking.util.ts src/utils/robot-apps.util.ts src/utils/robot-permission.util.ts src/utils/robot-network.util.ts src/utils/lift-contract.util.ts src/models/robot-building-map.model.ts src/utils/anpr-revoke.util.ts src/utils/anpr-plate.util.ts src/utils/vehicle-request.util.ts src/utils/camera-view.util.ts src/utils/camera-bulk-import.util.ts src/utils/dashboard-metrics.util.ts src/utils/camera-capability.util.ts src/utils/camera-playback.util.ts src/services/camera-device-http.service.ts src/utils/camera-alert.util.ts src/utils/dahua-protocol.util.ts src/utils/site-name.util.ts src/utils/site-owner-edit.util.ts src/utils/service-provider-invite.util.ts src/utils/notification-category.util.ts src/utils/notification-kind.util.ts src/utils/notification-email.util.ts src/models/notification.model.ts src/utils/hid-card.util.ts src/utils/hid-json.util.ts src/utils/hid-access.util.ts src/models/customer-site.model.ts src/models/site-camera.model.ts src/models/site.model.ts src/models/member.model.ts src/models/subscription.model.ts src/utils/session-id.util.ts src/utils/occurrence-query.util.ts src/utils/occurrence-book-create.util.ts src/utils/update-result.util.ts src/utils/hid-amico-authz.util.ts src/utils/promo-code-currency.util.ts src/utils/console-audit.util.ts src/utils/org-suspension.util.ts src/utils/session-revoke.util.ts src/utils/member-suspension.util.ts src/models/hid-amico.model.ts src/models/patrol-email.model.ts src/utils/patrol-email-pdf.util.ts src/utils/visitor-status.util.ts src/utils/self-service-email.util.ts src/utils/hid-secret.util.ts src/models/visitor-invite.model.ts src/models/guard-invite.model.ts src/utils/site-timezone.util.ts src/utils/bulletin-service-apps.util.ts src/models/bulletin-board.model.ts src/utils/vehicle-import.util.ts src/utils/expired-vehicle-sweep.util.ts src/models/vehicle.model.ts src/controllers/chat.controller.ts src/utils/file-owner.util.ts src/utils/console-permission.util.ts src/utils/role-scope.util.ts src/utils/org-default-roles.util.ts src/utils/org-modules.util.ts src/utils/site-modules.util.ts src/utils/resident-app-modules.util.ts src/utils/resident-default-role.util.ts src/utils/invite-role-scope.util.ts src/models/personal-emergency-chain.model.ts src/utils/self-signup-files.util.ts src/models/person.model.ts src/utils/module-gate.util.ts src/utils/member-lookup.util.ts src/utils/notification-access.util.ts src/utils/module-access.util.ts src/services/module-gate.service.ts src/controllers/site.controller.ts src/controllers/organization.controller.ts src/controllers/member.controller.ts src/utils/org-owner-repair.util.ts src/utils/socket-session.util.ts src/utils/people-create-gate.util.ts src/utils/service-app-type.util.ts src/utils/customer-site-list.util.ts src/utils/org-search.util.ts src/utils/invite-origin.util.ts src/utils/member-role-assign.util.ts src/utils/cross-site-role.util.ts src/utils/cross-site-role-fix.util.ts src/utils/visitor-search.util.ts src/utils/attendance-proxy.util.ts src/models/attendance.model.ts src/services/attendance-photo.service.ts --format esm --out-dir test/.build --no-dts --silent && node --test test/*.test.mjs",
16
+ "test": "tsup src/config.ts src/utils/building-map.util.ts src/utils/robot-cloud.util.ts src/utils/robot-adapter.util.ts src/utils/png-encoder.util.ts src/services/demo.adapter.ts src/services/ounitech.adapter.ts src/utils/ounitech-codes.util.ts src/services/robot-cloud.service.ts src/services/robot-fleet.service.ts src/services/padbot.adapter.ts src/utils/padbot-sign.util.ts src/utils/padbot-mqtt.util.ts src/services/open-rmf.adapter.ts src/utils/robot-credentials.util.ts src/utils/rmf-building.util.ts src/utils/door-contract.util.ts src/utils/robot-fault.util.ts src/utils/robot-tracking.util.ts src/utils/robot-apps.util.ts src/utils/robot-permission.util.ts src/utils/robot-network.util.ts src/utils/lift-contract.util.ts src/models/robot-building-map.model.ts src/utils/anpr-revoke.util.ts src/utils/anpr-plate.util.ts src/utils/vehicle-request.util.ts src/utils/camera-view.util.ts src/utils/camera-bulk-import.util.ts src/utils/dashboard-metrics.util.ts src/utils/camera-capability.util.ts src/utils/camera-playback.util.ts src/services/camera-device-http.service.ts src/utils/camera-alert.util.ts src/utils/dahua-protocol.util.ts src/utils/site-name.util.ts src/utils/site-owner-edit.util.ts src/utils/service-provider-invite.util.ts src/utils/notification-category.util.ts src/utils/notification-kind.util.ts src/utils/notification-email.util.ts src/models/notification.model.ts src/utils/hid-card.util.ts src/utils/hid-json.util.ts src/utils/hid-access.util.ts src/models/customer-site.model.ts src/models/site-camera.model.ts src/models/site.model.ts src/models/member.model.ts src/models/subscription.model.ts src/utils/session-id.util.ts src/utils/occurrence-query.util.ts src/utils/occurrence-book-create.util.ts src/utils/update-result.util.ts src/utils/hid-amico-authz.util.ts src/utils/promo-code-currency.util.ts src/utils/console-audit.util.ts src/utils/org-suspension.util.ts src/utils/session-revoke.util.ts src/utils/member-suspension.util.ts src/models/hid-amico.model.ts src/models/patrol-email.model.ts src/utils/patrol-email-pdf.util.ts src/utils/visitor-status.util.ts src/utils/self-service-email.util.ts src/utils/hid-secret.util.ts src/models/visitor-invite.model.ts src/models/guard-invite.model.ts src/utils/site-timezone.util.ts src/utils/bulletin-service-apps.util.ts src/models/bulletin-board.model.ts src/utils/vehicle-import.util.ts src/utils/expired-vehicle-sweep.util.ts src/models/vehicle.model.ts src/controllers/chat.controller.ts src/utils/file-owner.util.ts src/utils/console-permission.util.ts src/utils/role-scope.util.ts src/utils/org-default-roles.util.ts src/utils/org-modules.util.ts src/utils/site-modules.util.ts src/utils/resident-app-modules.util.ts src/utils/resident-default-role.util.ts src/utils/invite-role-scope.util.ts src/models/personal-emergency-chain.model.ts src/utils/self-signup-files.util.ts src/models/person.model.ts src/utils/module-gate.util.ts src/utils/member-lookup.util.ts src/utils/notification-access.util.ts src/utils/module-access.util.ts src/services/module-gate.service.ts src/controllers/site.controller.ts src/controllers/organization.controller.ts src/controllers/member.controller.ts src/utils/org-owner-repair.util.ts src/utils/socket-session.util.ts src/utils/people-create-gate.util.ts src/utils/service-app-type.util.ts src/utils/customer-site-list.util.ts src/utils/org-search.util.ts src/utils/invite-origin.util.ts src/utils/member-role-assign.util.ts src/utils/cross-site-role.util.ts src/utils/cross-site-role-fix.util.ts src/utils/visitor-search.util.ts src/utils/attendance-proxy.util.ts src/models/attendance.model.ts src/services/attendance-photo.service.ts src/utils/spaces-object.util.ts --format esm --out-dir test/.build --no-dts --silent && node --test test/*.test.mjs",
17
17
  "test:camera": "yarn test",
18
18
  "test:e2e": "yarn build && tsup src/repositories/visitor-transaction.repo.ts --format esm --out-dir test/.build-e2e/repositories --no-dts --silent && node --test --test-concurrency=1 --test-timeout=600000 \"test/e2e/*.test.mjs\""
19
19
  },
@@ -164,6 +164,19 @@ describe("Seven365 staff access", { concurrency: 1 }, () => {
164
164
  assert.equal((await audit("staff-view.write")).length, before);
165
165
  });
166
166
 
167
+ record("9b. inside a client, staff reach its cameras and vehicles like its owner; an outsider still cannot", async () => {
168
+ // Owner 2026-10-01 "no missed out": the camera wall (entitleSite) and every
169
+ // getUserPermissions(org) check (cameras, ANPR, vehicles) honour staff.
170
+ const { getSiteWall } = h.core.useCameraViewService();
171
+ const wall = await getSiteWall({ siteId: id.ownSite.toString(), userId: id.staff.toString() });
172
+ assert.ok(wall, "staff got the wall");
173
+ await assert.rejects(
174
+ getSiteWall({ siteId: id.ownSite.toString(), userId: id.otherOwner.toString() }),
175
+ /not found/i,
176
+ "another client's owner is still refused",
177
+ );
178
+ });
179
+
167
180
  record("10. support@ preview lists every membership, named, with the cross-client site", async () => {
168
181
  const res = await h.api(`${B}/memberships?email=${SUPPORT}`, { sid: staffSid });
169
182
  assert.equal(res.status, 200, JSON.stringify(res.body));
@@ -264,5 +277,5 @@ async function seed(h) {
264
277
  { _id: supportClientMember, user: support, org: agency, siteId: strangerSite, siteName: "Stranger Condo", role: agencyRole, type: "security_agency", status: "active", createdBy: staff, createdAt: now },
265
278
  ]);
266
279
 
267
- return { client, agency, ownSite, servedSite, strangerSite, staff, supportClientMember, supportStaffMember };
280
+ return { client, agency, ownSite, servedSite, strangerSite, staff, otherOwner: other, supportClientMember, supportStaffMember };
268
281
  }
@@ -0,0 +1,55 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import {
5
+ spacesUploadAcl,
6
+ isServableObjectKey,
7
+ anprSiteOfKey,
8
+ servedObjectHeaders,
9
+ } from "./.build/utils/spaces-object.util.mjs";
10
+
11
+ const ID = "68b0c1d2e3f4a5b6c7d8e9f0";
12
+ const SITE = "66aa11bb22cc33dd44ee55ff";
13
+
14
+ test("unset switch keeps every upload public-read (no behaviour change)", () => {
15
+ assert.equal(spacesUploadAcl(`default/${ID}`, ""), "public-read");
16
+ assert.equal(spacesUploadAcl(`anpr/${SITE}/${ID}`, ""), "public-read");
17
+ assert.equal(spacesUploadAcl(ID, ""), "public-read");
18
+ });
19
+
20
+ test("a prefix makes only its own keys private", () => {
21
+ assert.equal(spacesUploadAcl(`anpr/${SITE}/${ID}`, "anpr/"), "private");
22
+ assert.equal(spacesUploadAcl(`default/${ID}`, "anpr/"), "public-read");
23
+ assert.equal(spacesUploadAcl(`default/${ID}`, " anpr/ , default/ "), "private");
24
+ assert.equal(spacesUploadAcl(ID, "*"), "private");
25
+ });
26
+
27
+ test("only id-shaped keys are servable - no traversal, no logs, no listing", () => {
28
+ assert.ok(isServableObjectKey(`default/${ID}`));
29
+ assert.ok(isServableObjectKey(`anpr/${SITE}/${ID}`));
30
+ assert.ok(isServableObjectKey(ID));
31
+ for (const bad of ["", "default/", `../${ID}`, `default/../${ID}`, "production/logs/app.log",
32
+ `anpr/${SITE}/guard-post-1-2026-01-01-SBA1234A.jpeg`, `default//${ID}`, `default/${ID}.html`, null, 42]) {
33
+ assert.equal(isServableObjectKey(bad), false, String(bad));
34
+ }
35
+ });
36
+
37
+ test("an ANPR key names its site; others do not", () => {
38
+ assert.equal(anprSiteOfKey(`anpr/${SITE}/${ID}`), SITE);
39
+ assert.equal(anprSiteOfKey(`default/${ID}`), null);
40
+ assert.equal(anprSiteOfKey(`anpr/not-a-site/${ID}`), null);
41
+ });
42
+
43
+ test("HTML and SVG are never shown inline on the app origin", () => {
44
+ for (const t of ["text/html", "image/svg+xml", "application/xhtml+xml", "text/javascript", "", undefined]) {
45
+ const h = servedObjectHeaders(t);
46
+ assert.equal(h["Content-Type"], "application/octet-stream", String(t));
47
+ assert.equal(h["Content-Disposition"], "attachment");
48
+ }
49
+ const img = servedObjectHeaders("image/jpeg");
50
+ assert.equal(img["Content-Type"], "image/jpeg");
51
+ assert.equal(img["Content-Disposition"], "inline");
52
+ assert.match(img["Content-Security-Policy"], /sandbox/);
53
+ assert.equal(img["X-Content-Type-Options"], "nosniff");
54
+ assert.equal(servedObjectHeaders("application/pdf; charset=binary")["Content-Type"], "application/pdf");
55
+ });
@@ -111,14 +111,13 @@ test("add and delete gained nothing - staff still cannot create or destroy a cam
111
111
  }
112
112
  });
113
113
 
114
- test("staff have NOT gained a cross-organisation camera list", () => {
115
- // The whole reason the bypass is in the controller: entitleSite is the
116
- // tenant boundary for every camera READ, and it is untouched.
117
- assert.equal(
118
- /isSuperAdmin|super-admin\.util/.test(CAMERA_SERVICE),
119
- false,
120
- "entitleSite/authorizeSite now knows about staff - reads are widened",
121
- );
114
+ test("staff reach a client's cameras ONLY through the explicit staff rule (Open as staff, 2026-10-01)", () => {
115
+ // Owner decision 2026-10-01 ("no missed out"): Seven365 staff working inside
116
+ // a client see and set up its cameras like the owner. The only door is
117
+ // `staffReachesEveryClient` (live staff membership + cross-client tick), taken
118
+ // when no membership grants the site - never a bare isSuperAdmin.
119
+ assert.equal(/isSuperAdmin/.test(CAMERA_SERVICE), false, "a bare isSuperAdmin bypass appeared");
120
+ assert.match(CAMERA_SERVICE, /if \(!grant && \(await staffReachesEveryClient\(params\.userId\)\)\)/);
122
121
  const list = handler("getAll");
123
122
  assert.match(list, /entitleSite\(\{ siteId: site, userId: callerId\(req\) \}\)/);
124
123
  assert.equal(/staffStatusChange|isSuperAdmin/.test(list), false);