@7365admin1/core 3.48.1-staging.192 → 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.
- package/.changeset/form-entry-scope.md +29 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +47 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +60 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/form-entry-scope.e2e.test.mjs +621 -0
- package/test/e2e/harness.mjs +28 -0
|
@@ -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
|
@@ -85528,6 +85528,14 @@ function MFormEntry(value) {
|
|
|
85528
85528
|
unit: value.unit,
|
|
85529
85529
|
name: value.name,
|
|
85530
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,
|
|
85531
85539
|
fields: value.fields,
|
|
85532
85540
|
status: value.status,
|
|
85533
85541
|
org: value.org,
|
|
@@ -85824,6 +85832,16 @@ function useFormEntryController() {
|
|
|
85824
85832
|
addResidentForm: _addResidentForm,
|
|
85825
85833
|
residentForm: _residentForm
|
|
85826
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
|
+
}
|
|
85827
85845
|
function toCamelCase(str) {
|
|
85828
85846
|
return str.replace(/\s(.)/g, (_, char) => char.toUpperCase()).replace(/\s+/g, "").replace(/^(.)/, (_, char) => char.toLowerCase());
|
|
85829
85847
|
}
|
|
@@ -85903,6 +85921,13 @@ function useFormEntryController() {
|
|
|
85903
85921
|
return;
|
|
85904
85922
|
}
|
|
85905
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
|
+
}
|
|
85906
85931
|
const data = await _getAll({ search, page, limit, status, org, site });
|
|
85907
85932
|
res.json(data);
|
|
85908
85933
|
return;
|
|
@@ -85925,8 +85950,8 @@ function useFormEntryController() {
|
|
|
85925
85950
|
return;
|
|
85926
85951
|
}
|
|
85927
85952
|
const { _id } = value;
|
|
85928
|
-
const
|
|
85929
|
-
res.json(
|
|
85953
|
+
const { entry } = await requireFormEntryReach(req, _id);
|
|
85954
|
+
res.json(entry);
|
|
85930
85955
|
return;
|
|
85931
85956
|
} catch (error) {
|
|
85932
85957
|
import_node_server_utils291.logger.log({ level: "error", message: error.message });
|
|
@@ -85947,6 +85972,13 @@ function useFormEntryController() {
|
|
|
85947
85972
|
return;
|
|
85948
85973
|
}
|
|
85949
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
|
+
}
|
|
85950
85982
|
await _updateFormEntryById(_id, rest);
|
|
85951
85983
|
res.json({ message: "Successfully updated online form." });
|
|
85952
85984
|
return;
|
|
@@ -85966,6 +85998,7 @@ function useFormEntryController() {
|
|
|
85966
85998
|
next(new import_node_server_utils291.BadRequestError(error.message));
|
|
85967
85999
|
return;
|
|
85968
86000
|
}
|
|
86001
|
+
await requireFormEntryReach(req, _id);
|
|
85969
86002
|
await _deleteOnlineFormById(_id);
|
|
85970
86003
|
res.json({ message: "Successfully deleted online form." });
|
|
85971
86004
|
return;
|
|
@@ -85976,11 +86009,7 @@ function useFormEntryController() {
|
|
|
85976
86009
|
}
|
|
85977
86010
|
}
|
|
85978
86011
|
async function add(req, res, next) {
|
|
85979
|
-
const
|
|
85980
|
-
(acc, [key, value]) => ({ ...acc, [key]: value }),
|
|
85981
|
-
{}
|
|
85982
|
-
);
|
|
85983
|
-
const createdBy = cookies?.["user"].toString() ?? "";
|
|
86012
|
+
const createdBy = callerId(req);
|
|
85984
86013
|
const payload = { ...req.body, createdBy };
|
|
85985
86014
|
const { error } = schemaFormEntry.validate(payload, {
|
|
85986
86015
|
abortEarly: false
|
|
@@ -85992,6 +86021,7 @@ function useFormEntryController() {
|
|
|
85992
86021
|
return;
|
|
85993
86022
|
}
|
|
85994
86023
|
try {
|
|
86024
|
+
await requireSiteReach(req, payload.site);
|
|
85995
86025
|
const data = await _add(payload);
|
|
85996
86026
|
res.status(201).json({ message: data });
|
|
85997
86027
|
return;
|
|
@@ -86002,7 +86032,12 @@ function useFormEntryController() {
|
|
|
86002
86032
|
}
|
|
86003
86033
|
}
|
|
86004
86034
|
async function residentFormSubmission(req, res, next) {
|
|
86005
|
-
const
|
|
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 };
|
|
86006
86041
|
const { error } = residentFormEntry.validate(payload, {
|
|
86007
86042
|
abortEarly: false
|
|
86008
86043
|
});
|
|
@@ -86042,6 +86077,10 @@ function useFormEntryController() {
|
|
|
86042
86077
|
return;
|
|
86043
86078
|
}
|
|
86044
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
|
+
}
|
|
86045
86084
|
const result = await _residentForm({ userId, site, org, search, page, limit });
|
|
86046
86085
|
res.json(result);
|
|
86047
86086
|
} catch (error) {
|