@cosmicdrift/kumiko-bundled-features 0.355.0 → 0.357.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/dist/auth-mfa/i18n.js +1 -0
- package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
- package/dist/auth-mfa/screens.js +8 -1
- package/dist/billing-foundation/entities.d.ts +8 -0
- package/dist/billing-foundation/projection.d.ts +8 -0
- package/dist/cap-counter/entity.d.ts +1 -0
- package/dist/cap-counter/feature.js +7 -1
- package/dist/cap-overview/screens.js +3 -1
- package/dist/compliance-profiles/handlers/set-profile.write.js +14 -22
- package/dist/config/table.d.ts +6 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.js +15 -3
- package/dist/custom-fields/entity.d.ts +4 -0
- package/dist/custom-fields/handlers/define-system-field.write.js +8 -16
- package/dist/custom-fields/handlers/delete-system-field.write.js +5 -2
- package/dist/custom-fields/lib/define-or-resurrect.d.ts +2 -1
- package/dist/custom-fields/lib/define-or-resurrect.js +11 -4
- package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
- package/dist/delivery/chat-webhook-sender.js +1 -1
- package/dist/delivery/constants.d.ts +1 -0
- package/dist/delivery/constants.js +4 -0
- package/dist/delivery/delivery-service.js +62 -25
- package/dist/delivery/feature.js +7 -1
- package/dist/delivery/jobs.js +25 -1
- package/dist/delivery/tables.d.ts +6 -0
- package/dist/delivery/testing.d.ts +1 -0
- package/dist/document-ingest-foundation/entity.d.ts +4 -0
- package/dist/folders/entity.d.ts +8 -0
- package/dist/form-draft/entity.d.ts +2 -0
- package/dist/inbound-mail-foundation/entities.d.ts +25 -0
- package/dist/inbound-mail-foundation/projection.d.ts +20 -0
- package/dist/jobs/job-run-table.d.ts +6 -0
- package/dist/ledger/entity.d.ts +13 -0
- package/dist/managed-pages/handlers/set.write.js +6 -17
- package/dist/managed-pages/table.d.ts +12 -0
- package/dist/notes-history/entity.d.ts +12 -0
- package/dist/notes-history/entity.js +7 -6
- package/dist/notes-history-user-data/hooks.d.ts +10 -1
- package/dist/notes-history-user-data/hooks.js +78 -61
- package/dist/notes-history-user-data/index.d.ts +1 -0
- package/dist/notes-history-user-data/index.js +2 -1
- package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
- package/dist/personal-access-tokens/screens.js +2 -0
- package/dist/secrets/table.d.ts +1 -0
- package/dist/sessions/schema/user-session.d.ts +4 -0
- package/dist/sessions/screens.js +2 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/require-for-tenant.d.ts +2 -0
- package/dist/shared/require-for-tenant.js +9 -0
- package/dist/tags/entity.d.ts +9 -0
- package/dist/template-resolver/handlers/collection-set.write.js +4 -4
- package/dist/template-resolver/handlers/set.write.js +6 -7
- package/dist/template-resolver/handlers/upsert-system.write.js +6 -6
- package/dist/template-resolver/handlers/upsert-tenant.write.js +6 -4
- package/dist/template-resolver/table.d.ts +10 -0
- package/dist/template-resolver/user-content-table.d.ts +10 -0
- package/dist/tenant/cancel-pending-invitation.d.ts +2 -2
- package/dist/tenant/cancel-pending-invitation.js +2 -2
- package/dist/tenant/constants.d.ts +1 -0
- package/dist/tenant/constants.js +6 -3
- package/dist/tenant/feature.js +2 -0
- package/dist/tenant/handlers/member-role-availability.query.d.ts +7 -0
- package/dist/tenant/handlers/member-role-availability.query.js +18 -0
- package/dist/tenant/handlers/remove-member.write.js +13 -10
- package/dist/tenant/handlers/update-member-roles.write.js +12 -9
- package/dist/tenant/invitation-table.d.ts +6 -0
- package/dist/tenant/member-role-options.d.ts +2 -0
- package/dist/tenant/member-role-options.js +4 -0
- package/dist/tenant/membership-table.d.ts +4 -0
- package/dist/tenant/schema/tenant.d.ts +6 -0
- package/dist/tenant/screens.d.ts +4 -2
- package/dist/tenant/screens.js +10 -6
- package/dist/tier-engine/entity.d.ts +2 -0
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +7 -18
- package/dist/tier-engine/tier-resolver.d.ts +2 -0
- package/dist/user/schema/user.d.ts +12 -0
- package/dist/user/screens.js +8 -1
- package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
- package/dist/user-data-rights/schema/download-token.d.ts +8 -0
- package/dist/user-data-rights/schema/export-job.d.ts +6 -0
- package/dist/user-data-rights/screens.js +8 -1
- package/dist/waitlist/entity.d.ts +14 -0
- package/dist/waitlist/screens.js +8 -1
- package/package.json +9 -9
- package/src/auth-mfa/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/delivery/changes.json +6 -0
- package/src/tenant/changes.json +6 -0
|
@@ -5,6 +5,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
5
5
|
readonly description?: string;
|
|
6
6
|
readonly maxLength?: number;
|
|
7
7
|
readonly minLength?: number;
|
|
8
|
+
readonly autoComplete?: string;
|
|
8
9
|
readonly required?: boolean;
|
|
9
10
|
readonly searchable?: boolean;
|
|
10
11
|
readonly sortable?: boolean;
|
|
@@ -53,6 +54,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
53
54
|
readonly description?: string;
|
|
54
55
|
readonly maxLength?: number;
|
|
55
56
|
readonly minLength?: number;
|
|
57
|
+
readonly autoComplete?: string;
|
|
56
58
|
readonly required?: boolean;
|
|
57
59
|
readonly searchable?: boolean;
|
|
58
60
|
readonly sortable?: boolean;
|
|
@@ -75,6 +77,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
75
77
|
readonly description?: string;
|
|
76
78
|
readonly maxLength?: number;
|
|
77
79
|
readonly minLength?: number;
|
|
80
|
+
readonly autoComplete?: string;
|
|
78
81
|
readonly required?: boolean;
|
|
79
82
|
readonly searchable?: boolean;
|
|
80
83
|
readonly sortable?: boolean;
|
|
@@ -97,6 +100,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
97
100
|
readonly description?: string;
|
|
98
101
|
readonly maxLength?: number;
|
|
99
102
|
readonly minLength?: number;
|
|
103
|
+
readonly autoComplete?: string;
|
|
100
104
|
readonly required?: boolean;
|
|
101
105
|
readonly searchable?: boolean;
|
|
102
106
|
readonly sortable?: boolean;
|
|
@@ -213,6 +217,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
213
217
|
readonly description?: string;
|
|
214
218
|
readonly maxLength?: number;
|
|
215
219
|
readonly minLength?: number;
|
|
220
|
+
readonly autoComplete?: string;
|
|
216
221
|
readonly required?: boolean;
|
|
217
222
|
readonly searchable?: boolean;
|
|
218
223
|
readonly sortable?: boolean;
|
|
@@ -260,6 +265,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
260
265
|
readonly description?: string;
|
|
261
266
|
readonly maxLength?: number;
|
|
262
267
|
readonly minLength?: number;
|
|
268
|
+
readonly autoComplete?: string;
|
|
263
269
|
readonly required?: boolean;
|
|
264
270
|
readonly searchable?: boolean;
|
|
265
271
|
readonly sortable?: boolean;
|
|
@@ -308,6 +314,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
308
314
|
readonly description?: string;
|
|
309
315
|
readonly maxLength?: number;
|
|
310
316
|
readonly minLength?: number;
|
|
317
|
+
readonly autoComplete?: string;
|
|
311
318
|
readonly required?: boolean;
|
|
312
319
|
readonly searchable?: boolean;
|
|
313
320
|
readonly sortable?: boolean;
|
|
@@ -330,6 +337,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
330
337
|
readonly description?: string;
|
|
331
338
|
readonly maxLength?: number;
|
|
332
339
|
readonly minLength?: number;
|
|
340
|
+
readonly autoComplete?: string;
|
|
333
341
|
readonly required?: boolean;
|
|
334
342
|
readonly searchable?: boolean;
|
|
335
343
|
readonly sortable?: boolean;
|
|
@@ -352,6 +360,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
352
360
|
readonly description?: string;
|
|
353
361
|
readonly maxLength?: number;
|
|
354
362
|
readonly minLength?: number;
|
|
363
|
+
readonly autoComplete?: string;
|
|
355
364
|
readonly required?: boolean;
|
|
356
365
|
readonly searchable?: boolean;
|
|
357
366
|
readonly sortable?: boolean;
|
|
@@ -468,6 +477,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
468
477
|
readonly description?: string;
|
|
469
478
|
readonly maxLength?: number;
|
|
470
479
|
readonly minLength?: number;
|
|
480
|
+
readonly autoComplete?: string;
|
|
471
481
|
readonly required?: boolean;
|
|
472
482
|
readonly searchable?: boolean;
|
|
473
483
|
readonly sortable?: boolean;
|
|
@@ -5,6 +5,7 @@ export declare const userContentEntryEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
5
5
|
readonly description?: string;
|
|
6
6
|
readonly maxLength?: number;
|
|
7
7
|
readonly minLength?: number;
|
|
8
|
+
readonly autoComplete?: string;
|
|
8
9
|
readonly required?: boolean;
|
|
9
10
|
readonly searchable?: boolean;
|
|
10
11
|
readonly sortable?: boolean;
|
|
@@ -27,6 +28,7 @@ export declare const userContentEntryEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
27
28
|
readonly description?: string;
|
|
28
29
|
readonly maxLength?: number;
|
|
29
30
|
readonly minLength?: number;
|
|
31
|
+
readonly autoComplete?: string;
|
|
30
32
|
readonly required?: boolean;
|
|
31
33
|
readonly searchable?: boolean;
|
|
32
34
|
readonly sortable?: boolean;
|
|
@@ -75,6 +77,7 @@ export declare const userContentEntryEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
75
77
|
readonly description?: string;
|
|
76
78
|
readonly maxLength?: number;
|
|
77
79
|
readonly minLength?: number;
|
|
80
|
+
readonly autoComplete?: string;
|
|
78
81
|
readonly required?: boolean;
|
|
79
82
|
readonly searchable?: boolean;
|
|
80
83
|
readonly sortable?: boolean;
|
|
@@ -97,6 +100,7 @@ export declare const userContentEntryEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
97
100
|
readonly description?: string;
|
|
98
101
|
readonly maxLength?: number;
|
|
99
102
|
readonly minLength?: number;
|
|
103
|
+
readonly autoComplete?: string;
|
|
100
104
|
readonly required?: boolean;
|
|
101
105
|
readonly searchable?: boolean;
|
|
102
106
|
readonly sortable?: boolean;
|
|
@@ -119,6 +123,7 @@ export declare const userContentEntryEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
119
123
|
readonly description?: string;
|
|
120
124
|
readonly maxLength?: number;
|
|
121
125
|
readonly minLength?: number;
|
|
126
|
+
readonly autoComplete?: string;
|
|
122
127
|
readonly required?: boolean;
|
|
123
128
|
readonly searchable?: boolean;
|
|
124
129
|
readonly sortable?: boolean;
|
|
@@ -183,6 +188,7 @@ export declare const userContentEntriesTable: import("@cosmicdrift/kumiko-framew
|
|
|
183
188
|
readonly description?: string;
|
|
184
189
|
readonly maxLength?: number;
|
|
185
190
|
readonly minLength?: number;
|
|
191
|
+
readonly autoComplete?: string;
|
|
186
192
|
readonly required?: boolean;
|
|
187
193
|
readonly searchable?: boolean;
|
|
188
194
|
readonly sortable?: boolean;
|
|
@@ -205,6 +211,7 @@ export declare const userContentEntriesTable: import("@cosmicdrift/kumiko-framew
|
|
|
205
211
|
readonly description?: string;
|
|
206
212
|
readonly maxLength?: number;
|
|
207
213
|
readonly minLength?: number;
|
|
214
|
+
readonly autoComplete?: string;
|
|
208
215
|
readonly required?: boolean;
|
|
209
216
|
readonly searchable?: boolean;
|
|
210
217
|
readonly sortable?: boolean;
|
|
@@ -253,6 +260,7 @@ export declare const userContentEntriesTable: import("@cosmicdrift/kumiko-framew
|
|
|
253
260
|
readonly description?: string;
|
|
254
261
|
readonly maxLength?: number;
|
|
255
262
|
readonly minLength?: number;
|
|
263
|
+
readonly autoComplete?: string;
|
|
256
264
|
readonly required?: boolean;
|
|
257
265
|
readonly searchable?: boolean;
|
|
258
266
|
readonly sortable?: boolean;
|
|
@@ -275,6 +283,7 @@ export declare const userContentEntriesTable: import("@cosmicdrift/kumiko-framew
|
|
|
275
283
|
readonly description?: string;
|
|
276
284
|
readonly maxLength?: number;
|
|
277
285
|
readonly minLength?: number;
|
|
286
|
+
readonly autoComplete?: string;
|
|
278
287
|
readonly required?: boolean;
|
|
279
288
|
readonly searchable?: boolean;
|
|
280
289
|
readonly sortable?: boolean;
|
|
@@ -297,6 +306,7 @@ export declare const userContentEntriesTable: import("@cosmicdrift/kumiko-framew
|
|
|
297
306
|
readonly description?: string;
|
|
298
307
|
readonly maxLength?: number;
|
|
299
308
|
readonly minLength?: number;
|
|
309
|
+
readonly autoComplete?: string;
|
|
300
310
|
readonly required?: boolean;
|
|
301
311
|
readonly searchable?: boolean;
|
|
302
312
|
readonly sortable?: boolean;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type TenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
-
import type { SessionUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import type { WriteFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
4
|
import type { Redis } from "ioredis";
|
|
5
5
|
export declare function cancelPendingInvitation(db: TenantDb, invitation: {
|
|
6
6
|
readonly id: string;
|
|
7
7
|
readonly version: number;
|
|
8
|
-
}, actor: SessionUser, redis: Redis | undefined): Promise<WriteFailure | undefined>;
|
|
8
|
+
}, actor: SessionUser, redis: Redis | undefined, streamTenantId?: TenantId): Promise<WriteFailure | undefined>;
|
|
@@ -5,12 +5,12 @@ import { INVITATION_STATUS, tenantInvitationEntity, tenantInvitationsTable, } fr
|
|
|
5
5
|
const invitationExecutor = createEventStoreExecutor(tenantInvitationsTable, tenantInvitationEntity, { entityName: "tenant-invitation" });
|
|
6
6
|
// Shared by cancel-invitation and remove-member so the cancel behavior
|
|
7
7
|
// (status transition + token invalidation) cannot drift between them.
|
|
8
|
-
export async function cancelPendingInvitation(db, invitation, actor, redis) {
|
|
8
|
+
export async function cancelPendingInvitation(db, invitation, actor, redis, streamTenantId) {
|
|
9
9
|
const updateResult = await invitationExecutor.update({
|
|
10
10
|
id: invitation.id,
|
|
11
11
|
version: invitation.version,
|
|
12
12
|
changes: { status: INVITATION_STATUS.cancelled },
|
|
13
|
-
}, actor, db);
|
|
13
|
+
}, actor, db, { streamTenantId });
|
|
14
14
|
if (!updateResult.isSuccess)
|
|
15
15
|
return updateResult;
|
|
16
16
|
// Delete the token from Redis (if still there). If Redis is
|
|
@@ -33,6 +33,7 @@ export declare const TenantQueries: {
|
|
|
33
33
|
readonly resolveUserIds: "tenant:query:resolve-user-ids";
|
|
34
34
|
readonly invitations: "tenant:query:invitations";
|
|
35
35
|
readonly teamList: "tenant:query:team:list";
|
|
36
|
+
readonly memberRoleAvailability: "tenant:query:member-role-availability";
|
|
36
37
|
};
|
|
37
38
|
export declare const TenantErrors: {
|
|
38
39
|
readonly membershipNotFound: "membership_not_found";
|
package/dist/tenant/constants.js
CHANGED
|
@@ -19,9 +19,10 @@ export const MEMBER_ROLES_CELL_COMPONENT = "MemberRolesCell";
|
|
|
19
19
|
// Admin (rank 2) shares the invite-create screen with TenantAdmin — TenantAdmin
|
|
20
20
|
// must not appear here or Admin sees an unassignable option (fw#2414).
|
|
21
21
|
export const DEFAULT_INVITE_ROLE_OPTIONS = ["User", "Editor", "Admin"];
|
|
22
|
-
// Full
|
|
23
|
-
//
|
|
24
|
-
//
|
|
22
|
+
// Full rank list for memberRolesEditScreen: it is prefilled with a member's
|
|
23
|
+
// *current* roles, so it must represent every rank a membership can hold,
|
|
24
|
+
// including TenantAdmin (fw#2452). The screen hides what the caller may not
|
|
25
|
+
// assign via TenantQueries.memberRoleAvailability.
|
|
25
26
|
export const OWNER_INVITE_ROLE_OPTIONS = [
|
|
26
27
|
"User",
|
|
27
28
|
"Member",
|
|
@@ -58,6 +59,8 @@ export const TenantQueries = {
|
|
|
58
59
|
invitations: "tenant:query:invitations",
|
|
59
60
|
// Combined active-members + pending-invitations list backing /members.
|
|
60
61
|
teamList: "tenant:query:team:list",
|
|
62
|
+
// Per-role assignability for the caller, mirrors the rank rule of updateMemberRoles.
|
|
63
|
+
memberRoleAvailability: "tenant:query:member-role-availability",
|
|
61
64
|
};
|
|
62
65
|
// Error codes
|
|
63
66
|
export const TenantErrors = {
|
package/dist/tenant/feature.js
CHANGED
|
@@ -7,6 +7,7 @@ import { invitationsQuery } from "./handlers/invitations.query.js";
|
|
|
7
7
|
import { listQuery } from "./handlers/list.query.js";
|
|
8
8
|
import { meQuery } from "./handlers/me.query.js";
|
|
9
9
|
import { memberDirectoryQuery } from "./handlers/member-directory.query.js";
|
|
10
|
+
import { memberRoleAvailabilityQuery } from "./handlers/member-role-availability.query.js";
|
|
10
11
|
import { membersQuery } from "./handlers/members.query.js";
|
|
11
12
|
import { membershipsQuery } from "./handlers/memberships.query.js";
|
|
12
13
|
import { removeMemberWrite } from "./handlers/remove-member.write.js";
|
|
@@ -118,6 +119,7 @@ export function createTenantFeature(options) {
|
|
|
118
119
|
resolveUserIds: r.queryHandler(resolveUserIdsQuery),
|
|
119
120
|
invitations: r.queryHandler(invitationsQuery),
|
|
120
121
|
teamList: r.queryHandler(teamListQuery),
|
|
122
|
+
memberRoleAvailability: r.queryHandler(memberRoleAvailabilityQuery),
|
|
121
123
|
};
|
|
122
124
|
// Entity-convention handlers for the SystemAdmin entityList/entityEdit
|
|
123
125
|
// screens. The feature's original handlers predate the `<entity>:<verb>`
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const memberRoleAvailabilityQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"member-role-availability", z.ZodObject<{}, z.core.$strip>, {
|
|
3
|
+
rows: {
|
|
4
|
+
value: string;
|
|
5
|
+
disabled: boolean;
|
|
6
|
+
}[];
|
|
7
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { access, assignableAppRolesOf, canActorAssignRole, defineQueryHandler, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { memberRoleOptions } from "../member-role-options.js";
|
|
4
|
+
export const memberRoleAvailabilityQuery = defineQueryHandler({
|
|
5
|
+
name: "member-role-availability",
|
|
6
|
+
schema: z.object({}),
|
|
7
|
+
access: { roles: access.admin },
|
|
8
|
+
description: "Lists every role the member roles form offers and whether the caller's own rank may assign it; the form hides the roles marked disabled.",
|
|
9
|
+
handler: async (query, ctx) => {
|
|
10
|
+
const assignableAppRoles = assignableAppRolesOf(ctx.registry);
|
|
11
|
+
return {
|
|
12
|
+
rows: memberRoleOptions(assignableAppRoles).map((value) => ({
|
|
13
|
+
value,
|
|
14
|
+
disabled: !canActorAssignRole(query.user.roles, value, assignableAppRoles),
|
|
15
|
+
})),
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import { createEventStoreExecutor, } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
-
import { createSystemUser, defineWriteHandler, withResponseData, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { createSystemUser, defineWriteHandler, parseTenantId, withResponseData, } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import { InternalError, NotFoundError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
5
|
import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
|
|
6
6
|
import * as z from "zod";
|
|
@@ -20,7 +20,7 @@ async function cancelPendingInvitationsOfUser(db, options) {
|
|
|
20
20
|
const email = (await decryptStoredPii(user.email, "email", "tenant:remove-member")).toLowerCase();
|
|
21
21
|
const pendingInvitations = await selectMany(db, tenantInvitationsTable, { tenantId: options.tenantId, email, status: INVITATION_STATUS.pending });
|
|
22
22
|
for (const invitation of pendingInvitations) {
|
|
23
|
-
const failure = await cancelPendingInvitation(db, invitation, options.actor, options.redis);
|
|
23
|
+
const failure = await cancelPendingInvitation(db, invitation, options.actor, options.redis, options.streamTenantId);
|
|
24
24
|
if (failure)
|
|
25
25
|
return failure;
|
|
26
26
|
}
|
|
@@ -43,11 +43,14 @@ export const removeMemberWrite = defineWriteHandler({
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
const db = ctx.systemDb.acknowledgeCrossTenant("SystemAdmin manages memberships across tenants");
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const streamTenantId = parseTenantId(event.payload.tenantId);
|
|
47
|
+
const existing = streamTenantId === null
|
|
48
|
+
? undefined
|
|
49
|
+
: await fetchOne(db, tenantMembershipsTable, {
|
|
50
|
+
userId: event.payload.userId,
|
|
51
|
+
tenantId: streamTenantId,
|
|
52
|
+
});
|
|
53
|
+
if (!existing || streamTenantId === null) {
|
|
51
54
|
return writeFailure(new NotFoundError("membership", undefined, {
|
|
52
55
|
i18nKey: "tenant.errors.membershipNotFound",
|
|
53
56
|
i18nParams: { userId: event.payload.userId, tenantId: event.payload.tenantId },
|
|
@@ -75,14 +78,14 @@ export const removeMemberWrite = defineWriteHandler({
|
|
|
75
78
|
});
|
|
76
79
|
}
|
|
77
80
|
const result = await executor.delete({ id: existing["id"] }, // @cast-boundary db-row
|
|
78
|
-
event.user, db);
|
|
81
|
+
event.user, db, { streamTenantId });
|
|
79
82
|
if (!result.isSuccess)
|
|
80
83
|
return result;
|
|
81
|
-
// Actor tenant = invitation tenant so the update hits the invitation's stream.
|
|
82
84
|
const cancelFailure = await cancelPendingInvitationsOfUser(db, {
|
|
83
85
|
userId: event.payload.userId,
|
|
84
86
|
tenantId: event.payload.tenantId,
|
|
85
|
-
actor:
|
|
87
|
+
actor: event.user,
|
|
88
|
+
streamTenantId,
|
|
86
89
|
redis: ctx.redis,
|
|
87
90
|
});
|
|
88
91
|
if (cancelFailure !== undefined)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
-
import { access, assignableAppRolesOf, createSystemUser, defineWriteHandler, findForbiddenRoleAssignment, mergeAssignedRoles, SYSTEM_ROLE, withResponseData, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { access, assignableAppRolesOf, createSystemUser, defineWriteHandler, findForbiddenRoleAssignment, mayOverrideTenant, mergeAssignedRoles, parseTenantId, SYSTEM_ROLE, withResponseData, } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import { InternalError, NotFoundError, ValidationError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
5
|
import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
|
|
6
6
|
import * as z from "zod";
|
|
@@ -17,7 +17,12 @@ const REVOKE_ALL_SESSIONS_QN = "sessions:write:user-session:revoke-all-for-user"
|
|
|
17
17
|
/** SystemAdmin may pass payload.tenantId (cross-tenant); the members actionForm
|
|
18
18
|
* only sends userId+roles, so fall back to the active session tenant. */
|
|
19
19
|
function resolveTargetTenantId(isSystem, payloadTenantId, sessionTenantId) {
|
|
20
|
-
|
|
20
|
+
if (!isSystem || payloadTenantId === undefined)
|
|
21
|
+
return sessionTenantId;
|
|
22
|
+
return parseTenantId(payloadTenantId) ?? undefined;
|
|
23
|
+
}
|
|
24
|
+
function foreignStreamTenant(user, targetTenantId) {
|
|
25
|
+
return targetTenantId === user.tenantId ? undefined : targetTenantId;
|
|
21
26
|
}
|
|
22
27
|
// Editing your OWN roles keeps the current session: roles are re-derived per
|
|
23
28
|
// request, so the new roles apply at once without a re-login. Other sessions
|
|
@@ -49,7 +54,7 @@ export const updateMemberRolesWrite = defineWriteHandler({
|
|
|
49
54
|
});
|
|
50
55
|
}
|
|
51
56
|
const isSystemActor = event.user.roles.includes(SYSTEM_ROLE);
|
|
52
|
-
const isSystem = event.user
|
|
57
|
+
const isSystem = mayOverrideTenant(event.user) || isSystemActor;
|
|
53
58
|
const assignableAppRoles = assignableAppRolesOf(ctx.registry);
|
|
54
59
|
const targetTenantId = resolveTargetTenantId(isSystem, event.payload.tenantId, event.user.tenantId);
|
|
55
60
|
if (!targetTenantId) {
|
|
@@ -117,16 +122,14 @@ export const updateMemberRolesWrite = defineWriteHandler({
|
|
|
117
122
|
if (revoker) {
|
|
118
123
|
await ctx.writeAs(createSystemUser(targetTenantId), REVOKE_ALL_SESSIONS_QN, revokeSessionsPayload(event.payload.userId, targetTenantId, event.user));
|
|
119
124
|
}
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
// Keep the real actor for audit metadata; only override tenantId so
|
|
124
|
-
// streamTenantFor writes into the membership tenant (#2401).
|
|
125
|
+
// The membership stream lives on the membership's tenant (where seedTenantMembership/
|
|
126
|
+
// invite-accept wrote it); a cross-tenant operator addresses it without becoming a member.
|
|
127
|
+
// Same-tenant writes run on a tenant-mode db, which refuses a stream override.
|
|
125
128
|
const result = await executor.update({
|
|
126
129
|
id: row["id"], // @cast-boundary db-row
|
|
127
130
|
version: row["version"], // @cast-boundary db-row
|
|
128
131
|
changes: { roles: JSON.stringify(nextRoles) },
|
|
129
|
-
}, {
|
|
132
|
+
}, event.user, db, { streamTenantId: foreignStreamTenant(event.user, targetTenantId) });
|
|
130
133
|
return withResponseData(result, {
|
|
131
134
|
...event.payload,
|
|
132
135
|
roles: nextRoles,
|
|
@@ -12,6 +12,7 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
12
12
|
readonly description?: string;
|
|
13
13
|
readonly maxLength?: number;
|
|
14
14
|
readonly minLength?: number;
|
|
15
|
+
readonly autoComplete?: string;
|
|
15
16
|
readonly required?: boolean;
|
|
16
17
|
readonly searchable?: boolean;
|
|
17
18
|
readonly sortable?: boolean;
|
|
@@ -34,6 +35,7 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
34
35
|
readonly description?: string;
|
|
35
36
|
readonly maxLength?: number;
|
|
36
37
|
readonly minLength?: number;
|
|
38
|
+
readonly autoComplete?: string;
|
|
37
39
|
readonly required?: boolean;
|
|
38
40
|
readonly searchable?: boolean;
|
|
39
41
|
readonly sortable?: boolean;
|
|
@@ -81,6 +83,7 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
81
83
|
readonly description?: string;
|
|
82
84
|
readonly maxLength?: number;
|
|
83
85
|
readonly minLength?: number;
|
|
86
|
+
readonly autoComplete?: string;
|
|
84
87
|
readonly required?: boolean;
|
|
85
88
|
readonly searchable?: boolean;
|
|
86
89
|
readonly sortable?: boolean;
|
|
@@ -139,6 +142,7 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
|
|
|
139
142
|
readonly description?: string;
|
|
140
143
|
readonly maxLength?: number;
|
|
141
144
|
readonly minLength?: number;
|
|
145
|
+
readonly autoComplete?: string;
|
|
142
146
|
readonly required?: boolean;
|
|
143
147
|
readonly searchable?: boolean;
|
|
144
148
|
readonly sortable?: boolean;
|
|
@@ -161,6 +165,7 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
|
|
|
161
165
|
readonly description?: string;
|
|
162
166
|
readonly maxLength?: number;
|
|
163
167
|
readonly minLength?: number;
|
|
168
|
+
readonly autoComplete?: string;
|
|
164
169
|
readonly required?: boolean;
|
|
165
170
|
readonly searchable?: boolean;
|
|
166
171
|
readonly sortable?: boolean;
|
|
@@ -208,6 +213,7 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
|
|
|
208
213
|
readonly description?: string;
|
|
209
214
|
readonly maxLength?: number;
|
|
210
215
|
readonly minLength?: number;
|
|
216
|
+
readonly autoComplete?: string;
|
|
211
217
|
readonly required?: boolean;
|
|
212
218
|
readonly searchable?: boolean;
|
|
213
219
|
readonly sortable?: boolean;
|
|
@@ -4,6 +4,7 @@ export declare const tenantMembershipEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
6
|
readonly minLength?: number;
|
|
7
|
+
readonly autoComplete?: string;
|
|
7
8
|
readonly required?: boolean;
|
|
8
9
|
readonly searchable?: boolean;
|
|
9
10
|
readonly sortable?: boolean;
|
|
@@ -26,6 +27,7 @@ export declare const tenantMembershipEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
26
27
|
readonly description?: string;
|
|
27
28
|
readonly maxLength?: number;
|
|
28
29
|
readonly minLength?: number;
|
|
30
|
+
readonly autoComplete?: string;
|
|
29
31
|
readonly required?: boolean;
|
|
30
32
|
readonly searchable?: boolean;
|
|
31
33
|
readonly sortable?: boolean;
|
|
@@ -50,6 +52,7 @@ export declare const tenantMembershipsTable: import("@cosmicdrift/kumiko-framewo
|
|
|
50
52
|
readonly description?: string;
|
|
51
53
|
readonly maxLength?: number;
|
|
52
54
|
readonly minLength?: number;
|
|
55
|
+
readonly autoComplete?: string;
|
|
53
56
|
readonly required?: boolean;
|
|
54
57
|
readonly searchable?: boolean;
|
|
55
58
|
readonly sortable?: boolean;
|
|
@@ -72,6 +75,7 @@ export declare const tenantMembershipsTable: import("@cosmicdrift/kumiko-framewo
|
|
|
72
75
|
readonly description?: string;
|
|
73
76
|
readonly maxLength?: number;
|
|
74
77
|
readonly minLength?: number;
|
|
78
|
+
readonly autoComplete?: string;
|
|
75
79
|
readonly required?: boolean;
|
|
76
80
|
readonly searchable?: boolean;
|
|
77
81
|
readonly sortable?: boolean;
|
|
@@ -6,6 +6,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
6
6
|
readonly description?: string;
|
|
7
7
|
readonly maxLength?: number;
|
|
8
8
|
readonly minLength?: number;
|
|
9
|
+
readonly autoComplete?: string;
|
|
9
10
|
readonly required?: boolean;
|
|
10
11
|
readonly searchable?: boolean;
|
|
11
12
|
readonly sortable?: boolean;
|
|
@@ -28,6 +29,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
28
29
|
readonly description?: string;
|
|
29
30
|
readonly maxLength?: number;
|
|
30
31
|
readonly minLength?: number;
|
|
32
|
+
readonly autoComplete?: string;
|
|
31
33
|
readonly required?: boolean;
|
|
32
34
|
readonly searchable?: boolean;
|
|
33
35
|
readonly sortable?: boolean;
|
|
@@ -93,6 +95,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
|
|
|
93
95
|
readonly description?: string;
|
|
94
96
|
readonly maxLength?: number;
|
|
95
97
|
readonly minLength?: number;
|
|
98
|
+
readonly autoComplete?: string;
|
|
96
99
|
readonly required?: boolean;
|
|
97
100
|
readonly searchable?: boolean;
|
|
98
101
|
readonly sortable?: boolean;
|
|
@@ -162,6 +165,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
|
|
|
162
165
|
readonly description?: string;
|
|
163
166
|
readonly maxLength?: number;
|
|
164
167
|
readonly minLength?: number;
|
|
168
|
+
readonly autoComplete?: string;
|
|
165
169
|
readonly required?: boolean;
|
|
166
170
|
readonly searchable?: boolean;
|
|
167
171
|
readonly sortable?: boolean;
|
|
@@ -184,6 +188,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
|
|
|
184
188
|
readonly description?: string;
|
|
185
189
|
readonly maxLength?: number;
|
|
186
190
|
readonly minLength?: number;
|
|
191
|
+
readonly autoComplete?: string;
|
|
187
192
|
readonly required?: boolean;
|
|
188
193
|
readonly searchable?: boolean;
|
|
189
194
|
readonly sortable?: boolean;
|
|
@@ -249,6 +254,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
|
|
|
249
254
|
readonly description?: string;
|
|
250
255
|
readonly maxLength?: number;
|
|
251
256
|
readonly minLength?: number;
|
|
257
|
+
readonly autoComplete?: string;
|
|
252
258
|
readonly required?: boolean;
|
|
253
259
|
readonly searchable?: boolean;
|
|
254
260
|
readonly sortable?: boolean;
|
package/dist/tenant/screens.d.ts
CHANGED
|
@@ -69,7 +69,8 @@ export declare function createMemberRolesEditScreen(assignableAppRoles?: Assigna
|
|
|
69
69
|
};
|
|
70
70
|
roles: {
|
|
71
71
|
type: "multiSelect";
|
|
72
|
-
options: string[];
|
|
72
|
+
options: readonly string[];
|
|
73
|
+
optionsAvailabilityQuery: "tenant:query:member-role-availability";
|
|
73
74
|
required: true;
|
|
74
75
|
};
|
|
75
76
|
};
|
|
@@ -99,7 +100,8 @@ export declare const memberRolesEditScreen: {
|
|
|
99
100
|
};
|
|
100
101
|
roles: {
|
|
101
102
|
type: "multiSelect";
|
|
102
|
-
options: string[];
|
|
103
|
+
options: readonly string[];
|
|
104
|
+
optionsAvailabilityQuery: "tenant:query:member-role-availability";
|
|
103
105
|
required: true;
|
|
104
106
|
};
|
|
105
107
|
};
|
package/dist/tenant/screens.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AuthHandlers } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/constants";
|
|
2
2
|
import { access, i18nKey, isAssignableByRole, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
-
import { DEFAULT_INVITE_ROLE_OPTIONS, INVITE_CREATE_SCREEN_ID, MEMBER_ROLES_CELL_COMPONENT, MEMBER_ROLES_EDIT_SCREEN_ID, MEMBER_STATUS_CELL_COMPONENT, MEMBERS_SCREEN_ID,
|
|
3
|
+
import { DEFAULT_INVITE_ROLE_OPTIONS, INVITE_CREATE_SCREEN_ID, MEMBER_ROLES_CELL_COMPONENT, MEMBER_ROLES_EDIT_SCREEN_ID, MEMBER_STATUS_CELL_COMPONENT, MEMBERS_SCREEN_ID, TenantHandlers, TenantQueries, } from "./constants.js";
|
|
4
|
+
import { memberRoleOptions } from "./member-role-options.js";
|
|
4
5
|
// Cross-tenant SystemAdmin platform view of the tenants themselves. The tenant
|
|
5
6
|
// feature runs with `r.systemScope()`, so the entityList returns every tenant.
|
|
6
7
|
// Both screens are SystemAdmin-gated and inert until an app navs them.
|
|
@@ -83,6 +84,7 @@ export function createMembersScreen(options) {
|
|
|
83
84
|
field: "lastSeenAt",
|
|
84
85
|
label: i18nKey("tenant.members.col.lastActivity"),
|
|
85
86
|
renderer: { format: "timestamp" },
|
|
87
|
+
hideOnNarrow: true,
|
|
86
88
|
},
|
|
87
89
|
],
|
|
88
90
|
defaultSort: { field: "createdAt", dir: "desc" },
|
|
@@ -172,13 +174,15 @@ export function createMemberRolesEditScreen(assignableAppRoles = new Map()) {
|
|
|
172
174
|
cancelTarget: MEMBERS_SCREEN_ID,
|
|
173
175
|
fields: {
|
|
174
176
|
userId: { type: "text", required: true },
|
|
175
|
-
// Prefilled with the member's current roles, so the
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
177
|
+
// Prefilled with the member's current roles, so the static options must
|
|
178
|
+
// represent every rank (DEFAULT_INVITE_ROLE_OPTIONS omits TenantAdmin and
|
|
179
|
+
// would silently strip it on submit); the availability query hides what the
|
|
180
|
+
// caller may not assign. Enforcement stays server-side via
|
|
181
|
+
// findForbiddenRoleAssignment (update-member-roles.write.ts).
|
|
179
182
|
roles: {
|
|
180
183
|
type: "multiSelect",
|
|
181
|
-
options:
|
|
184
|
+
options: memberRoleOptions(assignableAppRoles),
|
|
185
|
+
optionsAvailabilityQuery: TenantQueries.memberRoleAvailability,
|
|
182
186
|
required: true,
|
|
183
187
|
},
|
|
184
188
|
},
|
|
@@ -4,6 +4,7 @@ export declare const tierAssignmentEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
4
4
|
readonly description?: string;
|
|
5
5
|
readonly maxLength?: number;
|
|
6
6
|
readonly minLength?: number;
|
|
7
|
+
readonly autoComplete?: string;
|
|
7
8
|
readonly required?: boolean;
|
|
8
9
|
readonly searchable?: boolean;
|
|
9
10
|
readonly sortable?: boolean;
|
|
@@ -26,6 +27,7 @@ export declare const tierAssignmentEntity: import("@cosmicdrift/kumiko-types/fie
|
|
|
26
27
|
readonly description?: string;
|
|
27
28
|
readonly maxLength?: number;
|
|
28
29
|
readonly minLength?: number;
|
|
30
|
+
readonly autoComplete?: string;
|
|
29
31
|
readonly required?: boolean;
|
|
30
32
|
readonly searchable?: boolean;
|
|
31
33
|
readonly sortable?: boolean;
|