@7365admin1/core 3.48.1-staging.170 → 3.48.1-staging.171
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/site-soa-scope.md +30 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +27 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +27 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +27 -0
- package/test/e2e/site-soa-scope.e2e.test.mjs +548 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the statement of account routes to the estate and the unit, and take the requester and approver from the session
|
|
6
|
+
|
|
7
|
+
Eight of the nine routes on `/api/site-soa` carried `requireAuth` and nothing
|
|
8
|
+
more, and `site-soa.controller.ts` held no authorization identifier of any kind.
|
|
9
|
+
A statement of account is one unit's ledger for a period, so any signed-in
|
|
10
|
+
account on the platform could read another estate's statements, restate one,
|
|
11
|
+
approve or reject it, move it to another estate, or delete it.
|
|
12
|
+
|
|
13
|
+
`GET /user` is named for the signed-in resident but took `site` and `unitId`
|
|
14
|
+
from the query and identified the caller not at all, so a resident could read a
|
|
15
|
+
neighbour's statement of account by changing one id in a URL. It now requires
|
|
16
|
+
`unitId` and checks it against a live `people` row joining the caller to that
|
|
17
|
+
unit, via `requireOwnUnit`.
|
|
18
|
+
|
|
19
|
+
`add` set `createdBy`, and `reviewSOA` set `approvedBy._id`, by parsing the raw
|
|
20
|
+
`user` cookie off the request, so a statement could be requested under another
|
|
21
|
+
resident's name and approved under another person's. Both now come from
|
|
22
|
+
`callerId(req)`.
|
|
23
|
+
|
|
24
|
+
Adds `requireStatementReach` plus an uncached `getRawStatementOfAccountById`.
|
|
25
|
+
`PUT /id/:id` checks both ends, because `schemaUpdateStatementOfAccount` accepts
|
|
26
|
+
`site` and `updateById` writes it.
|
|
27
|
+
|
|
28
|
+
`GET /generate-pdf` is deliberately unchanged: it is the platform's PDF service,
|
|
29
|
+
called from every web app by `@7365admin1/layer-common`'s `usePDFDownload`, and
|
|
30
|
+
it is authorized by forwarding the caller's own session to the headless browser.
|
package/dist/index.d.ts
CHANGED
|
@@ -7204,6 +7204,7 @@ declare function useStatementOfAccountRepo(): {
|
|
|
7204
7204
|
dateTo?: string | undefined;
|
|
7205
7205
|
}, session?: ClientSession) => Promise<{}>;
|
|
7206
7206
|
getById: (_id: string | ObjectId) => Promise<TStatementOfAccount | null>;
|
|
7207
|
+
getRawStatementOfAccountById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
|
|
7207
7208
|
updateById: (_id: string | ObjectId, value: Partial<TStatementOfAccount>, session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
|
|
7208
7209
|
deleteById: (_id: string | ObjectId) => Promise<number>;
|
|
7209
7210
|
updateStatusById: (_id: string | ObjectId, value: Pick<TStatementOfAccount, "status">, session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
|
|
@@ -7227,6 +7228,43 @@ declare function useStatementOfAccountRepo(): {
|
|
|
7227
7228
|
createIndexes: () => Promise<string>;
|
|
7228
7229
|
};
|
|
7229
7230
|
|
|
7231
|
+
/**
|
|
7232
|
+
* Who may see, or change, a statement of account.
|
|
7233
|
+
*
|
|
7234
|
+
* A statement of account is one unit's ledger for a period - every charge and
|
|
7235
|
+
* every payment on a household's account, requested by the resident and
|
|
7236
|
+
* approved by the estate. It is money, and on the resident side it is somebody
|
|
7237
|
+
* else's household finances.
|
|
7238
|
+
*
|
|
7239
|
+
* Eight of the nine routes on `/api/site-soa` carried `requireAuth` and nothing
|
|
7240
|
+
* more, and this controller held no authorization identifier of any kind, so
|
|
7241
|
+
* being signed in ANYWHERE on the platform was enough to read another estate's
|
|
7242
|
+
* statements, restate one, approve or reject it, move it to another estate, or
|
|
7243
|
+
* delete it.
|
|
7244
|
+
*
|
|
7245
|
+
* Three distinct problems, and they need three different answers.
|
|
7246
|
+
*
|
|
7247
|
+
* 1. THE ESTATE SIDE. `POST /` and `GET /` name a site, so the guard checks the
|
|
7248
|
+
* site the request already names. The four record routes name only a record,
|
|
7249
|
+
* so the site is read off the STORED row via `requireStatementReach`, and a
|
|
7250
|
+
* `site` in the caller's own body cannot stand in for it. `PUT /id/:id` CAN
|
|
7251
|
+
* move a statement - `schemaUpdateStatementOfAccount` accepts `site` and
|
|
7252
|
+
* `updateById` writes it - so both ends are checked.
|
|
7253
|
+
*
|
|
7254
|
+
* 2. THE RESIDENT SIDE. `GET /user` is named for the signed-in resident but
|
|
7255
|
+
* took `site` and `unitId` from the QUERY and identified the caller not at
|
|
7256
|
+
* all, so it was never "my statements": it was any unit's, at any estate.
|
|
7257
|
+
* `requireOwnUnit` asks the question the data already answers - is there a
|
|
7258
|
+
* live `people` row joining this caller to this unit at this site?
|
|
7259
|
+
*
|
|
7260
|
+
* 3. IDENTITY FROM THE REQUEST. `add` set `createdBy` and `reviewSOA` set
|
|
7261
|
+
* `approvedBy._id` by parsing the raw `user` COOKIE off the request, so a
|
|
7262
|
+
* statement could be requested under another resident's name and, worse,
|
|
7263
|
+
* APPROVED under another person's. Both now come from `callerId(req)`.
|
|
7264
|
+
*
|
|
7265
|
+
* `GET /generate-pdf` is deliberately NOT scoped here - see the note on the
|
|
7266
|
+
* handler.
|
|
7267
|
+
*/
|
|
7230
7268
|
declare function useStatementOfAccountController(): {
|
|
7231
7269
|
add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
7232
7270
|
getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -67762,11 +67762,20 @@ function useStatementOfAccountRepo() {
|
|
|
67762
67762
|
throw error;
|
|
67763
67763
|
}
|
|
67764
67764
|
}
|
|
67765
|
+
async function getRawStatementOfAccountById(_id) {
|
|
67766
|
+
try {
|
|
67767
|
+
_id = new import_mongodb127.ObjectId(_id);
|
|
67768
|
+
} catch (error) {
|
|
67769
|
+
throw new import_node_server_utils200.BadRequestError("Invalid statement of account ID format.");
|
|
67770
|
+
}
|
|
67771
|
+
return await collection.findOne({ _id });
|
|
67772
|
+
}
|
|
67765
67773
|
return {
|
|
67766
67774
|
createTextIndex,
|
|
67767
67775
|
add,
|
|
67768
67776
|
getAll,
|
|
67769
67777
|
getById,
|
|
67778
|
+
getRawStatementOfAccountById,
|
|
67770
67779
|
updateById,
|
|
67771
67780
|
deleteById,
|
|
67772
67781
|
updateStatusById,
|
|
@@ -67947,19 +67956,20 @@ function useStatementOfAccountController() {
|
|
|
67947
67956
|
updateById: _updateById,
|
|
67948
67957
|
deleteById: _deleteById,
|
|
67949
67958
|
updateStatusById: _updateStatusById,
|
|
67950
|
-
getResidentUserSoa: _getResidentUserSoa
|
|
67959
|
+
getResidentUserSoa: _getResidentUserSoa,
|
|
67960
|
+
getRawStatementOfAccountById: _getRawStatementOfAccountById
|
|
67951
67961
|
} = useStatementOfAccountRepo();
|
|
67952
67962
|
const {
|
|
67953
67963
|
add: _add,
|
|
67954
67964
|
generatePDF: _generatePDF,
|
|
67955
67965
|
reviewSOA: _reviewSOA
|
|
67956
67966
|
} = useStatementOfAccountService();
|
|
67967
|
+
async function requireStatementReach(req, _id) {
|
|
67968
|
+
const row = await _getRawStatementOfAccountById(_id);
|
|
67969
|
+
await requireSiteReach(req, row?.site);
|
|
67970
|
+
}
|
|
67957
67971
|
async function add(req, res, next) {
|
|
67958
|
-
|
|
67959
|
-
(acc, [key, value]) => ({ ...acc, [key]: value }),
|
|
67960
|
-
{}
|
|
67961
|
-
);
|
|
67962
|
-
req.body.createdBy = cookies?.["user"] ? cookies["user"].toString() : req.body.createdBy;
|
|
67972
|
+
req.body.createdBy = callerId(req);
|
|
67963
67973
|
const data = { ...req.body };
|
|
67964
67974
|
const validation = import_joi104.default.object({
|
|
67965
67975
|
dateFrom: import_joi104.default.string().required(),
|
|
@@ -67982,6 +67992,7 @@ function useStatementOfAccountController() {
|
|
|
67982
67992
|
throw new import_node_server_utils202.BadRequestError(queryError.message);
|
|
67983
67993
|
}
|
|
67984
67994
|
try {
|
|
67995
|
+
await requireSiteReach(req, bodyValue.site);
|
|
67985
67996
|
const soa = await _add(bodyValue, queryValue.dateFrom, queryValue.dateTo);
|
|
67986
67997
|
res.status(201).json({
|
|
67987
67998
|
message: "SOA added successfully.",
|
|
@@ -68030,6 +68041,7 @@ function useStatementOfAccountController() {
|
|
|
68030
68041
|
}
|
|
68031
68042
|
});
|
|
68032
68043
|
try {
|
|
68044
|
+
await requireSiteReach(req, req.query.site);
|
|
68033
68045
|
const data = await _getAll({
|
|
68034
68046
|
search,
|
|
68035
68047
|
page,
|
|
@@ -68059,6 +68071,7 @@ function useStatementOfAccountController() {
|
|
|
68059
68071
|
return;
|
|
68060
68072
|
}
|
|
68061
68073
|
try {
|
|
68074
|
+
await requireStatementReach(req, id);
|
|
68062
68075
|
const soa = await _getById(id);
|
|
68063
68076
|
res.json({
|
|
68064
68077
|
message: "Successfully retrieved soa.",
|
|
@@ -68082,6 +68095,9 @@ function useStatementOfAccountController() {
|
|
|
68082
68095
|
return;
|
|
68083
68096
|
}
|
|
68084
68097
|
try {
|
|
68098
|
+
await requireStatementReach(req, _id);
|
|
68099
|
+
if (req.body?.site)
|
|
68100
|
+
await requireSiteReach(req, req.body.site);
|
|
68085
68101
|
const result = await _updateById(_id, req.body);
|
|
68086
68102
|
res.status(200).json({ message: result });
|
|
68087
68103
|
return;
|
|
@@ -68101,6 +68117,7 @@ function useStatementOfAccountController() {
|
|
|
68101
68117
|
return;
|
|
68102
68118
|
}
|
|
68103
68119
|
try {
|
|
68120
|
+
await requireStatementReach(req, _id);
|
|
68104
68121
|
await _deleteById(_id);
|
|
68105
68122
|
res.status(200).json({ message: "Successfully deleted site SOA." });
|
|
68106
68123
|
return;
|
|
@@ -68192,6 +68209,7 @@ function useStatementOfAccountController() {
|
|
|
68192
68209
|
try {
|
|
68193
68210
|
const _id = req.params.id;
|
|
68194
68211
|
const status = req.params.status;
|
|
68212
|
+
await requireStatementReach(req, _id);
|
|
68195
68213
|
const result = await _updateStatusById(_id, { status });
|
|
68196
68214
|
res.status(200).json({ message: result });
|
|
68197
68215
|
return;
|
|
@@ -68233,6 +68251,7 @@ function useStatementOfAccountController() {
|
|
|
68233
68251
|
const unitId = req.query.unitId;
|
|
68234
68252
|
const category = req.query.category ?? "";
|
|
68235
68253
|
try {
|
|
68254
|
+
await requireOwnUnit(req, site, unitId);
|
|
68236
68255
|
const data = await _getResidentUserSoa({
|
|
68237
68256
|
search,
|
|
68238
68257
|
page,
|
|
@@ -68253,15 +68272,10 @@ function useStatementOfAccountController() {
|
|
|
68253
68272
|
}
|
|
68254
68273
|
}
|
|
68255
68274
|
async function reviewSOA(req, res, next) {
|
|
68256
|
-
const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
|
|
68257
|
-
(acc, [key, value]) => ({ ...acc, [key]: value }),
|
|
68258
|
-
{}
|
|
68259
|
-
);
|
|
68260
68275
|
req.body.approvedBy = {
|
|
68261
|
-
_id:
|
|
68276
|
+
_id: callerId(req),
|
|
68262
68277
|
name: ""
|
|
68263
68278
|
};
|
|
68264
|
-
req.body.approvedBy._id = cookies?.["user"] ? cookies["user"].toString() : req.body.approvedBy._id;
|
|
68265
68279
|
const _id = req.params.id;
|
|
68266
68280
|
const payload = { _id, ...req.body };
|
|
68267
68281
|
const schema2 = import_joi104.default.object({
|
|
@@ -68277,6 +68291,7 @@ function useStatementOfAccountController() {
|
|
|
68277
68291
|
return;
|
|
68278
68292
|
}
|
|
68279
68293
|
try {
|
|
68294
|
+
await requireStatementReach(req, _id);
|
|
68280
68295
|
const result = await _reviewSOA(_id, req.body);
|
|
68281
68296
|
res.status(200).json({ message: result });
|
|
68282
68297
|
return;
|