@7365admin1/core 3.48.1-staging.192 → 3.48.1-staging.194

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,5 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Scope the bulletin video routes to their estate. `POST /api/bulletin-videos`, the list, the by-id read, the update and the delete carried `requireAuth` and nothing more, so any signed-in account on the platform could publish a video into another client's estate, list that estate's videos, or read, rewrite and delete any video by its id alone. Reach is now decided from the session — on the site named in the request for the write and the list, and on the stored row for the three by-id routes.
@@ -0,0 +1,29 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Scope the form-entry routes to the estate and to the resident who submitted them
6
+
7
+ `/api/form-entries` is a second controller over the same collection as
8
+ `/api/online-forms`, and the rows in it are not templates only - a resident's
9
+ submission lives there too. Three of its seven routes carried no session check
10
+ at all, and the other four carried `requireAuth` and nothing more with no
11
+ authorization identifier in the controller.
12
+
13
+ `GET /resident` took `org`, `site` and `userId` from the query string and
14
+ matched on exactly those with no projection, so an unauthenticated caller who
15
+ could name a resident's user id got that resident's whole submission history.
16
+ It now answers only for the caller themselves, or for somebody who reaches the
17
+ estate being asked about.
18
+
19
+ The reads, the update and the archive are decided by `requireFormEntryReach`,
20
+ on the STORED row: the estate through `entitleSite`, or the person whose
21
+ `userId` the row carries - residents hold no `members` document, so the
22
+ owns-it branch is required or every resident loses their own submissions. The
23
+ list closes both halves of its optional `site`. The update authorises its
24
+ destination as well as its source, drops `userId` so ownership cannot be
25
+ reassigned, and stops a resident approving their own application while leaving
26
+ resubmission working. `POST /resident` stamps `userId` from the session instead
27
+ of the body, and `add` stamps `createdBy` from the session instead of the
28
+ caller's own cookie (which also removes a crash for any request sending cookies
29
+ without a `user`).
package/dist/index.d.ts CHANGED
@@ -10368,6 +10368,7 @@ declare function MFormEntry(value: TFormEntry): {
10368
10368
  unit: string | ObjectId;
10369
10369
  name: string;
10370
10370
  phoneNumber: string;
10371
+ createdBy: any;
10371
10372
  fields: DynamicFormFields;
10372
10373
  status: FormEntryStatus | undefined;
10373
10374
  org: string | ObjectId | undefined;
package/dist/index.js CHANGED
@@ -68057,6 +68057,14 @@ function useBulletinVideoController() {
68057
68057
  getBulletinVideoById: _getBulletinVideoById,
68058
68058
  deleteBulletinVideoById: _deleteBulletinVideoById
68059
68059
  } = useBulletinVideoRepo();
68060
+ async function requireBulletinVideoReach(req, id) {
68061
+ const video = await _getBulletinVideoById(id);
68062
+ const { canReach } = await siteReachOf(req);
68063
+ if (!await canReach(video)) {
68064
+ throw new import_node_server_utils203.NotFoundError("Bulletin video not found.");
68065
+ }
68066
+ return video;
68067
+ }
68060
68068
  async function add(req, res, next) {
68061
68069
  try {
68062
68070
  const { error, value } = schemaBulletinVideo.validate(req.body, {
@@ -68068,6 +68076,7 @@ function useBulletinVideoController() {
68068
68076
  next(new import_node_server_utils203.BadRequestError(messages));
68069
68077
  return;
68070
68078
  }
68079
+ await requireSiteReach(req, value.site);
68071
68080
  const data = await _add(value);
68072
68081
  res.status(201).json(data);
68073
68082
  return;
@@ -68102,6 +68111,7 @@ function useBulletinVideoController() {
68102
68111
  [sort ?? "_id" /* ID */]: order === "asc" /* ASC */ ? 1 : -1
68103
68112
  };
68104
68113
  try {
68114
+ await requireSiteReach(req, site);
68105
68115
  const data = await _getAll({
68106
68116
  search,
68107
68117
  page,
@@ -68131,7 +68141,7 @@ function useBulletinVideoController() {
68131
68141
  return;
68132
68142
  }
68133
68143
  const { _id } = value;
68134
- const data = await _getBulletinVideoById(_id);
68144
+ const data = await requireBulletinVideoReach(req, _id);
68135
68145
  res.status(200).json(data);
68136
68146
  return;
68137
68147
  } catch (error) {
@@ -68153,6 +68163,7 @@ function useBulletinVideoController() {
68153
68163
  return;
68154
68164
  }
68155
68165
  try {
68166
+ await requireBulletinVideoReach(req, _id);
68156
68167
  const result = await _updateBulletinVideoById(_id, req.body);
68157
68168
  res.status(200).json({ message: result });
68158
68169
  return;
@@ -68174,6 +68185,7 @@ function useBulletinVideoController() {
68174
68185
  return;
68175
68186
  }
68176
68187
  const { _id } = value;
68188
+ await requireBulletinVideoReach(req, _id);
68177
68189
  await _deleteBulletinVideoById(_id);
68178
68190
  res.status(200).json({ message: "Successfully deleted bulletin video." });
68179
68191
  return;
@@ -85528,6 +85540,14 @@ function MFormEntry(value) {
85528
85540
  unit: value.unit,
85529
85541
  name: value.name,
85530
85542
  phoneNumber: value.phoneNumber,
85543
+ // `createdBy` is `required` on the schema above but was dropped here, so
85544
+ // the author was validated and then thrown away - and the value being
85545
+ // validated came off the caller's own `user` cookie anyway. It is stamped
85546
+ // from the session now, so it is worth keeping, and keeping it is what
85547
+ // makes a row written through `/api/form-entries` carry the same author
85548
+ // field as a row written through `/api/online-forms` into the very same
85549
+ // collection.
85550
+ createdBy: value.createdBy,
85531
85551
  fields: value.fields,
85532
85552
  status: value.status,
85533
85553
  org: value.org,
@@ -85824,6 +85844,16 @@ function useFormEntryController() {
85824
85844
  addResidentForm: _addResidentForm,
85825
85845
  residentForm: _residentForm
85826
85846
  } = useFormEntryRepo();
85847
+ async function requireFormEntryReach(req, id) {
85848
+ const entry = await _getFormEntryById(id);
85849
+ const { actor, canReach } = await siteReachOf(req);
85850
+ const owner = entry?.userId?.toString?.() ?? "";
85851
+ const isOwner = Boolean(actor.id) && owner === actor.id;
85852
+ if (!isOwner && !await canReach(entry)) {
85853
+ throw new import_node_server_utils291.NotFoundError("Document not found.");
85854
+ }
85855
+ return { entry, isOwner, actor };
85856
+ }
85827
85857
  function toCamelCase(str) {
85828
85858
  return str.replace(/\s(.)/g, (_, char) => char.toUpperCase()).replace(/\s+/g, "").replace(/^(.)/, (_, char) => char.toLowerCase());
85829
85859
  }
@@ -85903,6 +85933,13 @@ function useFormEntryController() {
85903
85933
  return;
85904
85934
  }
85905
85935
  const { search, page, limit, status, org, site } = value;
85936
+ if (site) {
85937
+ await requireSiteReach(req, site);
85938
+ } else {
85939
+ const { actor } = await siteReachOf(req);
85940
+ if (!actor.isSuperAdmin)
85941
+ throw new import_node_server_utils291.UnauthorizedError("Not authorized.");
85942
+ }
85906
85943
  const data = await _getAll({ search, page, limit, status, org, site });
85907
85944
  res.json(data);
85908
85945
  return;
@@ -85925,8 +85962,8 @@ function useFormEntryController() {
85925
85962
  return;
85926
85963
  }
85927
85964
  const { _id } = value;
85928
- const data = await _getFormEntryById(_id);
85929
- res.json(data);
85965
+ const { entry } = await requireFormEntryReach(req, _id);
85966
+ res.json(entry);
85930
85967
  return;
85931
85968
  } catch (error) {
85932
85969
  import_node_server_utils291.logger.log({ level: "error", message: error.message });
@@ -85947,6 +85984,13 @@ function useFormEntryController() {
85947
85984
  return;
85948
85985
  }
85949
85986
  const { _id, ...rest } = value;
85987
+ const { isOwner } = await requireFormEntryReach(req, _id);
85988
+ if (rest.site)
85989
+ await requireSiteReach(req, rest.site);
85990
+ delete rest.userId;
85991
+ if (isOwner && rest.status && rest.status !== "pending") {
85992
+ throw new import_node_server_utils291.UnauthorizedError("Not authorized.");
85993
+ }
85950
85994
  await _updateFormEntryById(_id, rest);
85951
85995
  res.json({ message: "Successfully updated online form." });
85952
85996
  return;
@@ -85966,6 +86010,7 @@ function useFormEntryController() {
85966
86010
  next(new import_node_server_utils291.BadRequestError(error.message));
85967
86011
  return;
85968
86012
  }
86013
+ await requireFormEntryReach(req, _id);
85969
86014
  await _deleteOnlineFormById(_id);
85970
86015
  res.json({ message: "Successfully deleted online form." });
85971
86016
  return;
@@ -85976,11 +86021,7 @@ function useFormEntryController() {
85976
86021
  }
85977
86022
  }
85978
86023
  async function add(req, res, next) {
85979
- const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
85980
- (acc, [key, value]) => ({ ...acc, [key]: value }),
85981
- {}
85982
- );
85983
- const createdBy = cookies?.["user"].toString() ?? "";
86024
+ const createdBy = callerId(req);
85984
86025
  const payload = { ...req.body, createdBy };
85985
86026
  const { error } = schemaFormEntry.validate(payload, {
85986
86027
  abortEarly: false
@@ -85992,6 +86033,7 @@ function useFormEntryController() {
85992
86033
  return;
85993
86034
  }
85994
86035
  try {
86036
+ await requireSiteReach(req, payload.site);
85995
86037
  const data = await _add(payload);
85996
86038
  res.status(201).json({ message: data });
85997
86039
  return;
@@ -86002,7 +86044,12 @@ function useFormEntryController() {
86002
86044
  }
86003
86045
  }
86004
86046
  async function residentFormSubmission(req, res, next) {
86005
- const payload = { ...req.body };
86047
+ const userId = callerId(req);
86048
+ if (!userId) {
86049
+ next(new import_node_server_utils291.UnauthorizedError("Not authorized."));
86050
+ return;
86051
+ }
86052
+ const payload = { ...req.body, userId };
86006
86053
  const { error } = residentFormEntry.validate(payload, {
86007
86054
  abortEarly: false
86008
86055
  });
@@ -86042,6 +86089,10 @@ function useFormEntryController() {
86042
86089
  return;
86043
86090
  }
86044
86091
  const { site, userId, org, search, page, limit } = value;
86092
+ const { actor } = await siteReachOf(req);
86093
+ if (!actor.id || actor.id !== userId) {
86094
+ await requireSiteReach(req, site);
86095
+ }
86045
86096
  const result = await _residentForm({ userId, site, org, search, page, limit });
86046
86097
  res.json(result);
86047
86098
  } catch (error) {