@7365admin1/core 3.48.1-staging.195 → 3.48.1-staging.197
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/org-anon-lookup-projection.md +33 -0
- package/.changeset/robot-site-scope.md +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +30 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +30 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +10 -0
- package/test/e2e/org-anon-lookup-projection.e2e.test.mjs +202 -0
- package/test/e2e/robot-scope.e2e.test.mjs +363 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Return the organisation's name only on the anonymous organisation lookup.
|
|
6
|
+
|
|
7
|
+
`GET /api/organizations/org/id/:id` is the unguarded twin of `/id/:id`, and it
|
|
8
|
+
shares this controller's `getById`. The handler answered `res.json(data)` on the
|
|
9
|
+
raw stored organisation document, which carries the client's own business
|
|
10
|
+
`email` and `contact` number alongside `description`, `busInst`, `nature`,
|
|
11
|
+
`defaultSite`, `terms` and `policies`. So anyone at all, with no session, who
|
|
12
|
+
knew or guessed an organisation id got a client's business contact details back.
|
|
13
|
+
|
|
14
|
+
It cannot be given a guard. Its only callers anywhere in the organisation are
|
|
15
|
+
`web-app-main`'s `sign-up-invite/onboarding-admin` and
|
|
16
|
+
`sign-up-invite/service-provider` pages (via `layer-common`'s
|
|
17
|
+
`useOrg.getOrgById`), reached from an invite e-mail before the account being
|
|
18
|
+
invited exists. Both pages do the same thing with the reply:
|
|
19
|
+
`form.organization = org?.name || ""` — they read `name` and nothing else.
|
|
20
|
+
|
|
21
|
+
So the anonymous path now uses a new `getNameById` on the organisation
|
|
22
|
+
repository: `findOne` with an **allow-list** projection of `_id` and `name`. A
|
|
23
|
+
field added to the organisation document later is excluded by default rather
|
|
24
|
+
than leaking on the day it is added.
|
|
25
|
+
|
|
26
|
+
`getById` itself is untouched. About twenty callers inside this package need the
|
|
27
|
+
whole document — `customer`, `feedback`, `member`, `person`, `robot`,
|
|
28
|
+
`service-provider`, `service-provider-invite`, `site` and `user-v2` services all
|
|
29
|
+
read it — so projecting that function would have broken them. The new function
|
|
30
|
+
sits beside it, and the controller picks between the two on whether the request
|
|
31
|
+
carries a session (`callerId(req)`), which is the same shape used for the
|
|
32
|
+
pre-login user lookup and resident-create routes. The guarded `/id/:id` route is
|
|
33
|
+
therefore unchanged: still the whole document, still 401 without a session.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Scope the robot routes to their estate. `POST /api/robots` and `GET /api/robots/organization/:organization/site/:site` carried `requireAuth` and nothing more and the controller held no authorization identifier, so any signed-in account on the platform could page any estate's robot list — which projects `appKey` and `appToken`, the robot's credentials — and aim a robot registration at any estate. Reach is now decided from the session, on the site named in the request. A registration naming no estate is refused rather than falling through unscoped, and `createdBy` is taken from the session rather than from the request body.
|
package/dist/index.d.ts
CHANGED
|
@@ -714,6 +714,7 @@ declare function useOrgRepo(): {
|
|
|
714
714
|
pageRange: string;
|
|
715
715
|
} | TOrg>;
|
|
716
716
|
getById: (_id?: string | ObjectId) => Promise<TOrg>;
|
|
717
|
+
getNameById: (_id?: string | ObjectId) => Promise<mongodb.WithId<bson.Document>>;
|
|
717
718
|
getByName: (name: string) => Promise<TOrg>;
|
|
718
719
|
getByEmail: (email: string) => Promise<TOrg | null>;
|
|
719
720
|
updateFieldById: ({ _id, field, value, }: {
|
package/dist/index.js
CHANGED
|
@@ -11718,6 +11718,21 @@ function useOrgRepo() {
|
|
|
11718
11718
|
throw error;
|
|
11719
11719
|
}
|
|
11720
11720
|
}
|
|
11721
|
+
async function getNameById(_id) {
|
|
11722
|
+
try {
|
|
11723
|
+
_id = new import_mongodb18.ObjectId(_id);
|
|
11724
|
+
} catch (error) {
|
|
11725
|
+
throw new import_node_server_utils20.BadRequestError("Invalid organization ID format.");
|
|
11726
|
+
}
|
|
11727
|
+
const data = await collection.findOne(
|
|
11728
|
+
{ _id },
|
|
11729
|
+
{ projection: { _id: 1, name: 1 } }
|
|
11730
|
+
);
|
|
11731
|
+
if (!data) {
|
|
11732
|
+
throw new import_node_server_utils20.NotFoundError("Organization not found.");
|
|
11733
|
+
}
|
|
11734
|
+
return data;
|
|
11735
|
+
}
|
|
11721
11736
|
async function getById(_id) {
|
|
11722
11737
|
try {
|
|
11723
11738
|
_id = new import_mongodb18.ObjectId(_id);
|
|
@@ -12118,6 +12133,7 @@ function useOrgRepo() {
|
|
|
12118
12133
|
update,
|
|
12119
12134
|
getAll,
|
|
12120
12135
|
getById,
|
|
12136
|
+
getNameById,
|
|
12121
12137
|
getByName,
|
|
12122
12138
|
getByEmail,
|
|
12123
12139
|
updateFieldById,
|
|
@@ -27644,6 +27660,7 @@ function useOrgController() {
|
|
|
27644
27660
|
const {
|
|
27645
27661
|
getByName: _getByName,
|
|
27646
27662
|
getById: _getById,
|
|
27663
|
+
getNameById: _getNameById,
|
|
27647
27664
|
getByEmail: _getByEmail,
|
|
27648
27665
|
getAll: _getAll,
|
|
27649
27666
|
add: _add,
|
|
@@ -27815,7 +27832,7 @@ function useOrgController() {
|
|
|
27815
27832
|
return;
|
|
27816
27833
|
}
|
|
27817
27834
|
try {
|
|
27818
|
-
const data = await _getById(_id);
|
|
27835
|
+
const data = callerId(req) ? await _getById(_id) : await _getNameById(_id);
|
|
27819
27836
|
res.json(data);
|
|
27820
27837
|
return;
|
|
27821
27838
|
} catch (error2) {
|
|
@@ -51602,15 +51619,25 @@ var import_joi70 = __toESM(require("joi"));
|
|
|
51602
51619
|
var import_node_server_utils143 = require("@7365admin1/node-server-utils");
|
|
51603
51620
|
function useRobotController() {
|
|
51604
51621
|
const { createRobot: _createRobot, getRobots: _getRobots } = useRobotService();
|
|
51622
|
+
async function requireRobotReach(req, site) {
|
|
51623
|
+
await requireSiteReach(req, site);
|
|
51624
|
+
}
|
|
51605
51625
|
async function createRobot(req, res, next) {
|
|
51606
|
-
const payload = { ...req.body };
|
|
51626
|
+
const payload = { ...req.body, createdBy: callerId(req) };
|
|
51607
51627
|
const { error } = robotSchema.validate(payload);
|
|
51608
51628
|
if (error) {
|
|
51609
51629
|
import_node_server_utils143.logger.log({ level: "error", message: error.message });
|
|
51610
51630
|
next(new import_node_server_utils143.BadRequestError(error.message));
|
|
51611
51631
|
return;
|
|
51612
51632
|
}
|
|
51633
|
+
if (!payload.metadata?.siteId) {
|
|
51634
|
+
const message = "metadata.siteId is required.";
|
|
51635
|
+
import_node_server_utils143.logger.log({ level: "error", message });
|
|
51636
|
+
next(new import_node_server_utils143.BadRequestError(message));
|
|
51637
|
+
return;
|
|
51638
|
+
}
|
|
51613
51639
|
try {
|
|
51640
|
+
await requireRobotReach(req, payload.metadata.siteId);
|
|
51614
51641
|
await _createRobot(payload);
|
|
51615
51642
|
res.status(201).json({ message: "Successfully created robot." });
|
|
51616
51643
|
return;
|
|
@@ -51649,6 +51676,7 @@ function useRobotController() {
|
|
|
51649
51676
|
const from = req.query.from ?? "";
|
|
51650
51677
|
const to = req.query.to ?? "";
|
|
51651
51678
|
try {
|
|
51679
|
+
await requireRobotReach(req, site);
|
|
51652
51680
|
const data = await _getRobots({
|
|
51653
51681
|
search,
|
|
51654
51682
|
page,
|