@cosmicdrift/kumiko-bundled-features 0.105.2 → 0.108.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/package.json +8 -6
- package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +3 -2
- package/src/auth-email-password/__tests__/email-verification.integration.test.ts +5 -9
- package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +3 -2
- package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +3 -2
- package/src/auth-email-password/__tests__/password-reset.integration.test.ts +4 -3
- package/src/auth-email-password/handlers/change-password.write.ts +11 -11
- package/src/auth-email-password/handlers/confirm-token-flow.ts +33 -104
- package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +44 -0
- package/src/auth-email-password/web/__tests__/user-menu.test.tsx +14 -1
- package/src/auth-email-password/web/auth-gate.tsx +3 -4
- package/src/auth-email-password/web/invite-accept-screen.tsx +7 -3
- package/src/auth-email-password/web/session.tsx +5 -1
- package/src/auth-email-password/web/user-menu.tsx +1 -1
- package/src/config/__tests__/deserialize-value.test.ts +3 -6
- package/src/custom-fields/__tests__/custom-fields.integration.test.ts +65 -0
- package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +4 -3
- package/src/custom-fields/constants.ts +1 -0
- package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
- package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
- package/src/custom-fields/wire-for-entity.ts +104 -77
- package/src/data-retention/__tests__/parse-override.test.ts +11 -6
- package/src/data-retention/resolve-for-tenant.ts +16 -4
- package/src/data-retention/run-retention-cleanup.ts +72 -10
- package/src/delivery/attempt-log.ts +22 -10
- package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +4 -4
- package/src/folders/__tests__/folders.integration.test.ts +26 -0
- package/src/folders/feature.ts +4 -2
- package/src/folders/handlers/delete-folder.write.ts +40 -0
- package/src/folders/web/__tests__/folder-manager.test.tsx +24 -0
- package/src/folders/web/folder-manager.tsx +18 -13
- package/src/folders/web/i18n.ts +0 -2
- package/src/folders-user-data/__tests__/hooks.integration.test.ts +69 -0
- package/src/folders-user-data/hooks.ts +31 -8
- package/src/folders-user-data/index.ts +4 -4
- package/src/jobs/feature.ts +23 -21
- package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
- package/src/ledger/__tests__/ledger.integration.test.ts +44 -0
- package/src/ledger/__tests__/reports.test.ts +20 -0
- package/src/ledger/entity.ts +1 -1
- package/src/ledger/handlers/confirm-schedule-period.write.ts +29 -13
- package/src/ledger/handlers/reverse-transaction.write.ts +44 -5
- package/src/ledger/recurring.ts +18 -9
- package/src/ledger/schemas.ts +4 -1
- package/src/ledger/web/index.ts +3 -2
- package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
- package/src/personal-access-tokens/__tests__/pat.integration.test.ts +204 -0
- package/src/personal-access-tokens/constants.ts +33 -0
- package/src/personal-access-tokens/feature.ts +78 -0
- package/src/personal-access-tokens/handlers/available-scopes.query.ts +15 -0
- package/src/personal-access-tokens/handlers/create.write.ts +41 -0
- package/src/personal-access-tokens/handlers/list.query.ts +47 -0
- package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
- package/src/personal-access-tokens/hash.ts +17 -0
- package/src/personal-access-tokens/index.ts +45 -0
- package/src/personal-access-tokens/resolver.ts +58 -0
- package/src/personal-access-tokens/roles.ts +29 -0
- package/src/personal-access-tokens/schema/api-token.ts +56 -0
- package/src/personal-access-tokens/scopes.ts +22 -0
- package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
- package/src/personal-access-tokens/web/i18n.ts +45 -0
- package/src/personal-access-tokens/web/index.ts +7 -0
- package/src/personal-access-tokens/web/pat-tokens-screen.tsx +150 -0
- package/src/renderer-simple/__tests__/template-resolver.integration.test.ts +2 -2
- package/src/sessions/__tests__/cleanup.integration.test.ts +3 -3
- package/src/sessions/__tests__/rebuild-survival.integration.test.ts +5 -9
- package/src/sessions/__tests__/sessions.integration.test.ts +14 -9
- package/src/sessions/schema/user-session.ts +8 -2
- package/src/sessions/session-callbacks.ts +9 -3
- package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
- package/src/tags/__tests__/tags.integration.test.ts +15 -0
- package/src/tags/handlers/assign-tag.write.ts +13 -3
- package/src/tags/schemas.ts +10 -4
- package/src/template-resolver/__tests__/template-resolver.integration.test.ts +2 -2
- package/src/template-resolver/testing.ts +4 -3
- package/src/tier-engine/feature.ts +22 -10
- package/src/tier-engine/handlers/set-tenant-tier.write.ts +11 -1
- package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
- package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
- package/src/user/index.ts +4 -0
- package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +4 -4
- package/src/user-data-rights/__tests__/audit-log.integration.test.ts +4 -4
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -4
- package/src/user-data-rights/__tests__/download.integration.test.ts +7 -7
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +5 -10
- package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +84 -0
- package/src/user-data-rights/__tests__/file-retention.integration.test.ts +3 -3
- package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +3 -2
- package/src/user-data-rights/__tests__/forget-test-helpers.ts +3 -2
- package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +3 -3
- package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +10 -6
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +4 -4
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -4
- package/src/user-data-rights/__tests__/request-export.integration.test.ts +4 -9
- package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +7 -3
- package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +8 -4
- package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +12 -17
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +9 -5
- package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +3 -3
- package/src/user-data-rights/handlers/download-attempt-list.query.ts +1 -1
- package/src/user-data-rights/handlers/download-by-job.query.ts +4 -6
- package/src/user-data-rights/handlers/export-job-detail.query.ts +1 -0
- package/src/user-data-rights/handlers/export-job-list.query.ts +1 -0
- package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
- package/src/user-data-rights/run-forget-cleanup.ts +8 -1
- package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
- package/src/user-data-rights/web/i18n.ts +0 -10
- package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +3 -2
- package/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +102 -54
- package/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +53 -15
- package/src/user-profile/__tests__/profile-screen.test.tsx +9 -0
- package/src/user-profile/handlers/change-email.write.ts +11 -8
- package/src/user-profile/web/profile-screen.tsx +10 -8
- package/src/auth-email-password/stream-tenant.ts +0 -31
- package/src/user-data-rights-defaults/db/queries/user-hook.ts +0 -17
|
@@ -18,12 +18,7 @@
|
|
|
18
18
|
// hasJob=false wenn nichts da
|
|
19
19
|
|
|
20
20
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
21
|
-
import {
|
|
22
|
-
asRawClient,
|
|
23
|
-
insertOne,
|
|
24
|
-
selectMany,
|
|
25
|
-
updateMany,
|
|
26
|
-
} from "@cosmicdrift/kumiko-framework/bun-db";
|
|
21
|
+
import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
27
22
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
28
23
|
import {
|
|
29
24
|
createTestUser,
|
|
@@ -32,7 +27,7 @@ import {
|
|
|
32
27
|
testTenantId,
|
|
33
28
|
unsafeCreateEntityTable,
|
|
34
29
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
35
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
30
|
+
import { resetTestTables, seedRow, updateRows } from "@cosmicdrift/kumiko-framework/testing";
|
|
36
31
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
37
32
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
38
33
|
import { createDataRetentionFeature } from "../../data-retention";
|
|
@@ -136,7 +131,7 @@ describe("request-export :: App-side-Idempotency (primaerer Pfad)", () => {
|
|
|
136
131
|
test("Klick nach done-Job ist NEUER Job (Audit-Historie wird nicht blockiert)", async () => {
|
|
137
132
|
const first = await stack.http.writeOk<RequestExportResponse>(REQUEST_EXPORT, {}, aliceUser);
|
|
138
133
|
// Worker-Simulation: status auf done flippen (direct-update OK in Test)
|
|
139
|
-
await
|
|
134
|
+
await updateRows(
|
|
140
135
|
stack.db,
|
|
141
136
|
exportJobsTable,
|
|
142
137
|
{ status: EXPORT_JOB_STATUS.Done },
|
|
@@ -251,7 +246,7 @@ describe("export-status :: User-Polling", () => {
|
|
|
251
246
|
const T = getTemporal();
|
|
252
247
|
// 1. Job done in der Vergangenheit
|
|
253
248
|
const oldJobId = "11111111-1111-4111-8111-111111111111";
|
|
254
|
-
await
|
|
249
|
+
await seedRow(stack.db, exportJobsTable, {
|
|
255
250
|
id: oldJobId,
|
|
256
251
|
tenantId: tenantA,
|
|
257
252
|
userId: aliceUser.id,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
14
14
|
import { randomBytes } from "node:crypto";
|
|
15
|
-
import { selectMany
|
|
15
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
16
16
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
17
17
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
18
18
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
@@ -24,7 +24,11 @@ import {
|
|
|
24
24
|
unsafeCreateEntityTable,
|
|
25
25
|
unsafePushTables,
|
|
26
26
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
createLateBoundHolder,
|
|
29
|
+
resetTestTables,
|
|
30
|
+
updateRows,
|
|
31
|
+
} from "@cosmicdrift/kumiko-framework/testing";
|
|
28
32
|
import { AuthErrors, AuthHandlers } from "../../auth-email-password/constants";
|
|
29
33
|
import { createAuthEmailPasswordFeature } from "../../auth-email-password/feature";
|
|
30
34
|
import { hashPassword } from "../../auth-email-password/password-hashing";
|
|
@@ -117,7 +121,7 @@ async function seedAliceWithMembership(
|
|
|
117
121
|
TestUsers.systemAdmin,
|
|
118
122
|
);
|
|
119
123
|
if (status !== USER_STATUS.Active) {
|
|
120
|
-
await
|
|
124
|
+
await updateRows(stack.db, userTable, { status }, { id: created.id });
|
|
121
125
|
}
|
|
122
126
|
await seedTenantMembership(stack.db, {
|
|
123
127
|
userId: created.id,
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
// jeden Export auf "failed" setzte).
|
|
11
11
|
|
|
12
12
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
-
import { asRawClient,
|
|
14
|
-
import { SYSTEM_USER_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
13
|
+
import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
14
|
+
import { type JobContext, SYSTEM_USER_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
15
15
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
16
16
|
import {
|
|
17
17
|
setupTestStack,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
unsafeCreateEntityTable,
|
|
20
20
|
unsafePushTables,
|
|
21
21
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
22
|
+
import { seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
22
23
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
23
24
|
import {
|
|
24
25
|
createComplianceProfilesFeature,
|
|
@@ -104,7 +105,7 @@ beforeEach(async () => {
|
|
|
104
105
|
await raw.unsafe("DELETE FROM read_export_jobs");
|
|
105
106
|
await raw.unsafe("DELETE FROM read_users");
|
|
106
107
|
await raw.unsafe("DELETE FROM read_tenant_memberships");
|
|
107
|
-
await
|
|
108
|
+
await seedRow(stack.db, userTable, {
|
|
108
109
|
id: USER_ID,
|
|
109
110
|
tenantId: TENANT,
|
|
110
111
|
email: "export-cron@example.test",
|
|
@@ -146,7 +147,10 @@ describe("run-export-jobs cron-context", () => {
|
|
|
146
147
|
now: getTemporal().Now.instant(),
|
|
147
148
|
};
|
|
148
149
|
// Vor dem Fix wirft der Wrapper hier "ctx.config is missing".
|
|
149
|
-
|
|
150
|
+
// jobCtx is a deliberately minimal prod-context replica (missing
|
|
151
|
+
// systemUser/log/triggeredBy) — double-cast at this test boundary since
|
|
152
|
+
// the handler under test never touches those fields.
|
|
153
|
+
await job?.handler({}, jobCtx as unknown as JobContext);
|
|
150
154
|
|
|
151
155
|
const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
|
|
152
156
|
status: string;
|
|
@@ -17,12 +17,7 @@ import { spawn } from "node:child_process";
|
|
|
17
17
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
18
18
|
import { tmpdir } from "node:os";
|
|
19
19
|
import { join } from "node:path";
|
|
20
|
-
import {
|
|
21
|
-
asRawClient,
|
|
22
|
-
insertOne,
|
|
23
|
-
selectMany,
|
|
24
|
-
updateMany,
|
|
25
|
-
} from "@cosmicdrift/kumiko-framework/bun-db";
|
|
20
|
+
import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
26
21
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
27
22
|
import {
|
|
28
23
|
createInMemoryFileProvider,
|
|
@@ -35,7 +30,7 @@ import {
|
|
|
35
30
|
testTenantId,
|
|
36
31
|
unsafeCreateEntityTable,
|
|
37
32
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
38
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
33
|
+
import { resetTestTables, seedRow, updateRows } from "@cosmicdrift/kumiko-framework/testing";
|
|
39
34
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
40
35
|
import {
|
|
41
36
|
createComplianceProfilesFeature,
|
|
@@ -112,7 +107,7 @@ beforeEach(async () => {
|
|
|
112
107
|
|
|
113
108
|
// Atom 5: aliceUser-Row mit email seeden — Worker-Notification-Callback
|
|
114
109
|
// schaut email via lookupUserEmail an.
|
|
115
|
-
await
|
|
110
|
+
await seedRow(stack.db, userTable, {
|
|
116
111
|
id: String(aliceUser.id),
|
|
117
112
|
tenantId: tenantA,
|
|
118
113
|
email: "alice@example.com",
|
|
@@ -222,7 +217,7 @@ describe("runExportJobs :: stale-detection", () => {
|
|
|
222
217
|
const jobId = await seedPendingJob();
|
|
223
218
|
const T = getTemporal();
|
|
224
219
|
const twoHoursAgo = T.Instant.fromEpochMilliseconds(Date.now() - 2 * 60 * 60 * 1000);
|
|
225
|
-
await
|
|
220
|
+
await updateRows(
|
|
226
221
|
stack.db,
|
|
227
222
|
exportJobsTable,
|
|
228
223
|
{ status: EXPORT_JOB_STATUS.Running, startedAt: twoHoursAgo },
|
|
@@ -250,7 +245,7 @@ describe("runExportJobs :: stale-detection", () => {
|
|
|
250
245
|
const jobId = await seedPendingJob();
|
|
251
246
|
const T = getTemporal();
|
|
252
247
|
const justNow = T.Instant.fromEpochMilliseconds(Date.now() - 60 * 1000); // 1min ago
|
|
253
|
-
await
|
|
248
|
+
await updateRows(
|
|
254
249
|
stack.db,
|
|
255
250
|
exportJobsTable,
|
|
256
251
|
{ status: EXPORT_JOB_STATUS.Running, startedAt: justNow },
|
|
@@ -285,7 +280,7 @@ describe("runExportJobs :: stale-detection", () => {
|
|
|
285
280
|
// gesetzt + ZIP geschrieben. Worker dann gecrashed (kein done-flip).
|
|
286
281
|
const provider = await buildProvider(tenantA);
|
|
287
282
|
await provider.write(storageKey, new Uint8Array([1, 2, 3]));
|
|
288
|
-
await
|
|
283
|
+
await updateRows(
|
|
289
284
|
stack.db,
|
|
290
285
|
exportJobsTable,
|
|
291
286
|
{
|
|
@@ -327,7 +322,7 @@ describe("runExportJobs :: storage-cleanup", () => {
|
|
|
327
322
|
const provider = await buildProvider(tenantA);
|
|
328
323
|
await provider.write(storageKey, new Uint8Array([1, 2, 3]));
|
|
329
324
|
|
|
330
|
-
await
|
|
325
|
+
await updateRows(
|
|
331
326
|
stack.db,
|
|
332
327
|
exportJobsTable,
|
|
333
328
|
{
|
|
@@ -366,7 +361,7 @@ describe("runExportJobs :: storage-cleanup", () => {
|
|
|
366
361
|
const provider = await buildProvider(tenantA);
|
|
367
362
|
await provider.write(storageKey, new Uint8Array([4, 5, 6]));
|
|
368
363
|
|
|
369
|
-
await
|
|
364
|
+
await updateRows(
|
|
370
365
|
stack.db,
|
|
371
366
|
exportJobsTable,
|
|
372
367
|
{
|
|
@@ -470,7 +465,7 @@ describe("runExportJobs :: stale-detection profile-driven cutoff", () => {
|
|
|
470
465
|
const jobId = await seedPendingJob();
|
|
471
466
|
const T = getTemporal();
|
|
472
467
|
const fortyFiveMinAgo = T.Instant.fromEpochMilliseconds(Date.now() - 45 * 60 * 1000);
|
|
473
|
-
await
|
|
468
|
+
await updateRows(
|
|
474
469
|
stack.db,
|
|
475
470
|
exportJobsTable,
|
|
476
471
|
{ status: EXPORT_JOB_STATUS.Running, startedAt: fortyFiveMinAgo },
|
|
@@ -661,7 +656,7 @@ describe("runExportJobs :: Atom 4a download-tokens", () => {
|
|
|
661
656
|
// (simuliert orphan-state nach Worker-crash).
|
|
662
657
|
const provider = await buildProvider(tenantA);
|
|
663
658
|
await provider.write(storageKey, new Uint8Array([99, 99, 99]));
|
|
664
|
-
await
|
|
659
|
+
await updateRows(
|
|
665
660
|
stack.db,
|
|
666
661
|
exportJobsTable,
|
|
667
662
|
{
|
|
@@ -1046,7 +1041,7 @@ describe("runExportJobs :: Atom 5 notification-callbacks", () => {
|
|
|
1046
1041
|
// User-Row mit email=null seeden (override). Worker logged warn,
|
|
1047
1042
|
// Callback wird NICHT gerufen, Worker-Run bleibt successful.
|
|
1048
1043
|
await resetTestTables(stack.db, [userTable]);
|
|
1049
|
-
await
|
|
1044
|
+
await seedRow(stack.db, userTable, {
|
|
1050
1045
|
id: String(aliceUser.id),
|
|
1051
1046
|
tenantId: tenantA,
|
|
1052
1047
|
email: "" as string, // empty string — lookupUserEmail returnt null
|
|
@@ -1092,7 +1087,7 @@ describe("runExportJobs :: Atom 5 notification-callbacks", () => {
|
|
|
1092
1087
|
tenantId: tenantA,
|
|
1093
1088
|
roles: ["Member"],
|
|
1094
1089
|
});
|
|
1095
|
-
await
|
|
1090
|
+
await seedRow(stack.db, userTable, {
|
|
1096
1091
|
id: String(bobUser.id),
|
|
1097
1092
|
tenantId: tenantA,
|
|
1098
1093
|
email: "bob@example.com",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
// der end-to-end-Default-Pfad (delete).
|
|
17
17
|
|
|
18
18
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
19
|
-
import { asRawClient
|
|
19
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
20
|
+
import type { JobContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
20
21
|
import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
|
|
21
22
|
import {
|
|
22
23
|
setupTestStack,
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
unsafeCreateEntityTable,
|
|
25
26
|
unsafePushTables,
|
|
26
27
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
27
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
28
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
28
29
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
29
30
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
30
31
|
import { createDataRetentionFeature, tenantRetentionOverrideEntity } from "../../data-retention";
|
|
@@ -123,7 +124,7 @@ async function seedUser(
|
|
|
123
124
|
displayName?: string;
|
|
124
125
|
} = {},
|
|
125
126
|
): Promise<void> {
|
|
126
|
-
await
|
|
127
|
+
await seedRow(stack.db, userTable, {
|
|
127
128
|
id,
|
|
128
129
|
tenantId: TENANT_SYSTEM,
|
|
129
130
|
email: overrides.email ?? `user-${id}@example.com`,
|
|
@@ -289,7 +290,10 @@ describe("run-forget-cleanup :: registered cron (autonomous Art.17)", () => {
|
|
|
289
290
|
await seedMembership(CRON_USER, TENANT_A);
|
|
290
291
|
|
|
291
292
|
const jobCtx = { db: stack.db, registry: stack.registry };
|
|
292
|
-
|
|
293
|
+
// jobCtx is a deliberately minimal prod-context replica (missing
|
|
294
|
+
// systemUser/log/triggeredBy) — double-cast at this test boundary since
|
|
295
|
+
// the handler under test never touches those fields.
|
|
296
|
+
await job?.handler({}, jobCtx as unknown as JobContext);
|
|
293
297
|
|
|
294
298
|
const row = await fetchUser(CRON_USER);
|
|
295
299
|
expect(row?.status).toBe(USER_STATUS.Deleted);
|
|
@@ -616,7 +620,7 @@ describe("runForgetCleanup :: sendDeletionExecutedEmail callback (Atom 5b)", ()
|
|
|
616
620
|
// einem vorigen Run schon anonymisiert hat aber status haengen blieb,
|
|
617
621
|
// oder durch external Migration). Skip schuetzt vor crashing-callback
|
|
618
622
|
// mit invaliden Args.
|
|
619
|
-
await
|
|
623
|
+
await seedRow(stack.db, userTable, {
|
|
620
624
|
id: ALICE_ID,
|
|
621
625
|
tenantId: TENANT_SYSTEM,
|
|
622
626
|
email: "",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// ueber Pseudo-Tenant.
|
|
16
16
|
|
|
17
17
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
18
|
-
import { asRawClient
|
|
18
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
19
19
|
import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
|
|
20
20
|
import {
|
|
21
21
|
setupTestStack,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
unsafeCreateEntityTable,
|
|
24
24
|
unsafePushTables,
|
|
25
25
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
26
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
26
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
27
27
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
28
28
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
29
29
|
import { createDataRetentionFeature } from "../../data-retention";
|
|
@@ -99,7 +99,7 @@ async function seedUser(
|
|
|
99
99
|
id: string,
|
|
100
100
|
overrides: { email?: string; displayName?: string; roles?: string } = {},
|
|
101
101
|
): Promise<void> {
|
|
102
|
-
await
|
|
102
|
+
await seedRow(stack.db, userTable, {
|
|
103
103
|
id,
|
|
104
104
|
tenantId: TENANT_SYSTEM,
|
|
105
105
|
email: overrides.email ?? `user-${id}@example.com`,
|
|
@@ -7,5 +7,5 @@ import { downloadAttemptEntity } from "../schema/download-attempt";
|
|
|
7
7
|
export const downloadAttemptListQuery = defineEntityListHandler(
|
|
8
8
|
"download-attempt",
|
|
9
9
|
downloadAttemptEntity,
|
|
10
|
-
{ access: { roles: access.systemAdmin } },
|
|
10
|
+
{ access: { roles: access.systemAdmin }, crossTenant: true },
|
|
11
11
|
);
|
|
@@ -63,13 +63,11 @@ export const downloadByJobQuery = defineQueryHandler({
|
|
|
63
63
|
const userId = query.user.id;
|
|
64
64
|
const jobId = query.payload.jobId;
|
|
65
65
|
const tenantId = query.user.tenantId;
|
|
66
|
-
// IP aus dem request-scoped Kontext (von requestIdMiddleware
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// laesst sie null (best-effort, via requestId in den Server-Logs
|
|
70
|
-
// cross-referenzierbar).
|
|
66
|
+
// IP + UA aus dem request-scoped Kontext (von requestIdMiddleware
|
|
67
|
+
// befuellt) — server-trusted, anders als ein vom Client mitgeschickter
|
|
68
|
+
// Wert (603/2).
|
|
71
69
|
const auditIp = requestContext.get()?.ip ?? null;
|
|
72
|
-
const auditUa
|
|
70
|
+
const auditUa = requestContext.get()?.userAgent ?? null;
|
|
73
71
|
|
|
74
72
|
// Step 1-2: job-lookup + cross-user-isolation
|
|
75
73
|
// ctx.db.raw weil tenant-agnostisch — Alice in Tenant B sucht den
|
|
@@ -4,4 +4,5 @@ import { exportJobEntity } from "../schema/export-job";
|
|
|
4
4
|
// Detail fetch backing the read-only export-job inspector screen.
|
|
5
5
|
export const exportJobDetailQuery = defineEntityDetailHandler("export-job", exportJobEntity, {
|
|
6
6
|
access: { roles: access.systemAdmin },
|
|
7
|
+
crossTenant: true,
|
|
7
8
|
});
|
|
@@ -5,4 +5,5 @@ import { exportJobEntity } from "../schema/export-job";
|
|
|
5
5
|
// rows are created by the user's request-export flow, never through this handler.
|
|
6
6
|
export const exportJobListQuery = defineEntityListHandler("export-job", exportJobEntity, {
|
|
7
7
|
access: { roles: access.systemAdmin },
|
|
8
|
+
crossTenant: true,
|
|
8
9
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
4
|
-
import { createSystemUser,
|
|
4
|
+
import { createSystemUser, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
5
|
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
6
6
|
import { USER_STATUS, userEntity, userTable } from "../../user";
|
|
7
7
|
|
|
@@ -21,16 +21,6 @@ import { USER_STATUS, userEntity, userTable } from "../../user";
|
|
|
21
21
|
// kann abweichen).
|
|
22
22
|
const userExecutor = createEventStoreExecutor(userTable, userEntity, { entityName: "user" });
|
|
23
23
|
|
|
24
|
-
// Stream-Tenant = die framework-injizierte tenant_id der read_users-Row. Der
|
|
25
|
-
// Reducer setzt sie aus `user.created`.tenantId, ein Rebuild rekonstruiert sie
|
|
26
|
-
// daraus — sie IST also der Stream-Key des Aggregats. Die Row direkt zu lesen
|
|
27
|
-
// (statt das created-Event zu joinen) deckt auch direkt-geseedete Rows ab.
|
|
28
|
-
async function streamTenantOf(conn: DbRunner, userId: string): Promise<TenantId | null> {
|
|
29
|
-
const row = await fetchOne<{ tenantId?: string }>(conn, userTable, { id: userId });
|
|
30
|
-
// @cast-boundary db-row — tenant_id ist eine TenantId-shaped uuid-Spalte.
|
|
31
|
-
return row?.tenantId ? (row.tenantId as TenantId) : null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
24
|
// `conn` ist ctx.db.raw (regulaere Handler) ODER die offene tx (forget-cleanup
|
|
35
25
|
// Sub-Tx) — so bleibt der Event-Append atomar mit dem umgebenden Write.
|
|
36
26
|
export async function updateUserLifecycle(
|
|
@@ -38,20 +28,14 @@ export async function updateUserLifecycle(
|
|
|
38
28
|
userId: string,
|
|
39
29
|
changes: Record<string, unknown>,
|
|
40
30
|
): Promise<void> {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Rescope BEIDE Achsen auf den Stream-Tenant: der db-Arg treibt loadById +
|
|
49
|
-
// den Stream-Read, der user-Arg die Event-tenantId + Ownership. Nur beide
|
|
50
|
-
// zusammen halten created + updated auf einem Stream.
|
|
51
|
-
const tenantDb = createTenantDb(conn, streamTenantId, "tenant");
|
|
31
|
+
// user ist systemStream (#497): der Executor-Choke-Point addressiert den
|
|
32
|
+
// Stream immer auf SYSTEM_TENANT_ID — ein Rescope auf die row-tenant_id
|
|
33
|
+
// waere wirkungslos. "system"-Mode, damit loadById auch Legacy-Rows findet,
|
|
34
|
+
// deren tenant_id noch vor dem #762-Backfill-Rebuild steht.
|
|
35
|
+
const tenantDb = createTenantDb(conn, SYSTEM_TENANT_ID, "system");
|
|
52
36
|
const result = await userExecutor.update(
|
|
53
37
|
{ id: userId, changes },
|
|
54
|
-
createSystemUser(
|
|
38
|
+
createSystemUser(SYSTEM_TENANT_ID),
|
|
55
39
|
tenantDb,
|
|
56
40
|
{ skipOptimisticLock: true },
|
|
57
41
|
);
|
|
@@ -410,7 +410,14 @@ async function resolveEffectiveTenantModel(
|
|
|
410
410
|
appTenantModel: TenantUserModel,
|
|
411
411
|
): Promise<TenantUserModel> {
|
|
412
412
|
if (appTenantModel !== "single-user") return "multi-user";
|
|
413
|
-
|
|
413
|
+
// Only "exactly one member vs. more than one" matters here — a LIMIT 2
|
|
414
|
+
// read answers that without pulling every historical membership row.
|
|
415
|
+
const members = await selectMany<{ userId: string }>(
|
|
416
|
+
db,
|
|
417
|
+
tenantMembershipsTable,
|
|
418
|
+
{ tenantId },
|
|
419
|
+
{ limit: 2 },
|
|
420
|
+
);
|
|
414
421
|
return members.length === 1 ? "single-user" : "multi-user";
|
|
415
422
|
}
|
|
416
423
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Provider-Wrapper lokal (Dependency-Richtung renderer-web → bundled-features
|
|
6
6
|
// verbietet test-utils-Import).
|
|
7
7
|
|
|
8
|
-
import { describe, expect, test } from "bun:test";
|
|
8
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
9
9
|
import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
|
|
10
10
|
import {
|
|
11
11
|
createStaticLocaleResolver,
|
|
@@ -27,6 +27,12 @@ const stubDispatcher = {
|
|
|
27
27
|
|
|
28
28
|
const ROUTES = { requestPath: "/account/delete", confirmPath: "/account/delete/confirm" };
|
|
29
29
|
|
|
30
|
+
// renderGate mutates the shared happy-dom document's history (570/1) — reset
|
|
31
|
+
// so a later, unrelated test file doesn't inherit this suite's last path.
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
window.history.replaceState({}, "", "/");
|
|
34
|
+
});
|
|
35
|
+
|
|
30
36
|
function renderGate(path: string, gate: ReactNode): ReturnType<typeof within> {
|
|
31
37
|
window.history.replaceState({}, "", path);
|
|
32
38
|
const { container } = render(
|
|
@@ -74,11 +74,6 @@ export const defaultTranslations: TranslationsByLocale = {
|
|
|
74
74
|
"userDataRights.privacyCenter.deletion.dialogTitle": "Konto wirklich löschen?",
|
|
75
75
|
"userDataRights.privacyCenter.deletion.dialogDescription":
|
|
76
76
|
"Mit dem Bestätigen startet die Lösch-Frist. Du kannst die Löschung bis zu ihrem Ablauf wieder abbrechen.",
|
|
77
|
-
|
|
78
|
-
"userDataRights.privacyCenter.audit.title": "Aktivitätsprotokoll (Art. 15)",
|
|
79
|
-
"userDataRights.privacyCenter.audit.intro":
|
|
80
|
-
"Die letzten Ereignisse zu deinem Konto über alle Tenants hinweg.",
|
|
81
|
-
"userDataRights.privacyCenter.audit.empty": "Noch keine Ereignisse.",
|
|
82
77
|
},
|
|
83
78
|
en: {
|
|
84
79
|
"userDataRights.deletion.request.title": "Request account deletion",
|
|
@@ -146,10 +141,5 @@ export const defaultTranslations: TranslationsByLocale = {
|
|
|
146
141
|
"userDataRights.privacyCenter.deletion.dialogTitle": "Delete your account?",
|
|
147
142
|
"userDataRights.privacyCenter.deletion.dialogDescription":
|
|
148
143
|
"Confirming starts the deletion grace period. You can cancel the deletion until it ends.",
|
|
149
|
-
|
|
150
|
-
"userDataRights.privacyCenter.audit.title": "Activity log (Art. 15)",
|
|
151
|
-
"userDataRights.privacyCenter.audit.intro":
|
|
152
|
-
"The most recent events on your account across all tenants.",
|
|
153
|
-
"userDataRights.privacyCenter.audit.empty": "No events yet.",
|
|
154
144
|
},
|
|
155
145
|
};
|
package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// fileRef-Forget beruehrt Tenant B's Files nicht)
|
|
12
12
|
|
|
13
13
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
14
|
-
import { asRawClient
|
|
14
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
15
15
|
import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
|
|
16
16
|
import {
|
|
17
17
|
setupTestStack,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
unsafeCreateEntityTable,
|
|
20
20
|
unsafePushTables,
|
|
21
21
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
22
|
+
import { seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
22
23
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
23
24
|
import { createDataRetentionFeature } from "../../data-retention";
|
|
24
25
|
import { createFilesFeature } from "../../files";
|
|
@@ -80,7 +81,7 @@ async function seedUser(id: string, overrides: Record<string, unknown> = {}): Pr
|
|
|
80
81
|
// Schema hat tenant_id-Spalte automatisch (Framework-Default).
|
|
81
82
|
// Pragmatisch: SYSTEM_TENANT_ID fuer User-Rows in Tests.
|
|
82
83
|
const SYSTEM_TENANT = "00000000-0000-4000-8000-000000000001";
|
|
83
|
-
await
|
|
84
|
+
await seedRow(stack.db, userTable, {
|
|
84
85
|
id,
|
|
85
86
|
tenantId: SYSTEM_TENANT,
|
|
86
87
|
email: `user-${id}@example.com`,
|