@7365admin1/core 3.47.1-staging.140 → 3.47.1-staging.142

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,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.
@@ -0,0 +1,26 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Make the people search filters narrow each other instead of overwriting each
6
+ other.
7
+
8
+ `person.repo.getAll` built its Mongo filter as one object literal in which four
9
+ different filters each wrote a `$or` key — the date range (`dateTo`), the
10
+ free-text `search`, `contact` and `plateNumber`. An object literal can only
11
+ carry one `$or`, so the last one written silently replaced every earlier one.
12
+ In practice:
13
+
14
+ - searching by name **and** contact number searched only the contact number,
15
+ - searching by name **and** plate number searched only the plate number,
16
+ - and a **date range was dropped entirely** the moment any of the three was
17
+ used — which is the live case, because `layer-common`
18
+ `composables/usePeople.ts:2-30` sends `search`, `dateFrom` and `dateTo`
19
+ together from the visitor/people list.
20
+
21
+ No error was raised; the endpoint simply answered a narrower question than it
22
+ was asked, with rows that should not have matched.
23
+
24
+ The four groups are now collected and combined with `$and`, so each one narrows
25
+ the result. A single filter behaves exactly as before, and a request with no
26
+ filters is unchanged.
package/dist/index.js CHANGED
@@ -17370,46 +17370,47 @@ function usePersonRepo() {
17370
17370
  }, session) {
17371
17371
  page = page > 0 ? page - 1 : 0;
17372
17372
  const start = dateFrom ? { $gte: new Date(dateFrom).toISOString() } : void 0;
17373
- const end = dateTo ? {
17374
- $or: [
17373
+ const anyOf = [];
17374
+ if (dateTo) {
17375
+ anyOf.push([
17375
17376
  { end: { $lte: new Date(dateTo).toISOString() } },
17376
17377
  { end: null }
17377
- ]
17378
- } : void 0;
17378
+ ]);
17379
+ }
17380
+ if (search) {
17381
+ anyOf.push([
17382
+ { name: { $regex: search, $options: "i" } },
17383
+ { email: { $regex: search, $options: "i" } },
17384
+ { nric: { $regex: search, $options: "i" } },
17385
+ { "plates.plateNumber": { $regex: search, $options: "i" } },
17386
+ { contact: { $regex: makeContainsRegex(search) } },
17387
+ { contacts: { $regex: makeContainsRegex(search) } },
17388
+ { plateNumbers: { $regex: makeContainsRegex(search) } }
17389
+ ]);
17390
+ }
17391
+ if (contact) {
17392
+ anyOf.push([
17393
+ { contact: { $regex: makeContainsRegex(contact) } },
17394
+ { contacts: { $regex: makeContainsRegex(contact) } }
17395
+ ]);
17396
+ }
17397
+ if (plateNumber) {
17398
+ anyOf.push([
17399
+ { "plates.plateNumber": { $regex: makeContainsRegex(plateNumber) } },
17400
+ { plateNumbers: { $regex: makeContainsRegex(plateNumber) } }
17401
+ ]);
17402
+ }
17379
17403
  const query2 = {
17380
17404
  ...status && status !== "all" ? { status } : { status: { $nin: ["deleted", "rejected"] } },
17381
17405
  ...start && { start },
17382
- ...end,
17383
- ...search && {
17384
- $or: [
17385
- { name: { $regex: search, $options: "i" } },
17386
- { email: { $regex: search, $options: "i" } },
17387
- { nric: { $regex: search, $options: "i" } },
17388
- { "plates.plateNumber": { $regex: search, $options: "i" } },
17389
- { contact: { $regex: makeContainsRegex(search) } },
17390
- { contacts: { $regex: makeContainsRegex(search) } },
17391
- { plateNumbers: { $regex: makeContainsRegex(search) } }
17392
- ]
17393
- },
17406
+ ...anyOf.length > 0 && { $and: anyOf.map(($or) => ({ $or })) },
17394
17407
  ...import_mongodb36.ObjectId.isValid(org) && { org: new import_mongodb36.ObjectId(org) },
17395
17408
  ...!import_mongodb36.ObjectId.isValid(org) && orgs && {
17396
17409
  org: { $in: orgs.filter(import_mongodb36.ObjectId.isValid).map((o) => new import_mongodb36.ObjectId(o)) }
17397
17410
  },
17398
17411
  ...import_mongodb36.ObjectId.isValid(site) && { site: new import_mongodb36.ObjectId(site) },
17399
17412
  ...PERSON_TYPES.includes(type) && { type },
17400
- ...nric && { nric: { $regex: makeContainsRegex(nric), $options: "i" } },
17401
- ...contact && {
17402
- $or: [
17403
- { contact: { $regex: makeContainsRegex(contact) } },
17404
- { contacts: { $regex: makeContainsRegex(contact) } }
17405
- ]
17406
- },
17407
- ...plateNumber && {
17408
- $or: [
17409
- { "plates.plateNumber": { $regex: makeContainsRegex(plateNumber) } },
17410
- { plateNumbers: { $regex: makeContainsRegex(plateNumber) } }
17411
- ]
17412
- }
17413
+ ...nric && { nric: { $regex: makeContainsRegex(nric), $options: "i" } }
17413
17414
  };
17414
17415
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
17415
17416
  try {
@@ -21171,6 +21172,7 @@ async function recordConsoleAction(entry) {
21171
21172
  // src/controllers/organization.controller.ts
21172
21173
  function useOrgController() {
21173
21174
  const { getOrgsByMembership } = useMemberRepo();
21175
+ const { getUserById: _getUserById } = useUserRepo();
21174
21176
  const {
21175
21177
  getByName: _getByName,
21176
21178
  getById: _getById,
@@ -21298,6 +21300,9 @@ function useOrgController() {
21298
21300
  const user = req.params.user;
21299
21301
  const type = req.query.type ?? "";
21300
21302
  try {
21303
+ if (user !== callerId(req)) {
21304
+ await requirePlatformStaff(req);
21305
+ }
21301
21306
  const data = await getOrgsByMembership({
21302
21307
  search,
21303
21308
  page,
@@ -21323,6 +21328,7 @@ function useOrgController() {
21323
21328
  return;
21324
21329
  }
21325
21330
  try {
21331
+ await requirePlatformStaff(req);
21326
21332
  const data = await _getByName(name);
21327
21333
  res.json(data);
21328
21334
  return;
@@ -21360,6 +21366,7 @@ function useOrgController() {
21360
21366
  return;
21361
21367
  }
21362
21368
  try {
21369
+ await requirePlatformStaff(req);
21363
21370
  const data = await _getByEmail(email);
21364
21371
  if (!data) {
21365
21372
  next(new import_node_server_utils54.NotFoundError("Organization not found."));
@@ -21387,6 +21394,10 @@ function useOrgController() {
21387
21394
  }
21388
21395
  const email = req.params.email;
21389
21396
  try {
21397
+ const self = await _getUserById(callerId(req)).catch(() => null);
21398
+ if (!self?.email || self.email.toLowerCase() !== email.toLowerCase()) {
21399
+ await requirePlatformStaff(req);
21400
+ }
21390
21401
  const data = await _getOrgsByEmail(email);
21391
21402
  res.json(data);
21392
21403
  return;