@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.
@@ -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
- const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
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;