@7365admin1/core 3.52.17 → 3.52.19
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/CHANGELOG.md +37 -0
- package/dist/index.js +35 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +35 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +5 -2
- package/test/e2e/onboarding-org-save-diagnosis.e2e.test.mjs +220 -0
- package/test/e2e/subscription-read-scope.e2e.test.mjs +182 -0
- package/test/e2e/user-email-lookup-leak.e2e.test.mjs +68 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @iservice365/core
|
|
2
2
|
|
|
3
|
+
## 3.52.19
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f66708d: Say what is wrong when an organisation cannot be saved.
|
|
8
|
+
|
|
9
|
+
`PUT /api/organizations/:id` — the write behind onboarding step 1 and the staff
|
|
10
|
+
Client List edit — collapsed every MongoDB write error into a 500 reading
|
|
11
|
+
"Failed to update organization.", with only `error.message` in the log. A
|
|
12
|
+
duplicate-key refusal (the `organizations` collection still carries a unique
|
|
13
|
+
index an earlier version of this file created and nothing ever dropped) is now
|
|
14
|
+
a 400 naming the field the caller has to change, a typed error raised
|
|
15
|
+
underneath is no longer turned into a 500, and the log line carries the error
|
|
16
|
+
name, code, key and the organisation id.
|
|
17
|
+
|
|
18
|
+
## 3.52.18
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- 5f38ea7: Narrow the account-by-e-mail lookup to identity only, and scope the two open subscription reads
|
|
23
|
+
|
|
24
|
+
`GET /api/users/email/:email` and its v2 twin `GET /api/users/v2/email/:email`
|
|
25
|
+
stripped the password hash and session id from the reply but answered every
|
|
26
|
+
other stored field, so any signed-in account could read any other account's
|
|
27
|
+
NRIC, contact number, date of birth, gender, default organisation, status and
|
|
28
|
+
profile just by knowing the e-mail address — across every client on the
|
|
29
|
+
platform. Both now answer `{_id, name, email}` and nothing else. An
|
|
30
|
+
organisation gate would have been the wrong fix: this endpoint exists for the
|
|
31
|
+
invite flow, where the person being looked up is deliberately not in the
|
|
32
|
+
caller's organisation yet.
|
|
33
|
+
|
|
34
|
+
`GET /api/subscriptions/org/:id` answered any organisation's billing record —
|
|
35
|
+
plan, seat count, price, currency, renewal date — to any signed-in account, and
|
|
36
|
+
`GET /api/subscriptions/` answered every subscription on the platform in one
|
|
37
|
+
list. They now carry `requireOrgAccess` and `requirePlatformStaff`, the two
|
|
38
|
+
gates already used elsewhere in the same file.
|
|
39
|
+
|
|
3
40
|
## 3.52.17
|
|
4
41
|
|
|
5
42
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -11683,6 +11683,19 @@ function MOrg(value) {
|
|
|
11683
11683
|
|
|
11684
11684
|
// src/repositories/organization.repo.ts
|
|
11685
11685
|
var import_mongodb19 = require("mongodb");
|
|
11686
|
+
var DUPLICATE_FIELD_LABEL = {
|
|
11687
|
+
name: "name",
|
|
11688
|
+
email: "email address"
|
|
11689
|
+
};
|
|
11690
|
+
function duplicatedField(error) {
|
|
11691
|
+
if (error?.code !== 11e3 && !/duplicate/i.test(error?.message ?? "")) {
|
|
11692
|
+
return "";
|
|
11693
|
+
}
|
|
11694
|
+
const fromPattern = Object.keys(error?.keyPattern ?? {})[0];
|
|
11695
|
+
if (fromPattern)
|
|
11696
|
+
return fromPattern;
|
|
11697
|
+
return /index:\s*([A-Za-z0-9_]+?)_\d/.exec(error?.message ?? "")?.[1] ?? "";
|
|
11698
|
+
}
|
|
11686
11699
|
function useOrgRepo() {
|
|
11687
11700
|
const db2 = import_node_server_utils21.useAtlas.getDb();
|
|
11688
11701
|
if (!db2) {
|
|
@@ -11764,8 +11777,21 @@ function useOrgRepo() {
|
|
|
11764
11777
|
await delNamespace();
|
|
11765
11778
|
return id;
|
|
11766
11779
|
} catch (error) {
|
|
11767
|
-
import_node_server_utils21.logger.log({
|
|
11768
|
-
|
|
11780
|
+
import_node_server_utils21.logger.log({
|
|
11781
|
+
level: "error",
|
|
11782
|
+
message: `Organization update failed for ${id}: ${error?.name ?? "Error"}${error?.code !== void 0 ? ` code=${error.code}` : ""}${duplicatedField(error) ? ` key=${duplicatedField(error)}` : ""} fields=[${Object.keys(value ?? {}).join(",")}] ${error?.message ?? ""}`
|
|
11783
|
+
});
|
|
11784
|
+
if (error instanceof import_node_server_utils21.AppError)
|
|
11785
|
+
throw error;
|
|
11786
|
+
const field = duplicatedField(error);
|
|
11787
|
+
if (field) {
|
|
11788
|
+
throw new import_node_server_utils21.BadRequestError(
|
|
11789
|
+
`Another organization already uses this ${DUPLICATE_FIELD_LABEL[field] ?? field}. Please use a different one.`
|
|
11790
|
+
);
|
|
11791
|
+
}
|
|
11792
|
+
throw new import_node_server_utils21.InternalServerError(
|
|
11793
|
+
`Failed to update organization${error?.code !== void 0 ? ` (${error.code})` : ""}.`
|
|
11794
|
+
);
|
|
11769
11795
|
}
|
|
11770
11796
|
}
|
|
11771
11797
|
async function getAll({
|
|
@@ -18680,11 +18706,11 @@ async function requireUserFieldWrite(req, userId, field) {
|
|
|
18680
18706
|
}
|
|
18681
18707
|
|
|
18682
18708
|
// src/utils/user-response.util.ts
|
|
18683
|
-
function
|
|
18709
|
+
function publicIdentity(user) {
|
|
18684
18710
|
if (!user || typeof user !== "object")
|
|
18685
18711
|
return user;
|
|
18686
|
-
const {
|
|
18687
|
-
return
|
|
18712
|
+
const { _id, name, email } = user;
|
|
18713
|
+
return { _id, name, email };
|
|
18688
18714
|
}
|
|
18689
18715
|
|
|
18690
18716
|
// src/controllers/user.controller.ts
|
|
@@ -18776,7 +18802,7 @@ function useUserController() {
|
|
|
18776
18802
|
return;
|
|
18777
18803
|
}
|
|
18778
18804
|
const user = await _getByEmail(email);
|
|
18779
|
-
res.json(
|
|
18805
|
+
res.json(publicIdentity(user));
|
|
18780
18806
|
return;
|
|
18781
18807
|
} catch (error2) {
|
|
18782
18808
|
import_node_server_utils43.logger.log({ level: "error", message: `${error2.message}` });
|
|
@@ -31692,6 +31718,7 @@ function useSubscriptionController() {
|
|
|
31692
31718
|
return;
|
|
31693
31719
|
}
|
|
31694
31720
|
try {
|
|
31721
|
+
await requireOrgAccess(req, _id);
|
|
31695
31722
|
const data = await _getByOrgId(_id);
|
|
31696
31723
|
res.json(data);
|
|
31697
31724
|
return;
|
|
@@ -31718,6 +31745,7 @@ function useSubscriptionController() {
|
|
|
31718
31745
|
const page = parseInt(req.query.page ?? "1");
|
|
31719
31746
|
const status = req.query.status ?? "active";
|
|
31720
31747
|
try {
|
|
31748
|
+
await requirePlatformStaff(req);
|
|
31721
31749
|
const data = await _getSubscriptions({ search, page, status });
|
|
31722
31750
|
res.json(data);
|
|
31723
31751
|
return;
|
|
@@ -86094,7 +86122,7 @@ function useUserControllerV2() {
|
|
|
86094
86122
|
return rejectValidation(error, next);
|
|
86095
86123
|
try {
|
|
86096
86124
|
const user = await _getUserByEmail(value);
|
|
86097
|
-
res.json(
|
|
86125
|
+
res.json(publicIdentity(user));
|
|
86098
86126
|
return;
|
|
86099
86127
|
} catch (err) {
|
|
86100
86128
|
import_node_server_utils269.logger.log({ level: "error", message: `${err.message}` });
|