@7365admin1/core 3.48.1-staging.206 → 3.48.1-staging.208

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,48 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the access-management routes that name a card, not a site.
6
+
7
+ `access-management-site-scope` closed the routes whose request already carries
8
+ an estate. Seven routes on the same mount carry none — they reach an access card
9
+ (or a unit) by `_id` and nothing else — so there was nothing for that rule to
10
+ read and they were left carrying `requireAuth` alone:
11
+
12
+ - `POST /sign-qr` — mints a working door credential. The card number is
13
+ RSA-encrypted into a QR payload the reader accepts for up to 24 hours when
14
+ `purpose` is `"vms"`. Any signed-in account could hand itself a valid pass for
15
+ any card in any estate on the platform.
16
+ - `PATCH /delete-card` — deactivates a card, which is what stops it opening a
17
+ door.
18
+ - `PATCH /nfc-status` — flips a card's status and activation.
19
+ - `PATCH /visitor-checkout` — releases a visitor's card and writes its history.
20
+ - `POST /card-replacement` — retires a card and hands its replacement to a unit.
21
+ - `POST /assign-user` — hands the cards on a unit to a list of people.
22
+ - `POST /settings` — rewrites an estate's access-management settings, including
23
+ the address of the Entrypass controller it talks to. This one DOES carry the
24
+ estate, in the body, and the repository upserts on it; it was simply never
25
+ checked.
26
+
27
+ For the six that name a record, the scope is now decided on the STORED row via
28
+ `siteReachOf` — the same rule `requireBulletinVideoReach` and
29
+ `requireEntryPassReach` already apply, and the same underlying `entitleSite`
30
+ verdict as `requireSiteReach`. `POST /settings` uses `requireSiteReach` on the
31
+ body's site, exactly as `GET /settings/:site` already does. No new mechanism and
32
+ no new permission string: reaching the estate is the whole rule, so the
33
+ contracted agency at the guardhouse, the org-wide manager and a resident reading
34
+ their own pass are all unaffected.
35
+
36
+ Where a write MOVES a card onto a unit (`card-replacement`, `assign-user`) the
37
+ destination unit is checked as well as the card, so a card cannot be re-parented
38
+ into an estate the caller cannot reach.
39
+
40
+ "Not yours" and "does not exist" answer identically (404), so card ids cannot be
41
+ enumerated. An id matching no row is refused rather than waved through, and a
42
+ row naming no estate fails closed. The guards sit after each handler's Joi
43
+ validation, so malformed input still answers 400 exactly as before.
44
+
45
+ `GET /door-access-levels`, `GET /lift-access-levels` and `GET /access-groups`
46
+ are deliberately NOT changed: they carry no estate and no record, only a
47
+ caller-supplied `acm_url`, so there is nothing to scope against without changing
48
+ their request contract. They are recorded as a separate piece of work.
@@ -0,0 +1,39 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the organisation list to the caller, and put the promo-code list behind the staff gate.
6
+
7
+ `GET /api/organizations` and `GET /api/promo-codes` performed no authorization
8
+ at all — `requireAuth` and nothing more — while every sibling handler in the
9
+ same two files guarded. So any signed-in account on the platform, a resident or
10
+ a cleaner included, could page through the name, e-mail, nature and status of
11
+ **every Seven365 client**, and read off **every discount code** we have ever
12
+ minted, with its rate and its expiry, and then redeem whichever is worth most.
13
+
14
+ The two rules are different, because the callers are different.
15
+
16
+ **`GET /api/organizations` is now a SCOPE, not a refusal.** Seven365 staff see
17
+ every organisation exactly as before; anybody else sees the organisations they
18
+ hold a live membership of, plus any registered under their own verified account
19
+ e-mail. The e-mail half is not decoration: `web-app-main
20
+ pages/verify/email/index.vue:322` — the org-owner self-signup — lists
21
+ organisations to find the one it created a line earlier, at which point that
22
+ brand-new client owner is a member of nothing. Refusing them would have traded
23
+ the leak for a broken signup. The comparison is the one
24
+ `getOrgsByEmail` already makes, against the address on the caller's own `users`
25
+ row; nothing the caller sends takes part in it. It also removes a real bug from
26
+ that flow: the page takes `items[0]` of a newest-first list, so two client
27
+ owners signing up in the same moment could be handed each other's organisation
28
+ as their `defaultOrg`.
29
+
30
+ The scope is part of the Redis cache key as well as the query, so one caller's
31
+ narrowed page is never replayed to the next, and `countDocuments` runs the
32
+ scoped query so pagination cannot walk past the scope.
33
+
34
+ **`GET /api/promo-codes` is Seven365 staff only,** together with
35
+ `GET /api/promo-codes/id/:id`, which is the same disclosure one record at a
36
+ time. Both are read solely by the staff console. A client at checkout is
37
+ unaffected: `web-app-org pages/checkout.vue` and
38
+ `subscription/manage-seats.vue` type a code and validate it through
39
+ `GET /api/promo-codes/code`, which stays signed-in-only and is untouched.
package/dist/index.d.ts CHANGED
@@ -702,13 +702,17 @@ declare function useOrgRepo(): {
702
702
  createUniqueIndex: () => Promise<void>;
703
703
  add: (value: TOrg, session?: ClientSession) => Promise<ObjectId>;
704
704
  update: (id: string, value: Partial<TOrg>, session?: ClientSession) => Promise<string>;
705
- getAll: ({ search, page, limit, sort, status, nature, }: {
705
+ getAll: ({ search, page, limit, sort, status, nature, scope, }: {
706
706
  search?: string | undefined;
707
707
  page?: number | undefined;
708
708
  limit?: number | undefined;
709
709
  sort?: Record<string, number> | undefined;
710
710
  status?: string | undefined;
711
711
  nature?: string | undefined;
712
+ scope?: {
713
+ orgIds?: string[] | undefined;
714
+ email?: string | undefined;
715
+ } | undefined;
712
716
  }) => Promise<{
713
717
  items: any[];
714
718
  pages: number;
@@ -6854,6 +6858,8 @@ declare function UseAccessManagementRepo(): {
6854
6858
  insertedCount: number;
6855
6859
  insertedIds: Record<number, ObjectId>;
6856
6860
  }>;
6861
+ cardSitesRepo: (cardIds: Array<string | ObjectId>) => Promise<mongodb.WithId<bson.Document>[]>;
6862
+ unitSiteRepo: (unitId: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
6857
6863
  accessManagementSettingsRepo: (params: TAccessMngmntSettings) => Promise<mongodb.UpdateResult<bson.Document>>;
6858
6864
  allAccessCardsCountsRepo: (params: {
6859
6865
  site: string;
@@ -7121,7 +7127,7 @@ declare function useAccessManagementController(): {
7121
7127
  doorAccessLevels: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7122
7128
  liftAccessLevels: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7123
7129
  accessGroups: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7124
- accessManagementSettings: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7130
+ accessManagementSettings: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7125
7131
  allAccessCardsCounts: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7126
7132
  availableAccessCards: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7127
7133
  userTypeAccessCards: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
@@ -7130,23 +7136,23 @@ declare function useAccessManagementController(): {
7130
7136
  acknowlegdeCard: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7131
7137
  accessandLiftCards: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7132
7138
  replaceCard: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7133
- updateNFCStatus: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7139
+ updateNFCStatus: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7134
7140
  doorAndLiftDropdown: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7135
- cardReplacement: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7141
+ cardReplacement: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7136
7142
  visitorAccessCards: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7137
7143
  getCardReplacement: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7138
7144
  getAccessManagementSettings: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7139
7145
  bulkPhysicalAccessCard: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7140
7146
  assignAccessCardToUnit: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7141
- deleteCard: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7147
+ deleteCard: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7142
7148
  getCardDetails: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7143
7149
  addQrTag: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7144
7150
  allQrTag: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7145
7151
  availableCardContractors: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7146
7152
  vmsgenerateQrCodes: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7147
7153
  addVisitorAccessCard: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7148
- signQrCode: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7149
- checkoutVisitor: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7154
+ signQrCode: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7155
+ checkoutVisitor: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7150
7156
  removeAccessCard: ({ cardNo, staffNo, url, }: {
7151
7157
  cardNo: string;
7152
7158
  staffNo: string;
@@ -7154,7 +7160,7 @@ declare function useAccessManagementController(): {
7154
7160
  }) => Promise<void>;
7155
7161
  getBlockLevelAndUnitList: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7156
7162
  getTransactions: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7157
- assignMultipleCards: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7163
+ assignMultipleCards: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7158
7164
  visitorCheckout: (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
7159
7165
  uploadTemplate: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
7160
7166
  getResidents: (req: Request, res: Response) => Promise<Response<any, Record<string, any>> | undefined>;
package/dist/index.js CHANGED
@@ -11739,7 +11739,8 @@ function useOrgRepo() {
11739
11739
  limit = 10,
11740
11740
  sort = {},
11741
11741
  status = "active",
11742
- nature = ""
11742
+ nature = "",
11743
+ scope
11743
11744
  }) {
11744
11745
  const query2 = { status };
11745
11746
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
@@ -11749,6 +11750,22 @@ function useOrgRepo() {
11749
11750
  page,
11750
11751
  limit
11751
11752
  };
11753
+ if (scope) {
11754
+ const ids2 = Array.from(
11755
+ new Set((scope.orgIds ?? []).filter((id) => import_mongodb19.ObjectId.isValid(id)))
11756
+ ).sort();
11757
+ const email = (scope.email ?? "").trim();
11758
+ const reach = [];
11759
+ if (ids2.length)
11760
+ reach.push({ _id: { $in: ids2.map((id) => new import_mongodb19.ObjectId(id)) } });
11761
+ if (email)
11762
+ reach.push({ email });
11763
+ if (!reach.length) {
11764
+ return (0, import_node_server_utils21.paginate)([], page > 0 ? page - 1 : 0, limit, 0);
11765
+ }
11766
+ query2.$or = reach;
11767
+ cacheOptions.scope = `${ids2.join(",")}|${email.toLowerCase()}`;
11768
+ }
11752
11769
  if (search) {
11753
11770
  query2.$text = { $search: search };
11754
11771
  cacheOptions.search = search;
@@ -27890,11 +27907,19 @@ function useOrgController() {
27890
27907
  const limit = parseInt(req.query.limit ?? "10");
27891
27908
  const nature = req.query.nature ?? "";
27892
27909
  try {
27910
+ const id = callerId(req);
27911
+ const actor = await resolveInviteActor(id);
27912
+ let scope;
27913
+ if (!actor.isSuperAdmin) {
27914
+ const self = await _getUserById(id).catch(() => null);
27915
+ scope = { orgIds: actor.orgIds, email: self?.email ?? "" };
27916
+ }
27893
27917
  const data = await _getAll({
27894
27918
  search,
27895
27919
  page,
27896
27920
  limit,
27897
- nature
27921
+ nature,
27922
+ scope
27898
27923
  });
27899
27924
  res.json(data);
27900
27925
  return;
@@ -31336,6 +31361,7 @@ function usePromoCodeController() {
31336
31361
  return;
31337
31362
  }
31338
31363
  try {
31364
+ await requirePlatformStaff(req);
31339
31365
  const data = await _getById(_id);
31340
31366
  res.json(data);
31341
31367
  return;
@@ -31364,6 +31390,7 @@ function usePromoCodeController() {
31364
31390
  const limit = parseInt(req.query.limit ?? "10");
31365
31391
  const status = req.query.status ?? "active";
31366
31392
  try {
31393
+ await requirePlatformStaff(req);
31367
31394
  const data = await _getPromoCodes({
31368
31395
  search,
31369
31396
  page,
@@ -62014,6 +62041,24 @@ function UseAccessManagementRepo() {
62014
62041
  throw new Error(error.message);
62015
62042
  }
62016
62043
  }
62044
+ async function cardSitesRepo(cardIds) {
62045
+ try {
62046
+ const ids2 = cardIds.map((id) => new import_mongodb121.ObjectId(id));
62047
+ return await collection().find({ _id: { $in: ids2 } }, { projection: { site: 1, org: 1 } }).toArray();
62048
+ } catch (error) {
62049
+ throw new Error(error.message);
62050
+ }
62051
+ }
62052
+ async function unitSiteRepo(unitId) {
62053
+ try {
62054
+ return await collectionName("building-units").findOne(
62055
+ { _id: new import_mongodb121.ObjectId(unitId) },
62056
+ { projection: { site: 1, org: 1 } }
62057
+ );
62058
+ } catch (error) {
62059
+ throw new Error(error.message);
62060
+ }
62061
+ }
62017
62062
  async function accessManagementSettingsRepo(params) {
62018
62063
  try {
62019
62064
  params.site = new import_mongodb121.ObjectId(params.site);
@@ -64696,6 +64741,8 @@ function UseAccessManagementRepo() {
64696
64741
  createIndexForEntrypass,
64697
64742
  addPhysicalCardRepo,
64698
64743
  addNonPhysicalCardRepo,
64744
+ cardSitesRepo,
64745
+ unitSiteRepo,
64699
64746
  accessManagementSettingsRepo,
64700
64747
  allAccessCardsCountsRepo,
64701
64748
  availableAccessCardsRepo,
@@ -64748,6 +64795,8 @@ function useAccessManagementSvc() {
64748
64795
  const {
64749
64796
  addPhysicalCardRepo,
64750
64797
  addNonPhysicalCardRepo,
64798
+ cardSitesRepo,
64799
+ unitSiteRepo,
64751
64800
  accessManagementSettingsRepo,
64752
64801
  allAccessCardsCountsRepo,
64753
64802
  availableAccessCardsRepo,
@@ -64888,6 +64937,20 @@ function useAccessManagementSvc() {
64888
64937
  throw new Error(err.message);
64889
64938
  }
64890
64939
  };
64940
+ const cardSitesSvc = async (cardIds) => {
64941
+ try {
64942
+ return await cardSitesRepo(cardIds);
64943
+ } catch (err) {
64944
+ throw new Error(err.message);
64945
+ }
64946
+ };
64947
+ const unitSiteSvc = async (unitId) => {
64948
+ try {
64949
+ return await unitSiteRepo(unitId);
64950
+ } catch (err) {
64951
+ throw new Error(err.message);
64952
+ }
64953
+ };
64891
64954
  const accessManagementSettingsSvc = async (settings) => {
64892
64955
  try {
64893
64956
  const response = await accessManagementSettingsRepo({ ...settings });
@@ -65249,6 +65312,8 @@ function useAccessManagementSvc() {
65249
65312
  accessGroupsSvc,
65250
65313
  setCache,
65251
65314
  getCache,
65315
+ cardSitesSvc,
65316
+ unitSiteSvc,
65252
65317
  accessManagementSettingsSvc,
65253
65318
  allAccessCardsCountsSvc,
65254
65319
  availableAccessCardsSvc,
@@ -65301,6 +65366,26 @@ function useAccessManagementController() {
65301
65366
  return true;
65302
65367
  }
65303
65368
  };
65369
+ const refuseUnreachableCards = async (req, res, cardIds) => {
65370
+ const ids2 = [...new Set(cardIds.map((id) => String(id)))];
65371
+ const cards = await cardSitesSvc(ids2);
65372
+ const { canReach } = await siteReachOf(req);
65373
+ const reached = await Promise.all(cards.map((card) => canReach(card)));
65374
+ if (cards.length !== ids2.length || reached.includes(false)) {
65375
+ res.status(404).json({ data: null, message: "Card not found." });
65376
+ return true;
65377
+ }
65378
+ return false;
65379
+ };
65380
+ const refuseUnreachableUnit = async (req, res, unitId) => {
65381
+ const unit = await unitSiteSvc(unitId);
65382
+ const { canReach } = await siteReachOf(req);
65383
+ if (!await canReach(unit)) {
65384
+ res.status(404).json({ data: null, message: "Unit not found." });
65385
+ return true;
65386
+ }
65387
+ return false;
65388
+ };
65304
65389
  const {
65305
65390
  addPhysicalCardSvc,
65306
65391
  addNonPhysicalCardSvc,
@@ -65309,6 +65394,8 @@ function useAccessManagementController() {
65309
65394
  accessGroupsSvc,
65310
65395
  setCache,
65311
65396
  getCache,
65397
+ cardSitesSvc,
65398
+ unitSiteSvc,
65312
65399
  accessManagementSettingsSvc,
65313
65400
  allAccessCardsCountsSvc,
65314
65401
  availableAccessCardsSvc,
@@ -65528,6 +65615,8 @@ function useAccessManagementController() {
65528
65615
  const accessManagementSettings = async (req, res) => {
65529
65616
  try {
65530
65617
  const settings = req.body;
65618
+ if (await refuseUnreachableSite(req, res, settings?.site))
65619
+ return;
65531
65620
  const result = await accessManagementSettingsSvc(settings);
65532
65621
  return res.status(201).json({ message: "Success", data: result });
65533
65622
  } catch (error) {
@@ -65827,6 +65916,8 @@ function useAccessManagementController() {
65827
65916
  if (error) {
65828
65917
  return res.status(400).json({ message: error.message });
65829
65918
  }
65919
+ if (await refuseUnreachableCards(req, res, nfcList.map((nfc) => nfc._id)))
65920
+ return;
65830
65921
  const result = await updateNFCStatusSvc({ nfcList, visitorId });
65831
65922
  return res.status(200).json({ message: "Success", data: result });
65832
65923
  } catch (error) {
@@ -65879,6 +65970,10 @@ function useAccessManagementController() {
65879
65970
  if (error) {
65880
65971
  return res.status(400).json({ message: error.message });
65881
65972
  }
65973
+ if (await refuseUnreachableCards(req, res, [cardId, issuedCardId]))
65974
+ return;
65975
+ if (await refuseUnreachableUnit(req, res, unitId))
65976
+ return;
65882
65977
  const result = await cardReplacementSvc({
65883
65978
  cardId,
65884
65979
  remarks,
@@ -66083,6 +66178,8 @@ function useAccessManagementController() {
66083
66178
  if (error) {
66084
66179
  return res.status(400).json({ message: error.message });
66085
66180
  }
66181
+ if (await refuseUnreachableCards(req, res, [cardId]))
66182
+ return;
66086
66183
  const result = await deleteCardSvc({ cardId, remarks });
66087
66184
  return res.status(200).json({ message: "Success", data: result });
66088
66185
  } catch (error) {
@@ -66302,6 +66399,8 @@ function useAccessManagementController() {
66302
66399
  if (error) {
66303
66400
  return res.status(400).json({ message: error.message });
66304
66401
  }
66402
+ if (await refuseUnreachableCards(req, res, [cardId]))
66403
+ return;
66305
66404
  const result = await signQrCodeSvc({ cardId, purpose });
66306
66405
  return res.status(200).json({ message: "Success", data: result });
66307
66406
  } catch (error) {
@@ -66328,6 +66427,8 @@ function useAccessManagementController() {
66328
66427
  if (error) {
66329
66428
  return res.status(400).json({ message: error.message });
66330
66429
  }
66430
+ if (await refuseUnreachableCards(req, res, cards.map((card) => card.cardId)))
66431
+ return;
66331
66432
  const result = await checkoutVisitorSvc({ cards });
66332
66433
  return res.status(200).json({ message: "Success", data: result });
66333
66434
  } catch (error) {
@@ -66407,6 +66508,10 @@ function useAccessManagementController() {
66407
66508
  if (error) {
66408
66509
  throw new Error(`${error.message}`);
66409
66510
  }
66511
+ if (await refuseUnreachableUnit(req, res, unit))
66512
+ return;
66513
+ if (id && await refuseUnreachableCards(req, res, [id]))
66514
+ return;
66410
66515
  const result = await assignMultipleCardsSvc({
66411
66516
  assignees,
66412
66517
  unit,