@7365admin1/core 3.48.1-staging.191 → 3.48.1-staging.193

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,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`).
@@ -0,0 +1,15 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Scope the online form routes to the estate
6
+
7
+ All six `/api/online-forms` routes carried `requireAuth` and nothing more, so any
8
+ signed-in account on the platform could read, rewrite, move or archive another
9
+ estate's form templates. The controller now takes its verdict from the session:
10
+ a new `requireOnlineFormReach` for the by-id read, update and delete, a site
11
+ check on the add and on the by-site list, both tenancy filters closed on the
12
+ list query, and the update's destination `site` checked as well as the source.
13
+ `createdBy`/`updatedBy` are stamped from the session instead of the caller's
14
+ `user` cookie, and `getOnlineFormsBySiteId` now actually applies the estate
15
+ filter it was given.
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
@@ -73090,8 +73090,9 @@ function useOnlineFormRepo() {
73090
73090
  query2.$or = [{ name: { $regex: search, $options: "i" } }];
73091
73091
  cacheOptions.search = search;
73092
73092
  }
73093
+ query2.site = site;
73093
73094
  cacheOptions.site = site;
73094
- const cacheKey = (0, import_node_server_utils227.makeCacheKey)(namespace_collection, { site });
73095
+ const cacheKey = (0, import_node_server_utils227.makeCacheKey)(namespace_collection, cacheOptions);
73095
73096
  const cachedData = await getCache(cacheKey);
73096
73097
  if (cachedData) {
73097
73098
  import_node_server_utils227.logger.info(`Cache hit for key: ${cacheKey}`);
@@ -73147,12 +73148,16 @@ function useOnlineFormController() {
73147
73148
  deleteOnlineFormById: _deleteOnlineFormById,
73148
73149
  getOnlineFormsBySiteId: _getOnlineFormsBySiteId
73149
73150
  } = useOnlineFormRepo();
73151
+ async function requireOnlineFormReach(req, id) {
73152
+ const form = await _getOnlineFormById(id);
73153
+ const { canReach } = await siteReachOf(req);
73154
+ if (!await canReach(form)) {
73155
+ throw new import_node_server_utils228.NotFoundError("Document not found.");
73156
+ }
73157
+ return form;
73158
+ }
73150
73159
  async function add(req, res, next) {
73151
- const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
73152
- (acc, [key, value]) => ({ ...acc, [key]: value }),
73153
- {}
73154
- );
73155
- const createdBy = cookies?.["user"].toString() ?? "";
73160
+ const createdBy = callerId(req);
73156
73161
  const payload = { ...req.body, createdBy };
73157
73162
  const { error } = schemaOnlineForm.validate(payload, {
73158
73163
  abortEarly: false
@@ -73164,6 +73169,7 @@ function useOnlineFormController() {
73164
73169
  return;
73165
73170
  }
73166
73171
  try {
73172
+ await requireSiteReach(req, payload.site);
73167
73173
  const data = await _add(payload);
73168
73174
  res.status(201).json({ message: data });
73169
73175
  return;
@@ -73197,6 +73203,15 @@ function useOnlineFormController() {
73197
73203
  const org = req.query.org ?? "";
73198
73204
  const site = req.query.site ?? "";
73199
73205
  try {
73206
+ const { actor } = await siteReachOf(req);
73207
+ if (site) {
73208
+ await requireSiteReach(req, site);
73209
+ } else if (!actor.isSuperAdmin) {
73210
+ throw new import_node_server_utils228.UnauthorizedError("Not authorized.");
73211
+ }
73212
+ if (org && !actor.isSuperAdmin && !actor.orgIds.includes(org)) {
73213
+ throw new import_node_server_utils228.UnauthorizedError("Not authorized.");
73214
+ }
73200
73215
  const data = await _getAll({ search, page, limit, status, org, site });
73201
73216
  res.json(data);
73202
73217
  return;
@@ -73216,7 +73231,7 @@ function useOnlineFormController() {
73216
73231
  return;
73217
73232
  }
73218
73233
  try {
73219
- const data = await _getOnlineFormById(_id);
73234
+ const data = await requireOnlineFormReach(req, _id);
73220
73235
  res.json(data);
73221
73236
  return;
73222
73237
  } catch (error2) {
@@ -73243,11 +73258,7 @@ function useOnlineFormController() {
73243
73258
  site: import_joi118.default.string().hex().optional().allow("", null),
73244
73259
  updatedBy: import_joi118.default.string().hex().optional().allow("", null)
73245
73260
  });
73246
- const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
73247
- (acc, [key, value]) => ({ ...acc, [key]: value }),
73248
- {}
73249
- );
73250
- const updatedBy = cookies?.["user"].toString() ?? "";
73261
+ const updatedBy = callerId(req);
73251
73262
  const _id = req.params.id;
73252
73263
  const payload = { ...req.body, updatedBy };
73253
73264
  const { error } = validation.validate({ _id, ...payload });
@@ -73257,6 +73268,16 @@ function useOnlineFormController() {
73257
73268
  return;
73258
73269
  }
73259
73270
  try {
73271
+ await requireOnlineFormReach(req, _id);
73272
+ if (payload.site) {
73273
+ await requireSiteReach(req, payload.site);
73274
+ }
73275
+ if (payload.org) {
73276
+ const { actor } = await siteReachOf(req);
73277
+ if (!actor.isSuperAdmin && !actor.orgIds.includes(payload.org)) {
73278
+ throw new import_node_server_utils228.UnauthorizedError("Not authorized.");
73279
+ }
73280
+ }
73260
73281
  await _updateOnlineFormById(_id, payload);
73261
73282
  res.json({ message: "Successfully updated online form." });
73262
73283
  return;
@@ -73276,6 +73297,7 @@ function useOnlineFormController() {
73276
73297
  return;
73277
73298
  }
73278
73299
  try {
73300
+ await requireOnlineFormReach(req, _id);
73279
73301
  await _deleteOnlineFormById(_id);
73280
73302
  res.json({ message: "Successfully deleted online form." });
73281
73303
  return;
@@ -73297,6 +73319,7 @@ function useOnlineFormController() {
73297
73319
  return;
73298
73320
  }
73299
73321
  try {
73322
+ await requireSiteReach(req, _id);
73300
73323
  const data = await _getOnlineFormsBySiteId(_id, { ...query2 });
73301
73324
  res.json(data);
73302
73325
  return;
@@ -85505,6 +85528,14 @@ function MFormEntry(value) {
85505
85528
  unit: value.unit,
85506
85529
  name: value.name,
85507
85530
  phoneNumber: value.phoneNumber,
85531
+ // `createdBy` is `required` on the schema above but was dropped here, so
85532
+ // the author was validated and then thrown away - and the value being
85533
+ // validated came off the caller's own `user` cookie anyway. It is stamped
85534
+ // from the session now, so it is worth keeping, and keeping it is what
85535
+ // makes a row written through `/api/form-entries` carry the same author
85536
+ // field as a row written through `/api/online-forms` into the very same
85537
+ // collection.
85538
+ createdBy: value.createdBy,
85508
85539
  fields: value.fields,
85509
85540
  status: value.status,
85510
85541
  org: value.org,
@@ -85801,6 +85832,16 @@ function useFormEntryController() {
85801
85832
  addResidentForm: _addResidentForm,
85802
85833
  residentForm: _residentForm
85803
85834
  } = useFormEntryRepo();
85835
+ async function requireFormEntryReach(req, id) {
85836
+ const entry = await _getFormEntryById(id);
85837
+ const { actor, canReach } = await siteReachOf(req);
85838
+ const owner = entry?.userId?.toString?.() ?? "";
85839
+ const isOwner = Boolean(actor.id) && owner === actor.id;
85840
+ if (!isOwner && !await canReach(entry)) {
85841
+ throw new import_node_server_utils291.NotFoundError("Document not found.");
85842
+ }
85843
+ return { entry, isOwner, actor };
85844
+ }
85804
85845
  function toCamelCase(str) {
85805
85846
  return str.replace(/\s(.)/g, (_, char) => char.toUpperCase()).replace(/\s+/g, "").replace(/^(.)/, (_, char) => char.toLowerCase());
85806
85847
  }
@@ -85880,6 +85921,13 @@ function useFormEntryController() {
85880
85921
  return;
85881
85922
  }
85882
85923
  const { search, page, limit, status, org, site } = value;
85924
+ if (site) {
85925
+ await requireSiteReach(req, site);
85926
+ } else {
85927
+ const { actor } = await siteReachOf(req);
85928
+ if (!actor.isSuperAdmin)
85929
+ throw new import_node_server_utils291.UnauthorizedError("Not authorized.");
85930
+ }
85883
85931
  const data = await _getAll({ search, page, limit, status, org, site });
85884
85932
  res.json(data);
85885
85933
  return;
@@ -85902,8 +85950,8 @@ function useFormEntryController() {
85902
85950
  return;
85903
85951
  }
85904
85952
  const { _id } = value;
85905
- const data = await _getFormEntryById(_id);
85906
- res.json(data);
85953
+ const { entry } = await requireFormEntryReach(req, _id);
85954
+ res.json(entry);
85907
85955
  return;
85908
85956
  } catch (error) {
85909
85957
  import_node_server_utils291.logger.log({ level: "error", message: error.message });
@@ -85924,6 +85972,13 @@ function useFormEntryController() {
85924
85972
  return;
85925
85973
  }
85926
85974
  const { _id, ...rest } = value;
85975
+ const { isOwner } = await requireFormEntryReach(req, _id);
85976
+ if (rest.site)
85977
+ await requireSiteReach(req, rest.site);
85978
+ delete rest.userId;
85979
+ if (isOwner && rest.status && rest.status !== "pending") {
85980
+ throw new import_node_server_utils291.UnauthorizedError("Not authorized.");
85981
+ }
85927
85982
  await _updateFormEntryById(_id, rest);
85928
85983
  res.json({ message: "Successfully updated online form." });
85929
85984
  return;
@@ -85943,6 +85998,7 @@ function useFormEntryController() {
85943
85998
  next(new import_node_server_utils291.BadRequestError(error.message));
85944
85999
  return;
85945
86000
  }
86001
+ await requireFormEntryReach(req, _id);
85946
86002
  await _deleteOnlineFormById(_id);
85947
86003
  res.json({ message: "Successfully deleted online form." });
85948
86004
  return;
@@ -85953,11 +86009,7 @@ function useFormEntryController() {
85953
86009
  }
85954
86010
  }
85955
86011
  async function add(req, res, next) {
85956
- const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
85957
- (acc, [key, value]) => ({ ...acc, [key]: value }),
85958
- {}
85959
- );
85960
- const createdBy = cookies?.["user"].toString() ?? "";
86012
+ const createdBy = callerId(req);
85961
86013
  const payload = { ...req.body, createdBy };
85962
86014
  const { error } = schemaFormEntry.validate(payload, {
85963
86015
  abortEarly: false
@@ -85969,6 +86021,7 @@ function useFormEntryController() {
85969
86021
  return;
85970
86022
  }
85971
86023
  try {
86024
+ await requireSiteReach(req, payload.site);
85972
86025
  const data = await _add(payload);
85973
86026
  res.status(201).json({ message: data });
85974
86027
  return;
@@ -85979,7 +86032,12 @@ function useFormEntryController() {
85979
86032
  }
85980
86033
  }
85981
86034
  async function residentFormSubmission(req, res, next) {
85982
- const payload = { ...req.body };
86035
+ const userId = callerId(req);
86036
+ if (!userId) {
86037
+ next(new import_node_server_utils291.UnauthorizedError("Not authorized."));
86038
+ return;
86039
+ }
86040
+ const payload = { ...req.body, userId };
85983
86041
  const { error } = residentFormEntry.validate(payload, {
85984
86042
  abortEarly: false
85985
86043
  });
@@ -86019,6 +86077,10 @@ function useFormEntryController() {
86019
86077
  return;
86020
86078
  }
86021
86079
  const { site, userId, org, search, page, limit } = value;
86080
+ const { actor } = await siteReachOf(req);
86081
+ if (!actor.id || actor.id !== userId) {
86082
+ await requireSiteReach(req, site);
86083
+ }
86022
86084
  const result = await _residentForm({ userId, site, org, search, page, limit });
86023
86085
  res.json(result);
86024
86086
  } catch (error) {