@7365admin1/core 3.48.1-staging.188 → 3.48.1-staging.190
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/invoice-subscription-owner-scope.md +23 -0
- package/.changeset/site-facility-site-scope.md +12 -0
- package/dist/index.js +30 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +205 -167
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +31 -0
- package/test/e2e/invoice-scope.e2e.test.mjs +262 -0
- package/test/e2e/site-facility-scope.e2e.test.mjs +479 -0
- package/test/staff-console-authz.test.mjs +7 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope a subscription's invoices to the subscription's owner.
|
|
6
|
+
|
|
7
|
+
`GET /api/invoices/subscription/:id` carried `requireAuth` and nothing more, and
|
|
8
|
+
`invoice.controller.ts` held no authorization identifier of any kind: the
|
|
9
|
+
subscription id came off the URL and went straight into the query. Being signed
|
|
10
|
+
in anywhere on the platform was therefore enough to page through another
|
|
11
|
+
organisation's billing history — invoice numbers, amounts, line-item
|
|
12
|
+
descriptions, due dates and payment status.
|
|
13
|
+
|
|
14
|
+
A new module-named `requireInvoiceReach` takes the verdict off the STORED
|
|
15
|
+
subscription, never from anything the caller supplied. A subscription is owned
|
|
16
|
+
by an organisation OR by a single user, so both are checked: `siteReachOf`'s
|
|
17
|
+
`canReach` answers the organisation half, and the personal half is the caller
|
|
18
|
+
being that user. A subscription that names neither is refused. "Not yours",
|
|
19
|
+
"does not exist" and "not a valid id" all answer 404 identically, so
|
|
20
|
+
subscription ids cannot be enumerated.
|
|
21
|
+
|
|
22
|
+
Read access to invoice records only — no card handling, no payment gateway and
|
|
23
|
+
no money movement is touched.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the site facility routes to the estate. `/api/site-facilities` carried
|
|
6
|
+
`requireAuth` and nothing else on all six routes: any signed-in account could add a
|
|
7
|
+
facility to another estate, list another client's facilities — or, by omitting the
|
|
8
|
+
optional `site` filter, every estate's facilities at once — and read, rewrite or
|
|
9
|
+
archive any facility by id alone, including its opening hours, booking fees, deposit
|
|
10
|
+
and policies. Reach is now checked on the site named in the request, and on the
|
|
11
|
+
stored row where the request names only an id; the unfiltered cross-estate list is
|
|
12
|
+
left to platform staff.
|
package/dist/index.js
CHANGED
|
@@ -31022,6 +31022,15 @@ var import_node_server_utils82 = require("@7365admin1/node-server-utils");
|
|
|
31022
31022
|
var import_joi39 = __toESM(require("joi"));
|
|
31023
31023
|
function useInvoiceController() {
|
|
31024
31024
|
const { getBySubscriptionId: _getBySubscriptionId } = useInvoiceRepo();
|
|
31025
|
+
const { getById: _getSubscriptionById } = useSubscriptionRepo();
|
|
31026
|
+
async function requireInvoiceReach(req, id) {
|
|
31027
|
+
const subscription = await _getSubscriptionById(id).catch(() => null);
|
|
31028
|
+
const { actor, canReach } = await siteReachOf(req);
|
|
31029
|
+
const owned = await canReach(subscription) || Boolean(actor.id) && Boolean(subscription?.user) && subscription?.user?.toString() === actor.id;
|
|
31030
|
+
if (!owned) {
|
|
31031
|
+
throw new import_node_server_utils82.NotFoundError("Subscription not found.");
|
|
31032
|
+
}
|
|
31033
|
+
}
|
|
31025
31034
|
async function getBySubscriptionId(req, res, next) {
|
|
31026
31035
|
const validation = import_joi39.default.object({
|
|
31027
31036
|
id: import_joi39.default.string().required(),
|
|
@@ -31044,6 +31053,7 @@ function useInvoiceController() {
|
|
|
31044
31053
|
const page = parseInt(req.query.page ?? "1");
|
|
31045
31054
|
const limit = parseInt(req.query.limit ?? "10");
|
|
31046
31055
|
try {
|
|
31056
|
+
await requireInvoiceReach(req, id);
|
|
31047
31057
|
const data = await _getBySubscriptionId({
|
|
31048
31058
|
id,
|
|
31049
31059
|
search,
|
|
@@ -54711,6 +54721,14 @@ function useSiteFacilityController() {
|
|
|
54711
54721
|
getSiteFacilityById: _getSiteFacilityById,
|
|
54712
54722
|
deleteSiteFacilityById: _deleteSiteFacilityById
|
|
54713
54723
|
} = useSiteFacilityRepo();
|
|
54724
|
+
async function requireFacilityReach(req, id) {
|
|
54725
|
+
const facility = await _getSiteFacilityById(id);
|
|
54726
|
+
const { canReach } = await siteReachOf(req);
|
|
54727
|
+
if (!await canReach(facility)) {
|
|
54728
|
+
throw new import_node_server_utils162.NotFoundError("Site facility not found.");
|
|
54729
|
+
}
|
|
54730
|
+
return facility;
|
|
54731
|
+
}
|
|
54714
54732
|
async function add(req, res, next) {
|
|
54715
54733
|
const payload = { ...req.body };
|
|
54716
54734
|
const { error } = schemaSiteFacility.validate(payload, {
|
|
@@ -54723,6 +54741,7 @@ function useSiteFacilityController() {
|
|
|
54723
54741
|
return;
|
|
54724
54742
|
}
|
|
54725
54743
|
try {
|
|
54744
|
+
await requireSiteReach(req, payload.site);
|
|
54726
54745
|
const data = await _add(payload);
|
|
54727
54746
|
res.status(201).json(data);
|
|
54728
54747
|
return;
|
|
@@ -54769,6 +54788,13 @@ function useSiteFacilityController() {
|
|
|
54769
54788
|
}
|
|
54770
54789
|
});
|
|
54771
54790
|
try {
|
|
54791
|
+
if (site) {
|
|
54792
|
+
await requireSiteReach(req, site);
|
|
54793
|
+
} else {
|
|
54794
|
+
const { actor } = await siteReachOf(req);
|
|
54795
|
+
if (!actor.isSuperAdmin)
|
|
54796
|
+
throw new import_node_server_utils162.UnauthorizedError("Not authorized.");
|
|
54797
|
+
}
|
|
54772
54798
|
const data = await _getAll({
|
|
54773
54799
|
search,
|
|
54774
54800
|
page,
|
|
@@ -54795,7 +54821,7 @@ function useSiteFacilityController() {
|
|
|
54795
54821
|
return;
|
|
54796
54822
|
}
|
|
54797
54823
|
try {
|
|
54798
|
-
const data = await
|
|
54824
|
+
const data = await requireFacilityReach(req, _id);
|
|
54799
54825
|
res.status(200).json(data);
|
|
54800
54826
|
return;
|
|
54801
54827
|
} catch (error2) {
|
|
@@ -54817,6 +54843,7 @@ function useSiteFacilityController() {
|
|
|
54817
54843
|
return;
|
|
54818
54844
|
}
|
|
54819
54845
|
try {
|
|
54846
|
+
await requireFacilityReach(req, _id);
|
|
54820
54847
|
const result = await _updateSiteFacilityById(_id, req.body);
|
|
54821
54848
|
res.status(200).json({ message: result });
|
|
54822
54849
|
return;
|
|
@@ -54836,6 +54863,7 @@ function useSiteFacilityController() {
|
|
|
54836
54863
|
return;
|
|
54837
54864
|
}
|
|
54838
54865
|
try {
|
|
54866
|
+
await requireFacilityReach(req, _id);
|
|
54839
54867
|
await _deleteSiteFacilityById(_id);
|
|
54840
54868
|
res.status(200).json({ message: "Successfully deleted site facility." });
|
|
54841
54869
|
return;
|
|
@@ -54862,6 +54890,7 @@ function useSiteFacilityController() {
|
|
|
54862
54890
|
return next(new import_node_server_utils162.BadRequestError(error.message));
|
|
54863
54891
|
}
|
|
54864
54892
|
try {
|
|
54893
|
+
await requireSiteReach(req, value.site);
|
|
54865
54894
|
const data = await _getSiteFacilityTimeSlotById(
|
|
54866
54895
|
payload._id,
|
|
54867
54896
|
value.date,
|