@7365admin1/core 3.48.1-staging.168 → 3.48.1-staging.169
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/provider-billing-org-scope.md +21 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +19 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +19 -0
- package/test/e2e/provider-billing-scope.e2e.test.mjs +360 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the service provider billing routes to the organisation
|
|
6
|
+
|
|
7
|
+
`/api/provider-billings` carried `requireAuth` and nothing more on all five
|
|
8
|
+
handlers, and `service-provider-billing.controller.ts` held no authorization
|
|
9
|
+
identifier of any kind. A service provider billing record is what a contractor
|
|
10
|
+
invoices an organisation for, so any signed-in account on the platform could
|
|
11
|
+
read another organisation's invoices, swap the attachments that evidence one,
|
|
12
|
+
flip its status, add an acknowledgement that was never given, or delete it.
|
|
13
|
+
|
|
14
|
+
The scope here is the ORGANISATION, not the estate: every row carries `orgId`
|
|
15
|
+
and no `site`, so the rule is `requireOrgAccess` — Seven365 staff, or a live
|
|
16
|
+
member of that organisation — not `requireSiteReach`.
|
|
17
|
+
|
|
18
|
+
Adds `requireServiceProviderBillingReach`, plus an uncached
|
|
19
|
+
`getRawServiceProviderBillingById` so the check is not decided from a 15-minute
|
|
20
|
+
cache entry (the existing reader is cached and throws `NotFoundError`, which
|
|
21
|
+
would also answer before the guard ran).
|
package/dist/index.d.ts
CHANGED
|
@@ -5438,6 +5438,7 @@ declare function useServiceProviderBillingRepo(): {
|
|
|
5438
5438
|
pageRange: string;
|
|
5439
5439
|
} | TServiceProviderBilling>;
|
|
5440
5440
|
getSiteServiceProviderBillingById: (_id: string | ObjectId, session?: ClientSession) => Promise<mongodb.WithId<bson.Document> | TServiceProviderBilling>;
|
|
5441
|
+
getRawServiceProviderBillingById: (_id: string | ObjectId) => Promise<mongodb.WithId<bson.Document> | null>;
|
|
5441
5442
|
updateServiceProviderBillingById: (_id: ObjectId | string, value: Partial<TServiceProviderBilling>, session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
|
|
5442
5443
|
deleteServiceProviderBillingById: (_id: string | ObjectId) => Promise<number>;
|
|
5443
5444
|
createIndexes: () => Promise<void>;
|
|
@@ -5448,6 +5449,36 @@ declare function useServiceProviderBillingService(): {
|
|
|
5448
5449
|
updateServiceProviderBillingById: (id: string | ObjectId, value: Partial<TServiceProviderBilling>) => Promise<string>;
|
|
5449
5450
|
};
|
|
5450
5451
|
|
|
5452
|
+
/**
|
|
5453
|
+
* Who may see, or change, a service provider's billing records.
|
|
5454
|
+
*
|
|
5455
|
+
* A service provider billing record is what a contractor invoices an
|
|
5456
|
+
* organisation for - the amount, the period, the attachments that evidence it,
|
|
5457
|
+
* and the people who acknowledged it. It is money.
|
|
5458
|
+
*
|
|
5459
|
+
* All five routes on `/api/provider-billings` carried `requireAuth` and nothing
|
|
5460
|
+
* more, and this controller held no authorization identifier of any kind, so
|
|
5461
|
+
* being signed in ANYWHERE on the platform was enough to read another
|
|
5462
|
+
* organisation's invoices, restate one, replace its evidence, or delete it.
|
|
5463
|
+
*
|
|
5464
|
+
* The scope here is the ORGANISATION, not the estate. Every row carries
|
|
5465
|
+
* `orgId` and no `site` - `getAll` is asked for an `org`, and the repository
|
|
5466
|
+
* builds `{ orgId: org, status }`. So the rule is `requireOrgAccess`, the same
|
|
5467
|
+
* one `/api/customer-sites` and `/api/members` already use: Seven365 staff, or
|
|
5468
|
+
* a live member of that organisation. Using `requireSiteReach` here would be
|
|
5469
|
+
* asking the wrong question of the wrong field.
|
|
5470
|
+
*
|
|
5471
|
+
* - `POST /` already REQUIRES `orgId` in the body and `GET /` already REQUIRES
|
|
5472
|
+
* `org` in the query, so the guard checks the organisation the request
|
|
5473
|
+
* already names and no caller's contract changes.
|
|
5474
|
+
* - The three record routes name only a record, so the organisation is read
|
|
5475
|
+
* off the STORED row via `requireServiceProviderBillingReach`, and an
|
|
5476
|
+
* `orgId` in the caller's own body cannot stand in for it.
|
|
5477
|
+
*
|
|
5478
|
+
* `PUT /id/:id` cannot MOVE a record between organisations:
|
|
5479
|
+
* `schemaUpdateServiceProviderBilling` does not accept `orgId` at all, so there
|
|
5480
|
+
* is no destination to check.
|
|
5481
|
+
*/
|
|
5451
5482
|
declare function useServiceProviderBillingController(): {
|
|
5452
5483
|
add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
5453
5484
|
getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -54331,10 +54331,19 @@ function useServiceProviderBillingRepo() {
|
|
|
54331
54331
|
throw error;
|
|
54332
54332
|
}
|
|
54333
54333
|
}
|
|
54334
|
+
async function getRawServiceProviderBillingById(_id) {
|
|
54335
|
+
try {
|
|
54336
|
+
_id = new import_mongodb98.ObjectId(_id);
|
|
54337
|
+
} catch (error) {
|
|
54338
|
+
throw new import_node_server_utils158.BadRequestError("Invalid service provider billing ID format.");
|
|
54339
|
+
}
|
|
54340
|
+
return await collection.findOne({ _id });
|
|
54341
|
+
}
|
|
54334
54342
|
return {
|
|
54335
54343
|
add,
|
|
54336
54344
|
getAll,
|
|
54337
54345
|
getSiteServiceProviderBillingById,
|
|
54346
|
+
getRawServiceProviderBillingById,
|
|
54338
54347
|
updateServiceProviderBillingById,
|
|
54339
54348
|
deleteServiceProviderBillingById,
|
|
54340
54349
|
createIndexes
|
|
@@ -54393,8 +54402,13 @@ function useServiceProviderBillingController() {
|
|
|
54393
54402
|
const {
|
|
54394
54403
|
getAll: _getAll,
|
|
54395
54404
|
getSiteServiceProviderBillingById: _getServiceProviderBillingById,
|
|
54405
|
+
getRawServiceProviderBillingById: _getRawServiceProviderBillingById,
|
|
54396
54406
|
deleteServiceProviderBillingById: _deleteServiceProviderBillingById
|
|
54397
54407
|
} = useServiceProviderBillingRepo();
|
|
54408
|
+
async function requireServiceProviderBillingReach(req, _id) {
|
|
54409
|
+
const row = await _getRawServiceProviderBillingById(_id);
|
|
54410
|
+
await requireOrgAccess(req, row?.orgId?.toString());
|
|
54411
|
+
}
|
|
54398
54412
|
async function add(req, res, next) {
|
|
54399
54413
|
const payload = { ...req.body };
|
|
54400
54414
|
const { error } = schemaServiceProviderBilling.validate(payload, {
|
|
@@ -54407,6 +54421,7 @@ function useServiceProviderBillingController() {
|
|
|
54407
54421
|
return;
|
|
54408
54422
|
}
|
|
54409
54423
|
try {
|
|
54424
|
+
await requireOrgAccess(req, payload.orgId);
|
|
54410
54425
|
const data = await _add(payload);
|
|
54411
54426
|
res.status(201).json(data);
|
|
54412
54427
|
return;
|
|
@@ -54453,6 +54468,7 @@ function useServiceProviderBillingController() {
|
|
|
54453
54468
|
}
|
|
54454
54469
|
});
|
|
54455
54470
|
try {
|
|
54471
|
+
await requireOrgAccess(req, org);
|
|
54456
54472
|
const data = await _getAll({
|
|
54457
54473
|
search,
|
|
54458
54474
|
page,
|
|
@@ -54479,6 +54495,7 @@ function useServiceProviderBillingController() {
|
|
|
54479
54495
|
return;
|
|
54480
54496
|
}
|
|
54481
54497
|
try {
|
|
54498
|
+
await requireServiceProviderBillingReach(req, _id);
|
|
54482
54499
|
const data = await _getServiceProviderBillingById(_id);
|
|
54483
54500
|
res.status(200).json(data);
|
|
54484
54501
|
return;
|
|
@@ -54501,6 +54518,7 @@ function useServiceProviderBillingController() {
|
|
|
54501
54518
|
return;
|
|
54502
54519
|
}
|
|
54503
54520
|
try {
|
|
54521
|
+
await requireServiceProviderBillingReach(req, _id);
|
|
54504
54522
|
const result = await _updateServiceProviderBillingById(_id, req.body);
|
|
54505
54523
|
res.status(200).json({ message: result });
|
|
54506
54524
|
return;
|
|
@@ -54520,6 +54538,7 @@ function useServiceProviderBillingController() {
|
|
|
54520
54538
|
return;
|
|
54521
54539
|
}
|
|
54522
54540
|
try {
|
|
54541
|
+
await requireServiceProviderBillingReach(req, _id);
|
|
54523
54542
|
await _deleteServiceProviderBillingById(_id);
|
|
54524
54543
|
res.status(200).json({ message: "Successfully deleted service provider billing." });
|
|
54525
54544
|
return;
|