@7365admin1/core 3.138.0 → 3.139.0
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 +6 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +168 -66
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +168 -66
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/service-provider-invite-email-new-company.e2e.test.mjs +105 -1
- package/test/member-working-membership.test.mjs +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @iservice365/core
|
|
2
2
|
|
|
3
|
+
## 3.139.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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).
|
|
8
|
+
|
|
3
9
|
## 3.138.0
|
|
4
10
|
|
|
5
11
|
### 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,53 @@ 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
|
+
function preferWorkingMembership(rows, defaultOrg = "") {
|
|
4406
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
4407
|
+
const working = list.filter((m) => WORKING.has(String(m.status ?? "")) && !m.deletedAt);
|
|
4408
|
+
const home = idOf(defaultOrg);
|
|
4409
|
+
return (home ? working.find((m) => idOf(m.org) === home) : void 0) ?? working[0] ?? list.find(isLiveMembership) ?? list[0] ?? null;
|
|
4410
|
+
}
|
|
4411
|
+
function memberListSiteScope(memberships, org) {
|
|
4412
|
+
const inOrg = (memberships ?? []).filter((m) => idOf(m.org) === idOf(org));
|
|
4413
|
+
const here = inOrg.filter((m) => m.type !== "resident");
|
|
4414
|
+
if (!inOrg.length)
|
|
4415
|
+
return null;
|
|
4416
|
+
if (here.some((m) => !idOf(m.siteId) || m.orgLevelRole))
|
|
4417
|
+
return null;
|
|
4418
|
+
return [...new Set(here.map((m) => idOf(m.siteId)))];
|
|
4419
|
+
}
|
|
4420
|
+
|
|
4374
4421
|
// src/repositories/member.repo.ts
|
|
4375
4422
|
var import_node_server_utils15 = require("@7365admin1/node-server-utils");
|
|
4376
4423
|
|
|
@@ -4493,10 +4540,10 @@ var import_mongodb13 = require("mongodb");
|
|
|
4493
4540
|
function crossSiteRoleMode() {
|
|
4494
4541
|
return (process.env.CROSS_SITE_ROLE_MODE ?? "").trim().toLowerCase() === "enforce" ? "enforce" : "log";
|
|
4495
4542
|
}
|
|
4496
|
-
var
|
|
4543
|
+
var idOf2 = (value) => value === null || value === void 0 ? "" : String(value?.toString?.() ?? value);
|
|
4497
4544
|
function isCrossSiteRole(role, memberSite) {
|
|
4498
|
-
const roleSite =
|
|
4499
|
-
return !!roleSite && roleSite !==
|
|
4545
|
+
const roleSite = idOf2(role?.site);
|
|
4546
|
+
return !!roleSite && roleSite !== idOf2(memberSite);
|
|
4500
4547
|
}
|
|
4501
4548
|
var THROTTLE_MS = 10 * 60 * 1e3;
|
|
4502
4549
|
var lastLogged = /* @__PURE__ */ new Map();
|
|
@@ -4505,7 +4552,7 @@ function crossSiteRoleGrants(input) {
|
|
|
4505
4552
|
return true;
|
|
4506
4553
|
const mode = crossSiteRoleMode();
|
|
4507
4554
|
const granted = mode !== "enforce";
|
|
4508
|
-
const key = [mode,
|
|
4555
|
+
const key = [mode, idOf2(input.member) || idOf2(input.user), idOf2(input.role?._id), input.where].join("|");
|
|
4509
4556
|
const now = Date.now();
|
|
4510
4557
|
if ((lastLogged.get(key) ?? 0) + THROTTLE_MS <= now) {
|
|
4511
4558
|
lastLogged.set(key, now);
|
|
@@ -4513,7 +4560,7 @@ function crossSiteRoleGrants(input) {
|
|
|
4513
4560
|
lastLogged.clear();
|
|
4514
4561
|
import_node_server_utils14.logger.log({
|
|
4515
4562
|
level: "warn",
|
|
4516
|
-
message: `cross-site-role mode=${mode} ${granted ? "granted" : "refused"} where=${input.where} member=${
|
|
4563
|
+
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
4564
|
});
|
|
4518
4565
|
}
|
|
4519
4566
|
return granted;
|
|
@@ -4524,7 +4571,7 @@ function crossSiteRoleRefusal(roleName, roleSiteName) {
|
|
|
4524
4571
|
return `${role} belongs to ${site}. Choose a role for this site, or an org-wide role.`;
|
|
4525
4572
|
}
|
|
4526
4573
|
async function requireRoleFitsSite(roleId, siteId) {
|
|
4527
|
-
const id =
|
|
4574
|
+
const id = idOf2(roleId);
|
|
4528
4575
|
if (!id || !import_mongodb13.ObjectId.isValid(id))
|
|
4529
4576
|
return;
|
|
4530
4577
|
const db2 = import_node_server_utils14.useAtlas.getDb();
|
|
@@ -4533,7 +4580,7 @@ async function requireRoleFitsSite(roleId, siteId) {
|
|
|
4533
4580
|
const role = await db2.collection("roles").findOne({ _id: new import_mongodb13.ObjectId(id) }, { projection: { name: 1, site: 1 } });
|
|
4534
4581
|
if (!isCrossSiteRole(role, siteId))
|
|
4535
4582
|
return;
|
|
4536
|
-
const site = import_mongodb13.ObjectId.isValid(
|
|
4583
|
+
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
4584
|
throw new import_node_server_utils14.BadRequestError(crossSiteRoleRefusal(role?.name, site?.name));
|
|
4538
4585
|
}
|
|
4539
4586
|
async function otherSiteRoleClauses(roles, org) {
|
|
@@ -4541,7 +4588,7 @@ async function otherSiteRoleClauses(roles, org) {
|
|
|
4541
4588
|
{ ...org ? { org } : { org: { $nin: ["", null] } }, site: { $nin: ["", null] }, status: "active" },
|
|
4542
4589
|
{ projection: { site: 1, org: 1 } }
|
|
4543
4590
|
).toArray();
|
|
4544
|
-
const both3 = (v) => import_mongodb13.ObjectId.isValid(
|
|
4591
|
+
const both3 = (v) => import_mongodb13.ObjectId.isValid(idOf2(v)) ? [new import_mongodb13.ObjectId(idOf2(v)), idOf2(v)] : [v];
|
|
4545
4592
|
const clauses = (list ?? []).map((r) => ({
|
|
4546
4593
|
role: r._id,
|
|
4547
4594
|
org: { $in: both3(r.org) },
|
|
@@ -4724,9 +4771,12 @@ function useMemberRepo() {
|
|
|
4724
4771
|
} catch (error) {
|
|
4725
4772
|
throw new import_node_server_utils15.BadRequestError("Invalid user ID format.");
|
|
4726
4773
|
}
|
|
4774
|
+
const account = await db2?.collection("users").findOne({ _id: user }, { projection: { defaultOrg: 1 } }).catch(() => null);
|
|
4775
|
+
const defaultOrg = String(account?.defaultOrg ?? "");
|
|
4727
4776
|
const cacheKey = (0, import_node_server_utils15.makeCacheKey)(namespace_collection3, {
|
|
4728
4777
|
user: user.toString(),
|
|
4729
|
-
type
|
|
4778
|
+
type,
|
|
4779
|
+
defaultOrg
|
|
4730
4780
|
});
|
|
4731
4781
|
const cachedData = await getCache(cacheKey);
|
|
4732
4782
|
if (cachedData) {
|
|
@@ -4734,7 +4784,10 @@ function useMemberRepo() {
|
|
|
4734
4784
|
return cachedData;
|
|
4735
4785
|
}
|
|
4736
4786
|
try {
|
|
4737
|
-
const data =
|
|
4787
|
+
const data = preferWorkingMembership(
|
|
4788
|
+
await collection.find({ user, type: appTypeQuery(type) }).toArray(),
|
|
4789
|
+
defaultOrg
|
|
4790
|
+
);
|
|
4738
4791
|
if (!data) {
|
|
4739
4792
|
throw new import_node_server_utils15.NotFoundError("Member not found.");
|
|
4740
4793
|
}
|
|
@@ -5197,7 +5250,12 @@ function useMemberRepo() {
|
|
|
5197
5250
|
const query2 = {
|
|
5198
5251
|
user,
|
|
5199
5252
|
org: { $nin: [null, ""] },
|
|
5200
|
-
type: appTypeQuery(type)
|
|
5253
|
+
type: appTypeQuery(type),
|
|
5254
|
+
// The organisation switcher. A membership that was taken away (Repair
|
|
5255
|
+
// sets it inactive, removal deletes it) must not keep the organisation
|
|
5256
|
+
// on the person's list: latitude.sec@ still saw JLL Singapore beside its
|
|
5257
|
+
// own company after the repair (owner report 2026-09-30).
|
|
5258
|
+
status: { $nin: ["inactive", "deleted"] }
|
|
5201
5259
|
};
|
|
5202
5260
|
const cacheOptions = {
|
|
5203
5261
|
page: page2,
|
|
@@ -11619,7 +11677,7 @@ function enforcedModuleList(stored, save) {
|
|
|
11619
11677
|
return NO_LIST;
|
|
11620
11678
|
return { list, since: typeof save?.createdAt === "string" ? save.createdAt : "" };
|
|
11621
11679
|
}
|
|
11622
|
-
var
|
|
11680
|
+
var idOf3 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
|
|
11623
11681
|
function deniedForMembership({
|
|
11624
11682
|
membership,
|
|
11625
11683
|
org,
|
|
@@ -11627,12 +11685,12 @@ function deniedForMembership({
|
|
|
11627
11685
|
site,
|
|
11628
11686
|
siteSave
|
|
11629
11687
|
}) {
|
|
11630
|
-
const memberOrg =
|
|
11688
|
+
const memberOrg = idOf3(membership?.org);
|
|
11631
11689
|
if (!membership || membership.type === "resident" || !memberOrg) {
|
|
11632
11690
|
return { denied: [], since: "" };
|
|
11633
11691
|
}
|
|
11634
11692
|
const orgTier = enforcedModuleList(org?.modules, orgSave);
|
|
11635
|
-
const siteTier = site &&
|
|
11693
|
+
const siteTier = site && idOf3(site.orgId) === memberOrg ? enforcedModuleList(site.metadata?.modules, siteSave) : NO_LIST;
|
|
11636
11694
|
const denied = deniedModules(orgTier.list, siteTier.list);
|
|
11637
11695
|
if (!denied.length)
|
|
11638
11696
|
return { denied: [], since: "" };
|
|
@@ -12201,46 +12259,6 @@ function staffMayBypass(actor, grant = CROSS_CLIENT_GRANT) {
|
|
|
12201
12259
|
return consoleRoleAllows(actor.staffPermissions, grant.resource, grant.action);
|
|
12202
12260
|
}
|
|
12203
12261
|
|
|
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
12262
|
// src/services/module-gate.service.ts
|
|
12245
12263
|
var idOf4 = (v) => v == null ? "" : String(v?._id ?? v).toLowerCase();
|
|
12246
12264
|
var isId = (v) => /^[0-9a-f]{24}$/i.test(v);
|
|
@@ -25553,7 +25571,7 @@ function useServiceProviderInviteService() {
|
|
|
25553
25571
|
);
|
|
25554
25572
|
}
|
|
25555
25573
|
const existing = await getByCreatorAndNature(actor.id, nature).catch(() => null);
|
|
25556
|
-
if (existing) {
|
|
25574
|
+
if (existing && String(existing.invitedFrom ?? "") !== String(invite._id)) {
|
|
25557
25575
|
throw new import_node_server_utils61.BadRequestError(
|
|
25558
25576
|
`You already have a ${serviceTypeLabel(nature)} company on iService365 (${existing.name}). Accept the invitation from that company instead.`
|
|
25559
25577
|
);
|
|
@@ -25584,12 +25602,21 @@ function useServiceProviderInviteService() {
|
|
|
25584
25602
|
country
|
|
25585
25603
|
}) {
|
|
25586
25604
|
const inviteeId = String(user?._id ?? "");
|
|
25587
|
-
|
|
25605
|
+
let name = sanitizeCompanyName(companyName) || sanitizeCompanyName(invite.metadata?.name) || sanitizeCompanyName(user?.name);
|
|
25588
25606
|
if (!name)
|
|
25589
25607
|
throw new import_node_server_utils61.BadRequestError("Enter your company's name.");
|
|
25590
25608
|
const email = String(invite.email ?? "").trim().toLowerCase();
|
|
25591
25609
|
const actor = { id: inviteeId };
|
|
25592
|
-
const
|
|
25610
|
+
const db2 = import_node_server_utils61.useAtlas.getDb();
|
|
25611
|
+
const actorIds = [actor.id, ...import_mongodb56.ObjectId.isValid(actor.id) ? [new import_mongodb56.ObjectId(actor.id)] : []];
|
|
25612
|
+
const made = db2 ? await db2.collection("organizations").findOne({
|
|
25613
|
+
invitedFrom: { $in: [String(invite._id), invite._id] },
|
|
25614
|
+
createdBy: { $in: actorIds },
|
|
25615
|
+
status: { $ne: "deleted" }
|
|
25616
|
+
}) : null;
|
|
25617
|
+
if (made)
|
|
25618
|
+
name = String(made.name ?? name);
|
|
25619
|
+
const orgId = made ? String(made._id) : String(
|
|
25593
25620
|
await addOrg({
|
|
25594
25621
|
name,
|
|
25595
25622
|
type: nature,
|
|
@@ -25601,8 +25628,17 @@ function useServiceProviderInviteService() {
|
|
|
25601
25628
|
invitedFrom: String(invite._id)
|
|
25602
25629
|
})
|
|
25603
25630
|
);
|
|
25604
|
-
const roles = await seedDefaultOrgRoles(useRoleRepo().addRole, orgId, nature);
|
|
25605
25631
|
const appType = serviceAppType(nature);
|
|
25632
|
+
const owners = made && db2 ? await db2.collection("roles").find({
|
|
25633
|
+
org: { $in: [new import_mongodb56.ObjectId(orgId), orgId] },
|
|
25634
|
+
name: "owner",
|
|
25635
|
+
status: { $ne: "deleted" }
|
|
25636
|
+
}).toArray() : [];
|
|
25637
|
+
const ownerRole = (type) => {
|
|
25638
|
+
const r = owners.find((x) => x.type === type);
|
|
25639
|
+
return r ? { id: String(r._id), type, name: "owner" } : null;
|
|
25640
|
+
};
|
|
25641
|
+
const roles = ownerRole("organization") && ownerRole(appType) ? [ownerRole("organization"), ownerRole(appType)] : await seedDefaultOrgRoles(useRoleRepo().addRole, orgId, nature);
|
|
25606
25642
|
const orgOwner = roles.find((r) => r.type === "organization" && r.name === "owner");
|
|
25607
25643
|
const appOwner = roles.find((r) => r.type === appType && r.name === "owner");
|
|
25608
25644
|
if (!orgOwner || !appOwner) {
|
|
@@ -25625,8 +25661,18 @@ function useServiceProviderInviteService() {
|
|
|
25625
25661
|
siteName: "",
|
|
25626
25662
|
onboardingRequired: false
|
|
25627
25663
|
};
|
|
25628
|
-
|
|
25629
|
-
|
|
25664
|
+
const hasOwnerRow = async (type) => !!made && !!await db2?.collection("members").findOne({
|
|
25665
|
+
user: { $in: actorIds },
|
|
25666
|
+
org: { $in: [new import_mongodb56.ObjectId(orgId), orgId] },
|
|
25667
|
+
type,
|
|
25668
|
+
status: "active"
|
|
25669
|
+
});
|
|
25670
|
+
if (!await hasOwnerRow("organization")) {
|
|
25671
|
+
await addMember({ ...base, role: orgOwner.id, type: "organization" });
|
|
25672
|
+
}
|
|
25673
|
+
if (!await hasOwnerRow(appType)) {
|
|
25674
|
+
await addMember({ ...base, role: appOwner.id, type: appType });
|
|
25675
|
+
}
|
|
25630
25676
|
await useUserRepo().updateUserFieldById({ _id: actor.id, field: "defaultOrg", value: orgId }).catch(() => void 0);
|
|
25631
25677
|
import_node_server_utils61.logger.log({
|
|
25632
25678
|
level: "info",
|
|
@@ -25682,10 +25728,60 @@ function useServiceProviderInviteService() {
|
|
|
25682
25728
|
const last = [...history].reverse().find((e) => ["wrong-join-repaired", "wrong-join-undone"].includes(e?.action));
|
|
25683
25729
|
return last?.action === "wrong-join-repaired" ? last : null;
|
|
25684
25730
|
}
|
|
25731
|
+
async function leftoverRoles(invite, senderOrg, user) {
|
|
25732
|
+
const db2 = import_node_server_utils61.useAtlas.getDb();
|
|
25733
|
+
if (!db2 || !user?._id || !import_mongodb56.ObjectId.isValid(senderOrg))
|
|
25734
|
+
return [];
|
|
25735
|
+
const since = new Date(String(invite.createdAt ?? 0)).getTime();
|
|
25736
|
+
const rows = await db2.collection("members").find({
|
|
25737
|
+
user: { $in: [user._id, String(user._id)] },
|
|
25738
|
+
org: { $in: [new import_mongodb56.ObjectId(senderOrg), senderOrg] }
|
|
25739
|
+
}).project({ role: 1 }).toArray();
|
|
25740
|
+
const roleIds = [
|
|
25741
|
+
...new Set(
|
|
25742
|
+
rows.map((m) => m.role?.toString?.() ?? "").filter((r) => import_mongodb56.ObjectId.isValid(r))
|
|
25743
|
+
)
|
|
25744
|
+
].map((r) => new import_mongodb56.ObjectId(r));
|
|
25745
|
+
if (!roleIds.length)
|
|
25746
|
+
return [];
|
|
25747
|
+
const roles = await db2.collection("roles").find({
|
|
25748
|
+
_id: { $in: roleIds },
|
|
25749
|
+
org: { $in: [new import_mongodb56.ObjectId(senderOrg), senderOrg] },
|
|
25750
|
+
status: { $ne: "deleted" }
|
|
25751
|
+
}).project({ name: 1, permissions: 1, createdAt: 1 }).toArray();
|
|
25752
|
+
const out = [];
|
|
25753
|
+
for (const role of roles) {
|
|
25754
|
+
if (!(Array.isArray(role.permissions) && role.permissions.includes("*")))
|
|
25755
|
+
continue;
|
|
25756
|
+
if (role.createdAt && Number.isFinite(since) && new Date(role.createdAt).getTime() < since)
|
|
25757
|
+
continue;
|
|
25758
|
+
const inUse = await db2.collection("members").countDocuments({
|
|
25759
|
+
role: { $in: [role._id, role._id.toString()] },
|
|
25760
|
+
status: "active"
|
|
25761
|
+
});
|
|
25762
|
+
if (inUse)
|
|
25763
|
+
continue;
|
|
25764
|
+
out.push({ _id: role._id.toString(), name: String(role.name ?? "") });
|
|
25765
|
+
}
|
|
25766
|
+
return out;
|
|
25767
|
+
}
|
|
25768
|
+
async function setRoleStatus(ids2, status) {
|
|
25769
|
+
const db2 = import_node_server_utils61.useAtlas.getDb();
|
|
25770
|
+
const valid = ids2.filter((r) => import_mongodb56.ObjectId.isValid(r)).map((r) => new import_mongodb56.ObjectId(r));
|
|
25771
|
+
if (!db2 || !valid.length)
|
|
25772
|
+
return;
|
|
25773
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25774
|
+
await db2.collection("roles").updateMany(
|
|
25775
|
+
{ _id: { $in: valid } },
|
|
25776
|
+
{ $set: { status, deletedAt: status === "deleted" ? now : "", updatedAt: now } }
|
|
25777
|
+
);
|
|
25778
|
+
await (0, import_node_server_utils61.useCache)("roles").delNamespace().catch(() => void 0);
|
|
25779
|
+
}
|
|
25685
25780
|
async function wrongJoinPreview({ id, userId }) {
|
|
25686
25781
|
const { invite, senderOrg, user } = await wrongJoinContext(id, userId);
|
|
25687
25782
|
const org = await getOrgById(senderOrg).catch(() => null);
|
|
25688
25783
|
const memberships = await wrongJoinRows(invite, senderOrg, user);
|
|
25784
|
+
const roles = await leftoverRoles(invite, senderOrg, user);
|
|
25689
25785
|
const repaired = lastRepair(invite);
|
|
25690
25786
|
return {
|
|
25691
25787
|
invitationId: id,
|
|
@@ -25694,6 +25790,8 @@ function useServiceProviderInviteService() {
|
|
|
25694
25790
|
senderOrgId: senderOrg,
|
|
25695
25791
|
senderOrgName: String(org?.name ?? ""),
|
|
25696
25792
|
memberships,
|
|
25793
|
+
// Left in the sender's role list by the old sign-up; Repair retires them.
|
|
25794
|
+
leftoverRoles: roles,
|
|
25697
25795
|
defaultOrgIsSender: String(user?.defaultOrg ?? "") === senderOrg,
|
|
25698
25796
|
repaired: repaired ? { at: repaired.at, by: repaired.byName, memberIds: repaired.memberIds ?? [] } : null
|
|
25699
25797
|
};
|
|
@@ -25702,15 +25800,17 @@ function useServiceProviderInviteService() {
|
|
|
25702
25800
|
const { actor, invite, senderOrg, user } = await wrongJoinContext(id, userId);
|
|
25703
25801
|
const rows = await wrongJoinRows(invite, senderOrg, user);
|
|
25704
25802
|
const defaultOrgIsSender = !!user?._id && String(user?.defaultOrg ?? "") === senderOrg;
|
|
25705
|
-
|
|
25803
|
+
const { updateMemberStatus } = useMemberRepo();
|
|
25804
|
+
for (const row of rows)
|
|
25805
|
+
await updateMemberStatus(row._id, { status: "inactive" });
|
|
25806
|
+
const roles = await leftoverRoles(invite, senderOrg, user);
|
|
25807
|
+
if (!rows.length && !defaultOrgIsSender && !roles.length) {
|
|
25706
25808
|
return {
|
|
25707
25809
|
message: "Nothing to repair: this account is not in the sender's organisation.",
|
|
25708
25810
|
changed: 0
|
|
25709
25811
|
};
|
|
25710
25812
|
}
|
|
25711
|
-
|
|
25712
|
-
for (const row of rows)
|
|
25713
|
-
await updateMemberStatus(row._id, { status: "inactive" });
|
|
25813
|
+
await setRoleStatus(roles.map((r) => r._id), "deleted");
|
|
25714
25814
|
if (defaultOrgIsSender)
|
|
25715
25815
|
await setDefaultOrg(String(user._id), "");
|
|
25716
25816
|
await _transition({
|
|
@@ -25719,6 +25819,7 @@ function useServiceProviderInviteService() {
|
|
|
25719
25819
|
event: {
|
|
25720
25820
|
...event("wrong-join-repaired", actor),
|
|
25721
25821
|
memberIds: rows.map((row) => row._id),
|
|
25822
|
+
roleIds: roles.map((r) => r._id),
|
|
25722
25823
|
...defaultOrgIsSender ? { defaultOrgWas: senderOrg } : {}
|
|
25723
25824
|
}
|
|
25724
25825
|
});
|
|
@@ -25727,8 +25828,8 @@ function useServiceProviderInviteService() {
|
|
|
25727
25828
|
message: `[sp-invite] wrong-join repaired invite=${id} memberships=${rows.length} by=${actor.id}`
|
|
25728
25829
|
});
|
|
25729
25830
|
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
|
|
25831
|
+
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.",
|
|
25832
|
+
changed: rows.length + roles.length
|
|
25732
25833
|
};
|
|
25733
25834
|
}
|
|
25734
25835
|
async function undoWrongJoin({ id, userId }) {
|
|
@@ -25740,6 +25841,7 @@ function useServiceProviderInviteService() {
|
|
|
25740
25841
|
const ids2 = (repaired.memberIds ?? []).filter((m) => import_mongodb56.ObjectId.isValid(m));
|
|
25741
25842
|
for (const m of ids2)
|
|
25742
25843
|
await updateMemberStatus(m, { status: "active" }).catch(() => 0);
|
|
25844
|
+
await setRoleStatus(repaired.roleIds ?? [], "active");
|
|
25743
25845
|
const { user } = await wrongJoinContext(id, userId);
|
|
25744
25846
|
if (repaired.defaultOrgWas && user?._id && !String(user.defaultOrg ?? "")) {
|
|
25745
25847
|
await setDefaultOrg(String(user._id), String(repaired.defaultOrgWas));
|