@cosmicdrift/kumiko-bundled-features 0.105.2 → 0.109.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 +205 -0
- package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -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 +21 -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 +43 -0
- package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
- package/src/personal-access-tokens/web/i18n.ts +69 -0
- package/src/personal-access-tokens/web/index.ts +7 -0
- package/src/personal-access-tokens/web/pat-tokens-screen.tsx +251 -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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// export-job-list/detail :: SystemAdmin cross-tenant visibility.
|
|
2
|
+
//
|
|
3
|
+
// Regression: the changeset describes these as "a platform-operator
|
|
4
|
+
// inspector over the GDPR read-models" (i.e. cross-tenant), but the handlers
|
|
5
|
+
// registered no crossTenant/systemScope override — a SystemAdmin acting in
|
|
6
|
+
// tenant A saw only tenant A's export jobs, never tenant B's.
|
|
7
|
+
|
|
8
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
+
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
10
|
+
import {
|
|
11
|
+
createTestUser,
|
|
12
|
+
setupTestStack,
|
|
13
|
+
type TestStack,
|
|
14
|
+
testTenantId,
|
|
15
|
+
unsafeCreateEntityTable,
|
|
16
|
+
} from "@cosmicdrift/kumiko-framework/stack";
|
|
17
|
+
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
18
|
+
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
19
|
+
import { createDataRetentionFeature } from "../../data-retention";
|
|
20
|
+
import { createSessionsFeature } from "../../sessions";
|
|
21
|
+
import { createUserFeature } from "../../user";
|
|
22
|
+
import { createUserDataRightsFeature } from "../feature";
|
|
23
|
+
import { exportJobEntity, exportJobsTable } from "../schema/export-job";
|
|
24
|
+
|
|
25
|
+
const REQUEST_EXPORT = "user-data-rights:write:request-export";
|
|
26
|
+
const EXPORT_JOB_LIST = "user-data-rights:query:export-job:list";
|
|
27
|
+
const EXPORT_JOB_DETAIL = "user-data-rights:query:export-job:detail";
|
|
28
|
+
|
|
29
|
+
let stack: TestStack;
|
|
30
|
+
|
|
31
|
+
const tenantA = testTenantId(1);
|
|
32
|
+
const tenantB = testTenantId(2);
|
|
33
|
+
const aliceInA = createTestUser({ id: 42, tenantId: tenantA, roles: ["Member"] });
|
|
34
|
+
const bobInB = createTestUser({ id: 43, tenantId: tenantB, roles: ["Member"] });
|
|
35
|
+
const sysadminInA = createTestUser({ id: 1, tenantId: tenantA, roles: ["SystemAdmin"] });
|
|
36
|
+
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
stack = await setupTestStack({
|
|
39
|
+
features: [
|
|
40
|
+
createUserFeature(),
|
|
41
|
+
createDataRetentionFeature(),
|
|
42
|
+
createComplianceProfilesFeature(),
|
|
43
|
+
createSessionsFeature(),
|
|
44
|
+
createUserDataRightsFeature(),
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
await unsafeCreateEntityTable(stack.db, exportJobEntity);
|
|
48
|
+
await createEventsTable(stack.db);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterAll(async () => {
|
|
52
|
+
await stack.cleanup();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
beforeEach(async () => {
|
|
56
|
+
await resetTestTables(stack.db, [exportJobsTable, eventsTable]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("export-job list/detail :: SystemAdmin sees every tenant's jobs", () => {
|
|
60
|
+
test("list includes jobs from a tenant the SystemAdmin isn't acting in", async () => {
|
|
61
|
+
const aJob = await stack.http.writeOk<{ jobId: string }>(REQUEST_EXPORT, {}, aliceInA);
|
|
62
|
+
const bJob = await stack.http.writeOk<{ jobId: string }>(REQUEST_EXPORT, {}, bobInB);
|
|
63
|
+
|
|
64
|
+
const result = await stack.http.queryOk<{ rows: Array<{ id: string }> }>(
|
|
65
|
+
EXPORT_JOB_LIST,
|
|
66
|
+
{},
|
|
67
|
+
sysadminInA,
|
|
68
|
+
);
|
|
69
|
+
const ids = result.rows.map((r) => r.id);
|
|
70
|
+
expect(ids).toContain(aJob.jobId);
|
|
71
|
+
expect(ids).toContain(bJob.jobId);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("detail resolves a job that belongs to a different tenant than the caller", async () => {
|
|
75
|
+
const bJob = await stack.http.writeOk<{ jobId: string }>(REQUEST_EXPORT, {}, bobInB);
|
|
76
|
+
|
|
77
|
+
const detail = await stack.http.queryOk<{ id: string } | null>(
|
|
78
|
+
EXPORT_JOB_DETAIL,
|
|
79
|
+
{ id: bJob.jobId },
|
|
80
|
+
sysadminInA,
|
|
81
|
+
);
|
|
82
|
+
expect(detail?.id).toBe(bJob.jobId);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// die Policy entscheidet pro Tenant.
|
|
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
20
|
import {
|
|
21
21
|
createEventStoreExecutor,
|
|
22
22
|
createTenantDb,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
unsafeCreateEntityTable,
|
|
31
31
|
unsafePushTables,
|
|
32
32
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
33
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
33
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
34
34
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
35
35
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
36
36
|
import { createDataRetentionFeature, tenantRetentionOverrideEntity } from "../../data-retention";
|
|
@@ -117,7 +117,7 @@ beforeEach(async () => {
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
async function seedForgetUser(id: string): Promise<void> {
|
|
120
|
-
await
|
|
120
|
+
await seedRow(db, userTable, {
|
|
121
121
|
id,
|
|
122
122
|
tenantId: TENANT_SYSTEM,
|
|
123
123
|
email: `user-${id}@example.com`,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// (getExtensionUsages + direct call) and is structurally blind to ordering.
|
|
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 { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
|
|
20
20
|
import {
|
|
21
21
|
createEntity,
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
type TestStack,
|
|
33
33
|
unsafeCreateEntityTable,
|
|
34
34
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
35
|
+
import { seedRow as seedProjectionRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
35
36
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
36
37
|
import { fieldDefinitionEntity } from "../../custom-fields/entity";
|
|
37
38
|
import { createCustomFieldsFeature } from "../../custom-fields/feature";
|
|
@@ -181,7 +182,7 @@ async function defineSensitiveField(entityName: string, fieldKey: string, sensit
|
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
async function seedAnonymizeOverride(entityName: string) {
|
|
184
|
-
await
|
|
185
|
+
await seedProjectionRow(stack.db, tenantRetentionOverrideTable, {
|
|
185
186
|
entityName,
|
|
186
187
|
config: JSON.stringify({ keepFor: "0d", strategy: "anonymize" }),
|
|
187
188
|
reason: "test: force anonymize strategy",
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
// DDL and seed logic in one place so a file_refs/user-schema change updates
|
|
5
5
|
// both at once instead of drifting.
|
|
6
6
|
|
|
7
|
-
import { asRawClient
|
|
7
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
8
8
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
9
9
|
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
10
10
|
import type { FileStorageProvider } from "@cosmicdrift/kumiko-framework/files";
|
|
11
|
+
import { seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
11
12
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
12
13
|
import { USER_STATUS, userTable } from "../../user";
|
|
13
14
|
|
|
@@ -66,7 +67,7 @@ export interface ForgetSeeders {
|
|
|
66
67
|
export function createForgetSeeders(db: DbConnection, writer: FileWriter): ForgetSeeders {
|
|
67
68
|
return {
|
|
68
69
|
async seedForgetUser(id) {
|
|
69
|
-
await
|
|
70
|
+
await seedRow(db, userTable, {
|
|
70
71
|
id,
|
|
71
72
|
tenantId: TENANT_SYSTEM,
|
|
72
73
|
email: `user-${id}@example.com`,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// #624: "App mountet mail-foundation+transport → GDPR-Mails ohne Callback-Code".
|
|
11
11
|
|
|
12
12
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
-
import { asRawClient
|
|
13
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
14
14
|
import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
|
|
15
15
|
import {
|
|
16
16
|
setupTestStack,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
unsafeCreateEntityTable,
|
|
19
19
|
unsafePushTables,
|
|
20
20
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
21
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
21
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
22
22
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
23
23
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
24
24
|
import { configValuesTable, createConfigFeature, createConfigResolver } from "../../config";
|
|
@@ -99,7 +99,7 @@ beforeEach(async () => {
|
|
|
99
99
|
|
|
100
100
|
describe("C6 default mail bridge :: forget cron sends deletion-executed without app callback", () => {
|
|
101
101
|
test("registered cron + configResolver(provider=inmemory) → user deleted + mail in inbox", async () => {
|
|
102
|
-
await
|
|
102
|
+
await seedRow(stack.db, userTable, {
|
|
103
103
|
id: USER_ID,
|
|
104
104
|
tenantId: TENANT_SYSTEM,
|
|
105
105
|
email: ORIGINAL_EMAIL,
|
package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
15
15
|
import { randomBytes } from "node:crypto";
|
|
16
|
-
import { selectMany
|
|
16
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
17
17
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
18
18
|
import {
|
|
19
19
|
createRegistry,
|
|
@@ -38,7 +38,11 @@ import {
|
|
|
38
38
|
unsafeCreateEntityTable,
|
|
39
39
|
unsafePushTables,
|
|
40
40
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
createLateBoundHolder,
|
|
43
|
+
resetTestTables,
|
|
44
|
+
updateRows,
|
|
45
|
+
} from "@cosmicdrift/kumiko-framework/testing";
|
|
42
46
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
43
47
|
import { AuthHandlers } from "../../auth-email-password/constants";
|
|
44
48
|
import { createAuthEmailPasswordFeature } from "../../auth-email-password/feature";
|
|
@@ -218,7 +222,7 @@ describe("#494 :: read_users-Rebuild bewahrt Lifecycle-State", () => {
|
|
|
218
222
|
);
|
|
219
223
|
|
|
220
224
|
// Prae-Fix-Zustand simulieren: roher Write OHNE Event.
|
|
221
|
-
await
|
|
225
|
+
await updateRows(stack.db, userTable, { status: USER_STATUS.Restricted }, { id: created.id });
|
|
222
226
|
|
|
223
227
|
// Ohne Backfill replayt der Rebuild nur user.created -> Status weggewischt.
|
|
224
228
|
await rebuildProjection(USER_PROJECTION, { db: stack.db, registry });
|
|
@@ -229,7 +233,7 @@ describe("#494 :: read_users-Rebuild bewahrt Lifecycle-State", () => {
|
|
|
229
233
|
|
|
230
234
|
// Bestand wieder in den divergenten Live-State bringen (der Rebuild hat ihn
|
|
231
235
|
// auf Active gesetzt) und den Reconcile laufen lassen.
|
|
232
|
-
await
|
|
236
|
+
await updateRows(stack.db, userTable, { status: USER_STATUS.Restricted }, { id: created.id });
|
|
233
237
|
const { backfilled, failed } = await backfillUserLifecycleEvents(stack.db);
|
|
234
238
|
expect(backfilled).toBeGreaterThanOrEqual(1);
|
|
235
239
|
expect(failed).toEqual([]);
|
|
@@ -254,14 +258,14 @@ describe("#494 :: read_users-Rebuild bewahrt Lifecycle-State", () => {
|
|
|
254
258
|
{ email: "healthy.rebuild@example.com", passwordHash: hash, displayName: "Healthy" },
|
|
255
259
|
TestUsers.systemAdmin,
|
|
256
260
|
);
|
|
257
|
-
await
|
|
261
|
+
await updateRows(stack.db, userTable, { status: USER_STATUS.Restricted }, { id: goodRow.id });
|
|
258
262
|
|
|
259
263
|
const badRow = await stack.http.writeOk<{ id: string }>(
|
|
260
264
|
UserHandlers.create,
|
|
261
265
|
{ email: "corrupt.rebuild@example.com", passwordHash: hash, displayName: "Corrupt" },
|
|
262
266
|
TestUsers.systemAdmin,
|
|
263
267
|
);
|
|
264
|
-
await
|
|
268
|
+
await updateRows(stack.db, userTable, { status: USER_STATUS.Restricted }, { id: badRow.id });
|
|
265
269
|
// userEntity is systemStream:true (schema/user.ts) — its event stream
|
|
266
270
|
// lives on SYSTEM_TENANT_ID regardless of the creating tenant. Archiving
|
|
267
271
|
// under any other tenant would be a silent no-op against assertStreamWritable.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// hier; der Frist-Ablauf-Cleanup folgt mit S2.U5b.
|
|
11
11
|
|
|
12
12
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
-
import {
|
|
13
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
14
14
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
15
15
|
import {
|
|
16
16
|
createTestUser,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
testUserId,
|
|
21
21
|
unsafeCreateEntityTable,
|
|
22
22
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
23
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
23
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
24
24
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
25
25
|
import {
|
|
26
26
|
createComplianceProfilesFeature,
|
|
@@ -92,7 +92,7 @@ async function seedAlice(
|
|
|
92
92
|
gracePeriodEnd: Instant | null;
|
|
93
93
|
}> = {},
|
|
94
94
|
): Promise<void> {
|
|
95
|
-
await
|
|
95
|
+
await seedRow(stack.db, userTable, {
|
|
96
96
|
id: aliceUser.id,
|
|
97
97
|
tenantId: tenantA,
|
|
98
98
|
email: "alice@example.com",
|
|
@@ -345,7 +345,7 @@ describe("Cross-User-Isolation", () => {
|
|
|
345
345
|
tenantId: tenantA,
|
|
346
346
|
roles: ["Member"],
|
|
347
347
|
});
|
|
348
|
-
await
|
|
348
|
+
await seedRow(stack.db, userTable, {
|
|
349
349
|
id: testUserId(43),
|
|
350
350
|
tenantId: tenantA,
|
|
351
351
|
email: "bob@example.com",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Properties — dieser Test verifiziert sie end-to-end.
|
|
9
9
|
|
|
10
10
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
11
|
-
import {
|
|
11
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
12
12
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
13
13
|
import {
|
|
14
14
|
createTestUser,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
testTenantId,
|
|
18
18
|
unsafeCreateEntityTable,
|
|
19
19
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
20
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
20
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
21
21
|
import {
|
|
22
22
|
createComplianceProfilesFeature,
|
|
23
23
|
tenantComplianceProfileEntity,
|
|
@@ -85,7 +85,7 @@ beforeEach(async () => {
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
async function seedAlice(email: string = "alice@example.com"): Promise<void> {
|
|
88
|
-
await
|
|
88
|
+
await seedRow(stack.db, userTable, {
|
|
89
89
|
id: aliceUser.id,
|
|
90
90
|
tenantId: tenantA,
|
|
91
91
|
email,
|
|
@@ -153,7 +153,7 @@ describe("request-deletion :: sendDeletionRequestedEmail callback", () => {
|
|
|
153
153
|
});
|
|
154
154
|
|
|
155
155
|
test("422 user_not_in_active_state → callback NICHT gefeuert", async () => {
|
|
156
|
-
await
|
|
156
|
+
await seedRow(stack.db, userTable, {
|
|
157
157
|
id: aliceUser.id,
|
|
158
158
|
tenantId: tenantA,
|
|
159
159
|
email: "alice@example.com",
|
|
@@ -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
|
});
|