@7365admin1/core 3.138.0 → 3.139.1
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 +12 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +172 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +172 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/service-provider-invite-email-new-company.e2e.test.mjs +123 -1
- package/test/member-working-membership.test.mjs +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @iservice365/core
|
|
2
2
|
|
|
3
|
+
## 3.139.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 45755c1: Security: a membership that was taken away ("inactive" after the service-provider Repair or Finish-setup Undo, or "deleted") no longer grants organisation, site or role reach (resolveInviteActor, camera site entitlement, role-holder check).
|
|
8
|
+
|
|
9
|
+
## 3.139.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 2b2960d: Membership lookup and organisation switcher ignore memberships that were taken away (inactive/deleted) and prefer the account's default organisation; SP-invite Repair also retires the unused full-access role the old sign-up left in the client's role list (Undo restores it); accept-new-company reuses the company its own invitation already created (retry/double click).
|
|
14
|
+
|
|
3
15
|
## 3.138.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -17891,6 +17891,10 @@ declare function useServiceProviderInviteService(): {
|
|
|
17891
17891
|
siteName: string;
|
|
17892
17892
|
createdAt: any;
|
|
17893
17893
|
}[];
|
|
17894
|
+
leftoverRoles: {
|
|
17895
|
+
_id: string;
|
|
17896
|
+
name: string;
|
|
17897
|
+
}[];
|
|
17894
17898
|
defaultOrgIsSender: boolean;
|
|
17895
17899
|
repaired: {
|
|
17896
17900
|
at: any;
|
|
@@ -19114,7 +19118,7 @@ declare function MHidAmicoIdentity(value: THidAmicoIdentity): {
|
|
|
19114
19118
|
member: ObjectId | undefined;
|
|
19115
19119
|
serviceProvider: ObjectId | undefined;
|
|
19116
19120
|
visitor: ObjectId | undefined;
|
|
19117
|
-
type: "
|
|
19121
|
+
type: "resident" | "admin" | "staff" | "visitor" | "contractor" | "unknown";
|
|
19118
19122
|
status: "deleted" | "active" | "inactive";
|
|
19119
19123
|
metadata: Record<string, unknown>;
|
|
19120
19124
|
createdAt: string | Date;
|
|
@@ -19226,7 +19230,7 @@ declare function useHidAmicoRepo(): {
|
|
|
19226
19230
|
member: ObjectId | undefined;
|
|
19227
19231
|
serviceProvider: ObjectId | undefined;
|
|
19228
19232
|
visitor: ObjectId | undefined;
|
|
19229
|
-
type: "
|
|
19233
|
+
type: "resident" | "admin" | "staff" | "visitor" | "contractor" | "unknown";
|
|
19230
19234
|
status: "deleted" | "active" | "inactive";
|
|
19231
19235
|
metadata: Record<string, unknown>;
|
|
19232
19236
|
createdAt: string | Date;
|
|
@@ -19630,7 +19634,7 @@ declare function useHidAmicoService(): {
|
|
|
19630
19634
|
member: bson.ObjectId | undefined;
|
|
19631
19635
|
serviceProvider: bson.ObjectId | undefined;
|
|
19632
19636
|
visitor: bson.ObjectId | undefined;
|
|
19633
|
-
type: "
|
|
19637
|
+
type: "resident" | "admin" | "staff" | "visitor" | "contractor" | "unknown";
|
|
19634
19638
|
status: "deleted" | "active" | "inactive";
|
|
19635
19639
|
metadata: Record<string, unknown>;
|
|
19636
19640
|
createdAt: string | Date;
|
package/dist/index.js
CHANGED
|
@@ -4371,6 +4371,54 @@ function sameAppType(a, b) {
|
|
|
4371
4371
|
return !!a && !!b && serviceAppType(a) === serviceAppType(b);
|
|
4372
4372
|
}
|
|
4373
4373
|
|
|
4374
|
+
// src/utils/member-lookup.util.ts
|
|
4375
|
+
var idOf = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
|
|
4376
|
+
function isLiveMembership(m) {
|
|
4377
|
+
return m.status !== "deleted" && !m.deletedAt;
|
|
4378
|
+
}
|
|
4379
|
+
function pickSiteMembership(rows, type, site, org = "") {
|
|
4380
|
+
site = site.toLowerCase();
|
|
4381
|
+
org = org.toLowerCase();
|
|
4382
|
+
if (!site)
|
|
4383
|
+
return null;
|
|
4384
|
+
const live2 = rows.filter(
|
|
4385
|
+
(m) => sameAppType(m.type, type) && isLiveMembership(m) && (!org || idOf(m.org) === org)
|
|
4386
|
+
);
|
|
4387
|
+
const atSite = live2.find((m) => idOf(m.siteId) === site);
|
|
4388
|
+
if (atSite)
|
|
4389
|
+
return atSite;
|
|
4390
|
+
if (!org)
|
|
4391
|
+
return null;
|
|
4392
|
+
return live2.find((m) => !idOf(m.siteId)) ?? live2[0] ?? null;
|
|
4393
|
+
}
|
|
4394
|
+
function pickLookupMembership(rows, type, site = "", org = "") {
|
|
4395
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
4396
|
+
const atSite = site ? pickSiteMembership(list, type, site, org) : null;
|
|
4397
|
+
if (atSite)
|
|
4398
|
+
return atSite;
|
|
4399
|
+
const inOrg = org ? list.find((m) => sameAppType(m.type, type) && idOf(m.org) === org.toLowerCase()) : void 0;
|
|
4400
|
+
if (inOrg && isLiveMembership(inOrg))
|
|
4401
|
+
return inOrg;
|
|
4402
|
+
return preferWorkingMembership(list.filter((m) => sameAppType(m.type, type)));
|
|
4403
|
+
}
|
|
4404
|
+
var WORKING = /* @__PURE__ */ new Set(["active", "approved"]);
|
|
4405
|
+
var TAKEN_AWAY_MEMBER_STATUSES = ["deleted", "inactive"];
|
|
4406
|
+
function preferWorkingMembership(rows, defaultOrg = "") {
|
|
4407
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
4408
|
+
const working = list.filter((m) => WORKING.has(String(m.status ?? "")) && !m.deletedAt);
|
|
4409
|
+
const home = idOf(defaultOrg);
|
|
4410
|
+
return (home ? working.find((m) => idOf(m.org) === home) : void 0) ?? working[0] ?? list.find(isLiveMembership) ?? list[0] ?? null;
|
|
4411
|
+
}
|
|
4412
|
+
function memberListSiteScope(memberships, org) {
|
|
4413
|
+
const inOrg = (memberships ?? []).filter((m) => idOf(m.org) === idOf(org));
|
|
4414
|
+
const here = inOrg.filter((m) => m.type !== "resident");
|
|
4415
|
+
if (!inOrg.length)
|
|
4416
|
+
return null;
|
|
4417
|
+
if (here.some((m) => !idOf(m.siteId) || m.orgLevelRole))
|
|
4418
|
+
return null;
|
|
4419
|
+
return [...new Set(here.map((m) => idOf(m.siteId)))];
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4374
4422
|
// src/repositories/member.repo.ts
|
|
4375
4423
|
var import_node_server_utils15 = require("@7365admin1/node-server-utils");
|
|
4376
4424
|
|
|
@@ -4493,10 +4541,10 @@ var import_mongodb13 = require("mongodb");
|
|
|
4493
4541
|
function crossSiteRoleMode() {
|
|
4494
4542
|
return (process.env.CROSS_SITE_ROLE_MODE ?? "").trim().toLowerCase() === "enforce" ? "enforce" : "log";
|
|
4495
4543
|
}
|
|
4496
|
-
var
|
|
4544
|
+
var idOf2 = (value) => value === null || value === void 0 ? "" : String(value?.toString?.() ?? value);
|
|
4497
4545
|
function isCrossSiteRole(role, memberSite) {
|
|
4498
|
-
const roleSite =
|
|
4499
|
-
return !!roleSite && roleSite !==
|
|
4546
|
+
const roleSite = idOf2(role?.site);
|
|
4547
|
+
return !!roleSite && roleSite !== idOf2(memberSite);
|
|
4500
4548
|
}
|
|
4501
4549
|
var THROTTLE_MS = 10 * 60 * 1e3;
|
|
4502
4550
|
var lastLogged = /* @__PURE__ */ new Map();
|
|
@@ -4505,7 +4553,7 @@ function crossSiteRoleGrants(input) {
|
|
|
4505
4553
|
return true;
|
|
4506
4554
|
const mode = crossSiteRoleMode();
|
|
4507
4555
|
const granted = mode !== "enforce";
|
|
4508
|
-
const key = [mode,
|
|
4556
|
+
const key = [mode, idOf2(input.member) || idOf2(input.user), idOf2(input.role?._id), input.where].join("|");
|
|
4509
4557
|
const now = Date.now();
|
|
4510
4558
|
if ((lastLogged.get(key) ?? 0) + THROTTLE_MS <= now) {
|
|
4511
4559
|
lastLogged.set(key, now);
|
|
@@ -4513,7 +4561,7 @@ function crossSiteRoleGrants(input) {
|
|
|
4513
4561
|
lastLogged.clear();
|
|
4514
4562
|
import_node_server_utils14.logger.log({
|
|
4515
4563
|
level: "warn",
|
|
4516
|
-
message: `cross-site-role mode=${mode} ${granted ? "granted" : "refused"} where=${input.where} member=${
|
|
4564
|
+
message: `cross-site-role mode=${mode} ${granted ? "granted" : "refused"} where=${input.where} member=${idOf2(input.member) || "-"} user=${idOf2(input.user) || "-"} site=${idOf2(input.memberSite) || "-"} role=${idOf2(input.role?._id) || "-"} roleSite=${idOf2(input.role?.site)}`
|
|
4517
4565
|
});
|
|
4518
4566
|
}
|
|
4519
4567
|
return granted;
|
|
@@ -4524,7 +4572,7 @@ function crossSiteRoleRefusal(roleName, roleSiteName) {
|
|
|
4524
4572
|
return `${role} belongs to ${site}. Choose a role for this site, or an org-wide role.`;
|
|
4525
4573
|
}
|
|
4526
4574
|
async function requireRoleFitsSite(roleId, siteId) {
|
|
4527
|
-
const id =
|
|
4575
|
+
const id = idOf2(roleId);
|
|
4528
4576
|
if (!id || !import_mongodb13.ObjectId.isValid(id))
|
|
4529
4577
|
return;
|
|
4530
4578
|
const db2 = import_node_server_utils14.useAtlas.getDb();
|
|
@@ -4533,7 +4581,7 @@ async function requireRoleFitsSite(roleId, siteId) {
|
|
|
4533
4581
|
const role = await db2.collection("roles").findOne({ _id: new import_mongodb13.ObjectId(id) }, { projection: { name: 1, site: 1 } });
|
|
4534
4582
|
if (!isCrossSiteRole(role, siteId))
|
|
4535
4583
|
return;
|
|
4536
|
-
const site = import_mongodb13.ObjectId.isValid(
|
|
4584
|
+
const site = import_mongodb13.ObjectId.isValid(idOf2(role?.site)) ? await db2.collection("sites").findOne({ _id: new import_mongodb13.ObjectId(idOf2(role?.site)) }, { projection: { name: 1 } }) : null;
|
|
4537
4585
|
throw new import_node_server_utils14.BadRequestError(crossSiteRoleRefusal(role?.name, site?.name));
|
|
4538
4586
|
}
|
|
4539
4587
|
async function otherSiteRoleClauses(roles, org) {
|
|
@@ -4541,7 +4589,7 @@ async function otherSiteRoleClauses(roles, org) {
|
|
|
4541
4589
|
{ ...org ? { org } : { org: { $nin: ["", null] } }, site: { $nin: ["", null] }, status: "active" },
|
|
4542
4590
|
{ projection: { site: 1, org: 1 } }
|
|
4543
4591
|
).toArray();
|
|
4544
|
-
const both3 = (v) => import_mongodb13.ObjectId.isValid(
|
|
4592
|
+
const both3 = (v) => import_mongodb13.ObjectId.isValid(idOf2(v)) ? [new import_mongodb13.ObjectId(idOf2(v)), idOf2(v)] : [v];
|
|
4545
4593
|
const clauses = (list ?? []).map((r) => ({
|
|
4546
4594
|
role: r._id,
|
|
4547
4595
|
org: { $in: both3(r.org) },
|
|
@@ -4724,9 +4772,12 @@ function useMemberRepo() {
|
|
|
4724
4772
|
} catch (error) {
|
|
4725
4773
|
throw new import_node_server_utils15.BadRequestError("Invalid user ID format.");
|
|
4726
4774
|
}
|
|
4775
|
+
const account = await db2?.collection("users").findOne({ _id: user }, { projection: { defaultOrg: 1 } }).catch(() => null);
|
|
4776
|
+
const defaultOrg = String(account?.defaultOrg ?? "");
|
|
4727
4777
|
const cacheKey = (0, import_node_server_utils15.makeCacheKey)(namespace_collection3, {
|
|
4728
4778
|
user: user.toString(),
|
|
4729
|
-
type
|
|
4779
|
+
type,
|
|
4780
|
+
defaultOrg
|
|
4730
4781
|
});
|
|
4731
4782
|
const cachedData = await getCache(cacheKey);
|
|
4732
4783
|
if (cachedData) {
|
|
@@ -4734,7 +4785,10 @@ function useMemberRepo() {
|
|
|
4734
4785
|
return cachedData;
|
|
4735
4786
|
}
|
|
4736
4787
|
try {
|
|
4737
|
-
const data =
|
|
4788
|
+
const data = preferWorkingMembership(
|
|
4789
|
+
await collection.find({ user, type: appTypeQuery(type) }).toArray(),
|
|
4790
|
+
defaultOrg
|
|
4791
|
+
);
|
|
4738
4792
|
if (!data) {
|
|
4739
4793
|
throw new import_node_server_utils15.NotFoundError("Member not found.");
|
|
4740
4794
|
}
|
|
@@ -5197,7 +5251,12 @@ function useMemberRepo() {
|
|
|
5197
5251
|
const query2 = {
|
|
5198
5252
|
user,
|
|
5199
5253
|
org: { $nin: [null, ""] },
|
|
5200
|
-
type: appTypeQuery(type)
|
|
5254
|
+
type: appTypeQuery(type),
|
|
5255
|
+
// The organisation switcher. A membership that was taken away (Repair
|
|
5256
|
+
// sets it inactive, removal deletes it) must not keep the organisation
|
|
5257
|
+
// on the person's list: latitude.sec@ still saw JLL Singapore beside its
|
|
5258
|
+
// own company after the repair (owner report 2026-09-30).
|
|
5259
|
+
status: { $nin: TAKEN_AWAY_MEMBER_STATUSES }
|
|
5201
5260
|
};
|
|
5202
5261
|
const cacheOptions = {
|
|
5203
5262
|
page: page2,
|
|
@@ -9847,7 +9906,7 @@ async function resolveInviteActor(userId) {
|
|
|
9847
9906
|
const db2 = import_node_server_utils28.useAtlas.getDb();
|
|
9848
9907
|
if (!db2)
|
|
9849
9908
|
return { ...EMPTY, id };
|
|
9850
|
-
const members = await db2.collection("members").find({ user: new import_mongodb26.ObjectId(id), status: { $
|
|
9909
|
+
const members = await db2.collection("members").find({ user: new import_mongodb26.ObjectId(id), status: { $nin: TAKEN_AWAY_MEMBER_STATUSES } }).toArray();
|
|
9851
9910
|
const adminMember = members.find((m) => m.type === "admin");
|
|
9852
9911
|
let isSuperAdmin2 = false;
|
|
9853
9912
|
let staffPermissions;
|
|
@@ -10154,7 +10213,7 @@ async function holdsRole(userId, roleId, orgId) {
|
|
|
10154
10213
|
const member = await db2.collection("members").findOne({
|
|
10155
10214
|
user: new import_mongodb26.ObjectId(id),
|
|
10156
10215
|
role: new import_mongodb26.ObjectId(role),
|
|
10157
|
-
status: { $
|
|
10216
|
+
status: { $nin: TAKEN_AWAY_MEMBER_STATUSES },
|
|
10158
10217
|
// both shapes, as `hasOrgInvitation` does for `metadata.org`
|
|
10159
10218
|
...org ? { org: { $in: [new import_mongodb26.ObjectId(org), org] } } : {}
|
|
10160
10219
|
});
|
|
@@ -11619,7 +11678,7 @@ function enforcedModuleList(stored, save) {
|
|
|
11619
11678
|
return NO_LIST;
|
|
11620
11679
|
return { list, since: typeof save?.createdAt === "string" ? save.createdAt : "" };
|
|
11621
11680
|
}
|
|
11622
|
-
var
|
|
11681
|
+
var idOf3 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
|
|
11623
11682
|
function deniedForMembership({
|
|
11624
11683
|
membership,
|
|
11625
11684
|
org,
|
|
@@ -11627,12 +11686,12 @@ function deniedForMembership({
|
|
|
11627
11686
|
site,
|
|
11628
11687
|
siteSave
|
|
11629
11688
|
}) {
|
|
11630
|
-
const memberOrg =
|
|
11689
|
+
const memberOrg = idOf3(membership?.org);
|
|
11631
11690
|
if (!membership || membership.type === "resident" || !memberOrg) {
|
|
11632
11691
|
return { denied: [], since: "" };
|
|
11633
11692
|
}
|
|
11634
11693
|
const orgTier = enforcedModuleList(org?.modules, orgSave);
|
|
11635
|
-
const siteTier = site &&
|
|
11694
|
+
const siteTier = site && idOf3(site.orgId) === memberOrg ? enforcedModuleList(site.metadata?.modules, siteSave) : NO_LIST;
|
|
11636
11695
|
const denied = deniedModules(orgTier.list, siteTier.list);
|
|
11637
11696
|
if (!denied.length)
|
|
11638
11697
|
return { denied: [], since: "" };
|
|
@@ -12201,46 +12260,6 @@ function staffMayBypass(actor, grant = CROSS_CLIENT_GRANT) {
|
|
|
12201
12260
|
return consoleRoleAllows(actor.staffPermissions, grant.resource, grant.action);
|
|
12202
12261
|
}
|
|
12203
12262
|
|
|
12204
|
-
// src/utils/member-lookup.util.ts
|
|
12205
|
-
var idOf3 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
|
|
12206
|
-
function isLiveMembership(m) {
|
|
12207
|
-
return m.status !== "deleted" && !m.deletedAt;
|
|
12208
|
-
}
|
|
12209
|
-
function pickSiteMembership(rows, type, site, org = "") {
|
|
12210
|
-
site = site.toLowerCase();
|
|
12211
|
-
org = org.toLowerCase();
|
|
12212
|
-
if (!site)
|
|
12213
|
-
return null;
|
|
12214
|
-
const live2 = rows.filter(
|
|
12215
|
-
(m) => sameAppType(m.type, type) && isLiveMembership(m) && (!org || idOf3(m.org) === org)
|
|
12216
|
-
);
|
|
12217
|
-
const atSite = live2.find((m) => idOf3(m.siteId) === site);
|
|
12218
|
-
if (atSite)
|
|
12219
|
-
return atSite;
|
|
12220
|
-
if (!org)
|
|
12221
|
-
return null;
|
|
12222
|
-
return live2.find((m) => !idOf3(m.siteId)) ?? live2[0] ?? null;
|
|
12223
|
-
}
|
|
12224
|
-
function pickLookupMembership(rows, type, site = "", org = "") {
|
|
12225
|
-
const list = Array.isArray(rows) ? rows : [];
|
|
12226
|
-
const atSite = site ? pickSiteMembership(list, type, site, org) : null;
|
|
12227
|
-
if (atSite)
|
|
12228
|
-
return atSite;
|
|
12229
|
-
const inOrg = org ? list.find((m) => sameAppType(m.type, type) && idOf3(m.org) === org.toLowerCase()) : void 0;
|
|
12230
|
-
if (inOrg && isLiveMembership(inOrg))
|
|
12231
|
-
return inOrg;
|
|
12232
|
-
return list.find((m) => sameAppType(m.type, type)) ?? null;
|
|
12233
|
-
}
|
|
12234
|
-
function memberListSiteScope(memberships, org) {
|
|
12235
|
-
const inOrg = (memberships ?? []).filter((m) => idOf3(m.org) === idOf3(org));
|
|
12236
|
-
const here = inOrg.filter((m) => m.type !== "resident");
|
|
12237
|
-
if (!inOrg.length)
|
|
12238
|
-
return null;
|
|
12239
|
-
if (here.some((m) => !idOf3(m.siteId) || m.orgLevelRole))
|
|
12240
|
-
return null;
|
|
12241
|
-
return [...new Set(here.map((m) => idOf3(m.siteId)))];
|
|
12242
|
-
}
|
|
12243
|
-
|
|
12244
12263
|
// src/services/module-gate.service.ts
|
|
12245
12264
|
var idOf4 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
|
|
12246
12265
|
var isId = (v) => /^[0-9a-f]{24}$/i.test(v);
|
|
@@ -23225,7 +23244,7 @@ function useCameraViewService() {
|
|
|
23225
23244
|
}
|
|
23226
23245
|
}
|
|
23227
23246
|
async function resolveSiteAccess(params) {
|
|
23228
|
-
const memberships = await params.db.collection("members").find({ user: new import_mongodb52.ObjectId(params.userId), status: { $
|
|
23247
|
+
const memberships = await params.db.collection("members").find({ user: new import_mongodb52.ObjectId(params.userId), status: { $nin: TAKEN_AWAY_MEMBER_STATUSES } }).project({ siteId: 1, org: 1, role: 1, type: 1 }).toArray();
|
|
23229
23248
|
const memberOrgIds = [
|
|
23230
23249
|
...new Map(
|
|
23231
23250
|
memberships.filter((membership) => membership.org).map((membership) => [String(membership.org), membership.org])
|
|
@@ -25553,7 +25572,7 @@ function useServiceProviderInviteService() {
|
|
|
25553
25572
|
);
|
|
25554
25573
|
}
|
|
25555
25574
|
const existing = await getByCreatorAndNature(actor.id, nature).catch(() => null);
|
|
25556
|
-
if (existing) {
|
|
25575
|
+
if (existing && String(existing.invitedFrom ?? "") !== String(invite._id)) {
|
|
25557
25576
|
throw new import_node_server_utils61.BadRequestError(
|
|
25558
25577
|
`You already have a ${serviceTypeLabel(nature)} company on iService365 (${existing.name}). Accept the invitation from that company instead.`
|
|
25559
25578
|
);
|
|
@@ -25584,12 +25603,21 @@ function useServiceProviderInviteService() {
|
|
|
25584
25603
|
country
|
|
25585
25604
|
}) {
|
|
25586
25605
|
const inviteeId = String(user?._id ?? "");
|
|
25587
|
-
|
|
25606
|
+
let name = sanitizeCompanyName(companyName) || sanitizeCompanyName(invite.metadata?.name) || sanitizeCompanyName(user?.name);
|
|
25588
25607
|
if (!name)
|
|
25589
25608
|
throw new import_node_server_utils61.BadRequestError("Enter your company's name.");
|
|
25590
25609
|
const email = String(invite.email ?? "").trim().toLowerCase();
|
|
25591
25610
|
const actor = { id: inviteeId };
|
|
25592
|
-
const
|
|
25611
|
+
const db2 = import_node_server_utils61.useAtlas.getDb();
|
|
25612
|
+
const actorIds = [actor.id, ...import_mongodb56.ObjectId.isValid(actor.id) ? [new import_mongodb56.ObjectId(actor.id)] : []];
|
|
25613
|
+
const made = db2 ? await db2.collection("organizations").findOne({
|
|
25614
|
+
invitedFrom: { $in: [String(invite._id), invite._id] },
|
|
25615
|
+
createdBy: { $in: actorIds },
|
|
25616
|
+
status: { $ne: "deleted" }
|
|
25617
|
+
}) : null;
|
|
25618
|
+
if (made)
|
|
25619
|
+
name = String(made.name ?? name);
|
|
25620
|
+
const orgId = made ? String(made._id) : String(
|
|
25593
25621
|
await addOrg({
|
|
25594
25622
|
name,
|
|
25595
25623
|
type: nature,
|
|
@@ -25601,8 +25629,17 @@ function useServiceProviderInviteService() {
|
|
|
25601
25629
|
invitedFrom: String(invite._id)
|
|
25602
25630
|
})
|
|
25603
25631
|
);
|
|
25604
|
-
const roles = await seedDefaultOrgRoles(useRoleRepo().addRole, orgId, nature);
|
|
25605
25632
|
const appType = serviceAppType(nature);
|
|
25633
|
+
const owners = made && db2 ? await db2.collection("roles").find({
|
|
25634
|
+
org: { $in: [new import_mongodb56.ObjectId(orgId), orgId] },
|
|
25635
|
+
name: "owner",
|
|
25636
|
+
status: { $ne: "deleted" }
|
|
25637
|
+
}).toArray() : [];
|
|
25638
|
+
const ownerRole = (type) => {
|
|
25639
|
+
const r = owners.find((x) => x.type === type);
|
|
25640
|
+
return r ? { id: String(r._id), type, name: "owner" } : null;
|
|
25641
|
+
};
|
|
25642
|
+
const roles = ownerRole("organization") && ownerRole(appType) ? [ownerRole("organization"), ownerRole(appType)] : await seedDefaultOrgRoles(useRoleRepo().addRole, orgId, nature);
|
|
25606
25643
|
const orgOwner = roles.find((r) => r.type === "organization" && r.name === "owner");
|
|
25607
25644
|
const appOwner = roles.find((r) => r.type === appType && r.name === "owner");
|
|
25608
25645
|
if (!orgOwner || !appOwner) {
|
|
@@ -25625,8 +25662,18 @@ function useServiceProviderInviteService() {
|
|
|
25625
25662
|
siteName: "",
|
|
25626
25663
|
onboardingRequired: false
|
|
25627
25664
|
};
|
|
25628
|
-
|
|
25629
|
-
|
|
25665
|
+
const hasOwnerRow = async (type) => !!made && !!await db2?.collection("members").findOne({
|
|
25666
|
+
user: { $in: actorIds },
|
|
25667
|
+
org: { $in: [new import_mongodb56.ObjectId(orgId), orgId] },
|
|
25668
|
+
type,
|
|
25669
|
+
status: "active"
|
|
25670
|
+
});
|
|
25671
|
+
if (!await hasOwnerRow("organization")) {
|
|
25672
|
+
await addMember({ ...base, role: orgOwner.id, type: "organization" });
|
|
25673
|
+
}
|
|
25674
|
+
if (!await hasOwnerRow(appType)) {
|
|
25675
|
+
await addMember({ ...base, role: appOwner.id, type: appType });
|
|
25676
|
+
}
|
|
25630
25677
|
await useUserRepo().updateUserFieldById({ _id: actor.id, field: "defaultOrg", value: orgId }).catch(() => void 0);
|
|
25631
25678
|
import_node_server_utils61.logger.log({
|
|
25632
25679
|
level: "info",
|
|
@@ -25682,10 +25729,60 @@ function useServiceProviderInviteService() {
|
|
|
25682
25729
|
const last = [...history].reverse().find((e) => ["wrong-join-repaired", "wrong-join-undone"].includes(e?.action));
|
|
25683
25730
|
return last?.action === "wrong-join-repaired" ? last : null;
|
|
25684
25731
|
}
|
|
25732
|
+
async function leftoverRoles(invite, senderOrg, user) {
|
|
25733
|
+
const db2 = import_node_server_utils61.useAtlas.getDb();
|
|
25734
|
+
if (!db2 || !user?._id || !import_mongodb56.ObjectId.isValid(senderOrg))
|
|
25735
|
+
return [];
|
|
25736
|
+
const since = new Date(String(invite.createdAt ?? 0)).getTime();
|
|
25737
|
+
const rows = await db2.collection("members").find({
|
|
25738
|
+
user: { $in: [user._id, String(user._id)] },
|
|
25739
|
+
org: { $in: [new import_mongodb56.ObjectId(senderOrg), senderOrg] }
|
|
25740
|
+
}).project({ role: 1 }).toArray();
|
|
25741
|
+
const roleIds = [
|
|
25742
|
+
...new Set(
|
|
25743
|
+
rows.map((m) => m.role?.toString?.() ?? "").filter((r) => import_mongodb56.ObjectId.isValid(r))
|
|
25744
|
+
)
|
|
25745
|
+
].map((r) => new import_mongodb56.ObjectId(r));
|
|
25746
|
+
if (!roleIds.length)
|
|
25747
|
+
return [];
|
|
25748
|
+
const roles = await db2.collection("roles").find({
|
|
25749
|
+
_id: { $in: roleIds },
|
|
25750
|
+
org: { $in: [new import_mongodb56.ObjectId(senderOrg), senderOrg] },
|
|
25751
|
+
status: { $ne: "deleted" }
|
|
25752
|
+
}).project({ name: 1, permissions: 1, createdAt: 1 }).toArray();
|
|
25753
|
+
const out = [];
|
|
25754
|
+
for (const role of roles) {
|
|
25755
|
+
if (!(Array.isArray(role.permissions) && role.permissions.includes("*")))
|
|
25756
|
+
continue;
|
|
25757
|
+
if (role.createdAt && Number.isFinite(since) && new Date(role.createdAt).getTime() < since)
|
|
25758
|
+
continue;
|
|
25759
|
+
const inUse = await db2.collection("members").countDocuments({
|
|
25760
|
+
role: { $in: [role._id, role._id.toString()] },
|
|
25761
|
+
status: "active"
|
|
25762
|
+
});
|
|
25763
|
+
if (inUse)
|
|
25764
|
+
continue;
|
|
25765
|
+
out.push({ _id: role._id.toString(), name: String(role.name ?? "") });
|
|
25766
|
+
}
|
|
25767
|
+
return out;
|
|
25768
|
+
}
|
|
25769
|
+
async function setRoleStatus(ids2, status) {
|
|
25770
|
+
const db2 = import_node_server_utils61.useAtlas.getDb();
|
|
25771
|
+
const valid = ids2.filter((r) => import_mongodb56.ObjectId.isValid(r)).map((r) => new import_mongodb56.ObjectId(r));
|
|
25772
|
+
if (!db2 || !valid.length)
|
|
25773
|
+
return;
|
|
25774
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25775
|
+
await db2.collection("roles").updateMany(
|
|
25776
|
+
{ _id: { $in: valid } },
|
|
25777
|
+
{ $set: { status, deletedAt: status === "deleted" ? now : "", updatedAt: now } }
|
|
25778
|
+
);
|
|
25779
|
+
await (0, import_node_server_utils61.useCache)("roles").delNamespace().catch(() => void 0);
|
|
25780
|
+
}
|
|
25685
25781
|
async function wrongJoinPreview({ id, userId }) {
|
|
25686
25782
|
const { invite, senderOrg, user } = await wrongJoinContext(id, userId);
|
|
25687
25783
|
const org = await getOrgById(senderOrg).catch(() => null);
|
|
25688
25784
|
const memberships = await wrongJoinRows(invite, senderOrg, user);
|
|
25785
|
+
const roles = await leftoverRoles(invite, senderOrg, user);
|
|
25689
25786
|
const repaired = lastRepair(invite);
|
|
25690
25787
|
return {
|
|
25691
25788
|
invitationId: id,
|
|
@@ -25694,6 +25791,8 @@ function useServiceProviderInviteService() {
|
|
|
25694
25791
|
senderOrgId: senderOrg,
|
|
25695
25792
|
senderOrgName: String(org?.name ?? ""),
|
|
25696
25793
|
memberships,
|
|
25794
|
+
// Left in the sender's role list by the old sign-up; Repair retires them.
|
|
25795
|
+
leftoverRoles: roles,
|
|
25697
25796
|
defaultOrgIsSender: String(user?.defaultOrg ?? "") === senderOrg,
|
|
25698
25797
|
repaired: repaired ? { at: repaired.at, by: repaired.byName, memberIds: repaired.memberIds ?? [] } : null
|
|
25699
25798
|
};
|
|
@@ -25702,15 +25801,17 @@ function useServiceProviderInviteService() {
|
|
|
25702
25801
|
const { actor, invite, senderOrg, user } = await wrongJoinContext(id, userId);
|
|
25703
25802
|
const rows = await wrongJoinRows(invite, senderOrg, user);
|
|
25704
25803
|
const defaultOrgIsSender = !!user?._id && String(user?.defaultOrg ?? "") === senderOrg;
|
|
25705
|
-
|
|
25804
|
+
const { updateMemberStatus } = useMemberRepo();
|
|
25805
|
+
for (const row of rows)
|
|
25806
|
+
await updateMemberStatus(row._id, { status: "inactive" });
|
|
25807
|
+
const roles = await leftoverRoles(invite, senderOrg, user);
|
|
25808
|
+
if (!rows.length && !defaultOrgIsSender && !roles.length) {
|
|
25706
25809
|
return {
|
|
25707
25810
|
message: "Nothing to repair: this account is not in the sender's organisation.",
|
|
25708
25811
|
changed: 0
|
|
25709
25812
|
};
|
|
25710
25813
|
}
|
|
25711
|
-
|
|
25712
|
-
for (const row of rows)
|
|
25713
|
-
await updateMemberStatus(row._id, { status: "inactive" });
|
|
25814
|
+
await setRoleStatus(roles.map((r) => r._id), "deleted");
|
|
25714
25815
|
if (defaultOrgIsSender)
|
|
25715
25816
|
await setDefaultOrg(String(user._id), "");
|
|
25716
25817
|
await _transition({
|
|
@@ -25719,6 +25820,7 @@ function useServiceProviderInviteService() {
|
|
|
25719
25820
|
event: {
|
|
25720
25821
|
...event("wrong-join-repaired", actor),
|
|
25721
25822
|
memberIds: rows.map((row) => row._id),
|
|
25823
|
+
roleIds: roles.map((r) => r._id),
|
|
25722
25824
|
...defaultOrgIsSender ? { defaultOrgWas: senderOrg } : {}
|
|
25723
25825
|
}
|
|
25724
25826
|
});
|
|
@@ -25727,8 +25829,8 @@ function useServiceProviderInviteService() {
|
|
|
25727
25829
|
message: `[sp-invite] wrong-join repaired invite=${id} memberships=${rows.length} by=${actor.id}`
|
|
25728
25830
|
});
|
|
25729
25831
|
return {
|
|
25730
|
-
message: `Repaired: ${rows.length} membership${rows.length === 1 ? "" : "s"} in the sender's organisation set inactive. Undo is available
|
|
25731
|
-
changed: rows.length
|
|
25832
|
+
message: `Repaired: ${rows.length} membership${rows.length === 1 ? "" : "s"} in the sender's organisation set inactive` + (roles.length ? `, ${roles.length} unused full-access role${roles.length === 1 ? "" : "s"} removed from its role list` : "") + ". Undo is available.",
|
|
25833
|
+
changed: rows.length + roles.length
|
|
25732
25834
|
};
|
|
25733
25835
|
}
|
|
25734
25836
|
async function undoWrongJoin({ id, userId }) {
|
|
@@ -25740,6 +25842,7 @@ function useServiceProviderInviteService() {
|
|
|
25740
25842
|
const ids2 = (repaired.memberIds ?? []).filter((m) => import_mongodb56.ObjectId.isValid(m));
|
|
25741
25843
|
for (const m of ids2)
|
|
25742
25844
|
await updateMemberStatus(m, { status: "active" }).catch(() => 0);
|
|
25845
|
+
await setRoleStatus(repaired.roleIds ?? [], "active");
|
|
25743
25846
|
const { user } = await wrongJoinContext(id, userId);
|
|
25744
25847
|
if (repaired.defaultOrgWas && user?._id && !String(user.defaultOrg ?? "")) {
|
|
25745
25848
|
await setDefaultOrg(String(user._id), String(repaired.defaultOrgWas));
|