@cosmicdrift/kumiko-bundled-features 0.165.2 → 0.165.4
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 +7 -7
- package/src/__tests__/access-invalidation-consumer.integration.test.ts +1 -1
- package/src/__tests__/access-invalidation-stream.integration.test.ts +80 -66
- package/src/admin-shell/changes.json +8 -0
- package/src/agent-tools/changes.json +1 -0
- package/src/audit/__tests__/audit.integration.test.ts +5 -2
- package/src/audit/changes.json +8 -0
- package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +3 -0
- package/src/auth-email-password/changes.json +26 -0
- package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +3 -11
- package/src/auth-email-password/web/__tests__/fetch-mock.ts +14 -0
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +3 -11
- package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +3 -11
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +3 -11
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +3 -11
- package/src/auth-foundation/changes.json +8 -0
- package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +6 -6
- package/src/auth-mfa/changes.json +38 -0
- package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +3 -11
- package/src/auth-mfa-user-data/changes.json +1 -0
- package/src/billing-foundation/changes.json +1 -0
- package/src/cap-counter/changes.json +1 -0
- package/src/channel-email/changes.json +1 -0
- package/src/channel-in-app/changes.json +1 -0
- package/src/channel-push/changes.json +1 -0
- package/src/compliance-profiles/changes.json +1 -0
- package/src/config/changes.json +9 -0
- package/src/crypto-shredding/changes.json +14 -0
- package/src/crypto-shredding/handlers/forget-subject.write.ts +13 -0
- package/src/custom-fields/changes.json +8 -0
- package/src/data-retention/changes.json +8 -0
- package/src/delivery/changes.json +8 -0
- package/src/document-ingest-foundation/changes.json +1 -0
- package/src/feature-toggles/changes.json +26 -0
- package/src/file-foundation/changes.json +1 -0
- package/src/file-provider-inmemory/changes.json +1 -0
- package/src/file-provider-s3/changes.json +1 -0
- package/src/file-provider-s3-env/changes.json +1 -0
- package/src/files/changes.json +1 -0
- package/src/files-provider-s3/changes.json +1 -0
- package/src/folders/changes.json +1 -0
- package/src/folders-user-data/changes.json +1 -0
- package/src/foundation-shared/changes.json +1 -0
- package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +3 -1
- package/src/inbound-mail-foundation/changes.json +8 -0
- package/src/inbound-mail-foundation/watch-supervisor.ts +3 -1
- package/src/inbound-provider-imap/changes.json +1 -0
- package/src/inbound-provider-inmemory/changes.json +1 -0
- package/src/jobs/__tests__/jobs-catalog.integration.test.ts +159 -0
- package/src/jobs/__tests__/jobs-screens.boot.test.ts +1 -0
- package/src/jobs/__tests__/jobs-security.integration.test.ts +4 -2
- package/src/jobs/changes.json +8 -0
- package/src/jobs/constants.ts +2 -0
- package/src/jobs/feature.ts +3 -1
- package/src/jobs/handlers/catalog.query.ts +43 -0
- package/src/jobs/handlers/trigger.write.ts +26 -2
- package/src/jobs/i18n.ts +33 -0
- package/src/jobs/web/__tests__/job-runs-screen.test.tsx +223 -0
- package/src/jobs/web/job-runs-screen.tsx +218 -84
- package/src/ledger/changes.json +1 -0
- package/src/legal-pages/changes.json +1 -0
- package/src/mail-foundation/changes.json +1 -0
- package/src/mail-transport-inmemory/changes.json +1 -0
- package/src/mail-transport-smtp/changes.json +1 -0
- package/src/managed-pages/changes.json +20 -0
- package/src/page-render/changes.json +14 -0
- package/src/personal-access-tokens/changes.json +8 -0
- package/src/presets/changes.json +1 -0
- package/src/presets/dsgvo-self-service.ts +1 -1
- package/src/rate-limiting/changes.json +1 -0
- package/src/readiness/changes.json +1 -0
- package/src/renderer-foundation/changes.json +1 -0
- package/src/renderer-simple/changes.json +1 -0
- package/src/secrets/changes.json +1 -0
- package/src/seo/changes.json +20 -0
- package/src/sessions/changes.json +8 -0
- package/src/sessions/handlers/revoke-all-for-user.write.ts +9 -1
- package/src/shared/changes.json +1 -0
- package/src/step-dispatcher/changes.json +1 -0
- package/src/subscription-mollie/changes.json +1 -0
- package/src/subscription-stripe/changes.json +1 -0
- package/src/tags/changes.json +1 -0
- package/src/template-resolver/changes.json +1 -0
- package/src/tenant/changes.json +27 -0
- package/src/tenant-lifecycle/changes.json +1 -0
- package/src/tenant-settings/changes.json +1 -0
- package/src/text-content/changes.json +14 -0
- package/src/tier-engine/changes.json +1 -0
- package/src/user/changes.json +8 -0
- package/src/user/schema/user.ts +8 -5
- package/src/user-data-rights/__tests__/audit-log.integration.test.ts +7 -3
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +0 -1
- package/src/user-data-rights/__tests__/download.integration.test.ts +0 -1
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +0 -1
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +0 -1
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +0 -1
- package/src/user-data-rights/__tests__/request-export.integration.test.ts +0 -1
- package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +0 -2
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +0 -1
- package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +0 -1
- package/src/user-data-rights/changes.json +27 -0
- package/src/user-data-rights/feature.ts +7 -6
- package/src/user-data-rights/handlers/lift-restriction.write.ts +4 -20
- package/src/user-data-rights/handlers/restrict-account.write.ts +4 -20
- package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +1 -0
- package/src/user-data-rights/lib/deny-if-target-outside-admin-tenant.ts +32 -0
- package/src/user-data-rights/run-forget-cleanup.ts +23 -0
- package/src/user-data-rights-defaults/changes.json +8 -0
- package/src/user-profile/changes.json +8 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.147.3",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "`managed-pages`' `GET {basePath}/:slug` render route and `seo`'s `sitemap.xml`/`llms.txt`…",
|
|
6
|
+
"detail": "`managed-pages`' `GET {basePath}/:slug` render route and `seo`'s `sitemap.xml`/`llms.txt` routes now use the `systemQuery` in-process dispatch (introduced for `legal-pages` in the `resolverTrust` fix) instead of an internal `app.fetch` self-request with a forged `X-Tenant` header. That self-fetch carried no `Host` header (Host is only implied by the request URL, never stored in `Headers`), so a host-based `anonymousAccess.tenantResolver` reading only the header resolved `null` for the inner request. Under `resolverTrust: \"authoritative\"` there is no client-tenant fallback for a `null` resolution, so the route failed with `tenant_required` (503 \"page unavailable\" / empty sitemap entries) even for a legitimate, correctly-routed visitor. `systemQuery` forces the already host-resolved tenant in-process — no header round-trip, nothing that can go missing. Consumers running `managed-pages` or `seo` with `resolverTrust: \"authoritative\"` should upgrade — the render/discovery routes were not reliably servable under that mode before this fix. - @cosmicdrift/kumiko-framework@0.147.3 - @cosmicdrift/kumiko-dispatcher-live@0.147.3 - @cosmicdrift/kumiko-headless@0.147.3 - @cosmicdrift/kumiko-renderer@0.147.3 - @cosmicdrift/kumiko-renderer-web@0.147.3"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.146.2",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "seo: fix legal-pages/managed-pages URLs downgrading to http:// in sitemap.xml/llms.txt be…",
|
|
12
|
+
"detail": "seo: fix legal-pages/managed-pages URLs downgrading to http:// in sitemap.xml/llms.txt behind a TLS-terminating reverse proxy (#979 follow-up) — `requestHost()` trusted the raw request URL's scheme, which reflects the proxy's internal (plain HTTP) hop, not what the client actually used. Now prefers `x-forwarded-proto` when present, falling back to the raw URL scheme only for plain local dev without a proxy in front. Found by checking the sitemap.xml of the two production apps (cashcolt.kumiko.rocks, publicstatus.eu) that mount `seo` — both showed `http://` legal-pages entries next to correctly `https://` app-supplied entries (those app callbacks hardcode `https://` themselves, masking the bug). - @cosmicdrift/kumiko-framework@0.146.2 - @cosmicdrift/kumiko-dispatcher-live@0.146.2 - @cosmicdrift/kumiko-headless@0.146.2 - @cosmicdrift/kumiko-renderer@0.146.2 - @cosmicdrift/kumiko-renderer-web@0.146.2"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.146.0",
|
|
16
|
+
"type": "improvement",
|
|
17
|
+
"title": "seo: neues bundled-feature für SEO/AEO/GEO-Site-Discovery (#979) — `createSeoFeature` mou…",
|
|
18
|
+
"detail": "seo: neues bundled-feature für SEO/AEO/GEO-Site-Discovery (#979) — `createSeoFeature` mountet GET /sitemap.xml, /llms.txt und optional /robots.txt (merged aus app-eigenem Callback + legal-pages + managed-pages), plus die additive OG/JSON-LD-Erweiterung für `wrapInLayout` und die pure `organizationSchema`/`webPageSchema`/`faqPageSchema`-Builder für `ApexHead.schemaJson`. `managed-pages` bekommt dafür die neue anonyme `by-tenant-published`-Query. War Teil von #979, hatte aber kein Changeset — daher hier nachgereicht, damit die Version tatsächlich published wird."
|
|
19
|
+
}
|
|
20
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.165.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Revoke handlers emit session-revoked event",
|
|
6
|
+
"detail": "Session revocation now appends a session-revoked event to the aggregate stream — other features can react to it (e.g. cache invalidation)."
|
|
7
|
+
}
|
|
8
|
+
]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requestContext } from "@cosmicdrift/kumiko-framework/api";
|
|
1
2
|
import { updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
3
|
import { access, defineWriteHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
4
|
import { append } from "@cosmicdrift/kumiko-framework/event-store";
|
|
@@ -58,14 +59,21 @@ export const revokeAllForUserWrite = defineWriteHandler({
|
|
|
58
59
|
userId: event.payload.userId,
|
|
59
60
|
sessionIds: updated.map((row) => row.id),
|
|
60
61
|
});
|
|
62
|
+
const reqCtx = requestContext.get();
|
|
61
63
|
await append(ctx.db.raw, {
|
|
62
64
|
aggregateId: generateId(),
|
|
63
65
|
aggregateType: SESSION_REVOKED_AGGREGATE_TYPE,
|
|
64
66
|
tenantId: SYSTEM_TENANT_ID,
|
|
65
67
|
expectedVersion: 0,
|
|
66
68
|
type: SESSION_REVOKED_EVENT_QN,
|
|
69
|
+
eventVersion: ctx.registry.getEvent(SESSION_REVOKED_EVENT_QN)?.version ?? 1,
|
|
67
70
|
payload,
|
|
68
|
-
metadata: {
|
|
71
|
+
metadata: {
|
|
72
|
+
userId: event.user.id,
|
|
73
|
+
...(reqCtx?.requestId ? { requestId: reqCtx.requestId } : {}),
|
|
74
|
+
...(reqCtx?.correlationId ? { correlationId: reqCtx.correlationId } : {}),
|
|
75
|
+
...(reqCtx?.causationId ? { causationId: reqCtx.causationId } : {}),
|
|
76
|
+
},
|
|
69
77
|
});
|
|
70
78
|
}
|
|
71
79
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.165.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "resolveRawDb: removed redundant null check",
|
|
6
|
+
"detail": "CodeQL flagged db !== null after if (!db) — truthiness already excludes null. Internal cleanup, no API break."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.165.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Seed hook: explicit SessionUser typing",
|
|
12
|
+
"detail": "Fixed tenant seeding type annotation — prevents implicit any propagation in seed handlers."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.165.0",
|
|
16
|
+
"type": "fix",
|
|
17
|
+
"title": "Seed afterCommit + real tenant rescope",
|
|
18
|
+
"detail": "ScheduleAfterCommit now available on HandlerContext — fireEntityPostSave queues after TX commit instead of mid-transaction. DB/notify/hasFeature/_tenantId are correctly rescoped when targeting an entity's tenant.",
|
|
19
|
+
"migration": "Only affects custom seed handlers using fireEntityPostSave — now afterCommit-compatible."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"version": "0.163.0",
|
|
23
|
+
"type": "improvement",
|
|
24
|
+
"title": "tenant: seedTenant fires entity postSave hooks (#1463).",
|
|
25
|
+
"detail": "tenant: seedTenant fires entity postSave hooks (#1463). `seedTenant` writes through the raw event-store executor, which never fired postSave hooks — self-signup (`provisionSignupAccount` → `signup-confirm`) silently skipped feature-registered entity hooks like tier-engine's auto-default-tier and app-level auto-default-compliance, leaving new tenants without a tier/compliance row. `seedTenant` and `provisionSignupAccount` take a new optional `hooks: { registry, context }` param; passing it fires the same entity-scoped postSave hooks the dispatcher would, without the full dispatch roundtrip. Existing call-sites are unaffected — the param is optional and append-only."
|
|
26
|
+
}
|
|
27
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.165.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Legal pages seed: SYSTEM_USER instead of tenant user",
|
|
6
|
+
"detail": "Seed now runs as SYSTEM_USER and skips identical re-seeds — prevents unnecessary DB writes."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.130.2",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Content-Tree + Config-Nav Sysadmin-Shell polish: - text-content: Leaf-Knoten tragen jetzt…",
|
|
12
|
+
"detail": "Content-Tree + Config-Nav Sysadmin-Shell polish: - text-content: Leaf-Knoten tragen jetzt ein `file`-Icon statt eines Dots; der Editor läuft auf der Page-Shell (`Form`-Primitive mit Card statt des entfernten `FormPanelShell`). - Sidebar-Nav bekommt ein Suchfeld, das den Baum live filtert (Treffer + ihre Ancestors bleiben, zugeklappte Ordner öffnen für die Suche). - Ordner-Knoten zeigen `folder-open` wenn ausgeklappt. - NAV_ICONS um `server`, `mail`, `lock`, `hash`, `download`, `folder-open` ergänzt — SMTP-/Config-Nav-Kinder (z.B. „Email-Versand\") rendern damit ein Icon statt blank. - Verschachtelte Provider-Ordner (Content-Tree) rendern ihre Kinder in einem `<ul>` (valides HTML + Einrück-Stufe pro Tiefe) statt `<li>`-in-`<li>`. - Platform-Overview: `user:query:user:list` in der Allowlist (behebt den Overview-Crash). - @cosmicdrift/kumiko-renderer-web@0.130.2 - @cosmicdrift/kumiko-renderer@0.130.2 - @cosmicdrift/kumiko-framework@0.130.2 - @cosmicdrift/kumiko-dispatcher-live@0.130.2 - @cosmicdrift/kumiko-headless@0.130.2"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.165.3",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "user.displayName is searchable via derived Meili (fw#1610).",
|
|
6
|
+
"detail": "displayName is userOwned PII and now searchable through the decrypt-into-Meili path; forget purges the index entry with the subject key."
|
|
7
|
+
}
|
|
8
|
+
]
|
package/src/user/schema/user.ts
CHANGED
|
@@ -79,11 +79,14 @@ export const userEntity = createEntity({
|
|
|
79
79
|
access: { read: access.privileged, write: access.privileged },
|
|
80
80
|
}),
|
|
81
81
|
|
|
82
|
-
// Profile — user-editable.
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
// Profile — user-editable. Real name in most apps → PII. Searchable via
|
|
83
|
+
// the derived Meili index; events/projection stay ciphertext (#1610).
|
|
84
|
+
displayName: createTextField({
|
|
85
|
+
required: true,
|
|
86
|
+
maxLength: 100,
|
|
87
|
+
pii: true,
|
|
88
|
+
searchable: true,
|
|
89
|
+
}),
|
|
87
90
|
locale: createTextField({ maxLength: 10, default: "de" }),
|
|
88
91
|
|
|
89
92
|
// Which tenant should this user land in on next login. Set by the login
|
|
@@ -44,7 +44,6 @@ beforeAll(async () => {
|
|
|
44
44
|
createComplianceProfilesFeature(),
|
|
45
45
|
authFoundationFeature,
|
|
46
46
|
createSessionsFeature(),
|
|
47
|
-
|
|
48
47
|
createUserDataRightsFeature(),
|
|
49
48
|
],
|
|
50
49
|
});
|
|
@@ -128,8 +127,13 @@ describe("my-audit-log", () => {
|
|
|
128
127
|
expect(bobLog.rows.length).toBe(1);
|
|
129
128
|
// Payload-pinning beweist die Cross-User-Filterung: alice sieht nur
|
|
130
129
|
// ihre Payload, bob nur seine.
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
const aliceRow = aliceLog.rows[0];
|
|
131
|
+
const bobRow = bobLog.rows[0];
|
|
132
|
+
expect(aliceRow).toBeDefined();
|
|
133
|
+
expect(bobRow).toBeDefined();
|
|
134
|
+
if (!aliceRow || !bobRow) throw new Error("expected audit rows");
|
|
135
|
+
expect((aliceRow.payload as { foo: string }).foo).toBe("alice");
|
|
136
|
+
expect((bobRow.payload as { foo: string }).foo).toBe("bob");
|
|
133
137
|
});
|
|
134
138
|
|
|
135
139
|
test("Account-weite Sicht: User sieht events aus anderen Tenants (DSGVO Art. 15)", async () => {
|
|
@@ -62,7 +62,6 @@ beforeAll(async () => {
|
|
|
62
62
|
createComplianceProfilesFeature(),
|
|
63
63
|
authFoundationFeature,
|
|
64
64
|
createSessionsFeature(),
|
|
65
|
-
|
|
66
65
|
createUserDataRightsFeature(),
|
|
67
66
|
],
|
|
68
67
|
});
|
|
@@ -969,7 +968,6 @@ describe("runExportJobs :: Atom 3c file-binaries", () => {
|
|
|
969
968
|
createComplianceProfilesFeature(),
|
|
970
969
|
authFoundationFeature,
|
|
971
970
|
createSessionsFeature(),
|
|
972
|
-
|
|
973
971
|
createUserDataRightsFeature(),
|
|
974
972
|
testFileExporter,
|
|
975
973
|
],
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.165.3",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Forget cleanup purges derived Meili docs for searchable subject-PII (fw#1610).",
|
|
6
|
+
"detail": "run-forget-cleanup (and the forget cron path) call purgeSearchDocumentsForSubject after crypto-shred so searchable userOwned fields do not linger in Meili as plaintext after Art. 17."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.165.0",
|
|
10
|
+
"type": "breaking",
|
|
11
|
+
"title": "Export download: removed ?token= query param",
|
|
12
|
+
"detail": "GET /user-export/by-token no longer accepts the token as a query parameter. Only POST body (read from URL fragment) is supported now. Old email links with ?token= in the URL will stop working.",
|
|
13
|
+
"migration": "Replace any existing export links that use ?token= in the URL with POST-based links. The token is now only read from the URL fragment (#token), which browsers never send to the server."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"version": "0.122.0",
|
|
17
|
+
"type": "fix",
|
|
18
|
+
"title": "user-data-rights: export-download-token re-runs rotate the token in place on the same agg…",
|
|
19
|
+
"detail": "user-data-rights: export-download-token re-runs rotate the token in place on the same aggregate (update) instead of creating a second aggregate for the same jobId. A second `created` event without a `deleted` in between made every projection rebuild collide on the `one_per_job` unique index (fw#832). Operator recovery after a lost plain token is now just \"flip the job back to pending\" — never delete the token row read-side. - @cosmicdrift/kumiko-framework@0.122.0 - @cosmicdrift/kumiko-headless@0.122.0 - @cosmicdrift/kumiko-renderer@0.122.0 - @cosmicdrift/kumiko-dispatcher-live@0.122.0 - @cosmicdrift/kumiko-renderer-web@0.122.0"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"version": "0.119.0",
|
|
23
|
+
"type": "improvement",
|
|
24
|
+
"title": "Crypto-shredding phase D: forget wire.",
|
|
25
|
+
"detail": "Crypto-shredding phase D: forget wire. New `crypto-shredding` bundled feature with the `forget-subject` operator command (DPO/SystemAdmin) — erases a user/tenant subject key and appends a `subject-forgotten` audit event. `user-data-rights` forget-cleanup now erases the user's subject key inside the per-user sub-tx (crash-safe, before the status flip). Fixes `list()` returning ciphertext for camelCase encrypted/pii fields and caching plaintext rows."
|
|
26
|
+
}
|
|
27
|
+
]
|
|
@@ -309,13 +309,13 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
309
309
|
// afterward (a redirect doesn't undo an already-logged request) — the
|
|
310
310
|
// only leak-proof fix is to never accept the token that way.
|
|
311
311
|
//
|
|
312
|
-
// Path
|
|
313
|
-
//
|
|
312
|
+
// Path sits outside /api/* — r.httpRoute must not claim the /api namespace
|
|
313
|
+
// (reserved for write/query/batch/auth/sse-dispatcher).
|
|
314
314
|
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
315
|
+
// The session path (logged-in download) needs no httpRoute wrapper: the
|
|
316
|
+
// client calls download-by-job via the Dispatcher (with X-CSRF-Token) and
|
|
317
|
+
// navigates to the returned signed URL — see postWithDownload in
|
|
318
|
+
// privacy-center-screen.
|
|
319
319
|
r.httpRoute({
|
|
320
320
|
method: "GET",
|
|
321
321
|
path: "/user-export/by-token",
|
|
@@ -524,6 +524,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
524
524
|
registry: forgetRegistry,
|
|
525
525
|
now: T.Now.instant(),
|
|
526
526
|
tenantModel,
|
|
527
|
+
...(ctx.searchAdapter !== undefined && { searchAdapter: ctx.searchAdapter }),
|
|
527
528
|
// Same per-tenant provider resolution as the export cron — forget
|
|
528
529
|
// deletes binaries from the store upload + export use.
|
|
529
530
|
buildStorageProvider: makeTenantStorageProviderResolver({
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import { access, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
-
import {
|
|
4
|
-
AccessDeniedError,
|
|
5
|
-
UnprocessableError,
|
|
6
|
-
writeFailure,
|
|
7
|
-
} from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
8
4
|
import { z } from "zod";
|
|
9
|
-
import { tenantMembershipsTable } from "../../tenant";
|
|
10
5
|
import { USER_STATUS, userTable } from "../../user";
|
|
11
|
-
import {
|
|
6
|
+
import { denyIfTargetOutsideAdminTenant } from "../lib/deny-if-target-outside-admin-tenant";
|
|
12
7
|
import { updateUserLifecycle } from "../lib/update-user-lifecycle";
|
|
13
8
|
|
|
14
9
|
// POST /api/user/lift-restriction (S2.U6) — DSGVO Art. 18 Reverse.
|
|
@@ -41,19 +36,8 @@ export const liftRestrictionWrite = defineWriteHandler({
|
|
|
41
36
|
handler: async (event, ctx) => {
|
|
42
37
|
const targetUserId = event.payload.userId;
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
userId: targetUserId,
|
|
47
|
-
tenantId: event.user.tenantId,
|
|
48
|
-
});
|
|
49
|
-
if (!membership) {
|
|
50
|
-
return writeFailure(
|
|
51
|
-
new AccessDeniedError({
|
|
52
|
-
details: { reason: "target_user_not_in_admin_tenant" },
|
|
53
|
-
}),
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
39
|
+
const outside = await denyIfTargetOutsideAdminTenant(ctx.db.raw, event.user, targetUserId);
|
|
40
|
+
if (outside) return outside;
|
|
57
41
|
|
|
58
42
|
const userRow = await fetchOne<{ status: string }>(ctx.db.raw, userTable, {
|
|
59
43
|
id: targetUserId,
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
writeFailure,
|
|
7
7
|
} from "@cosmicdrift/kumiko-framework/errors";
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { tenantMembershipsTable } from "../../tenant";
|
|
10
9
|
import { USER_STATUS, userTable } from "../../user";
|
|
11
|
-
import {
|
|
10
|
+
import { denyIfTargetOutsideAdminTenant } from "../lib/deny-if-target-outside-admin-tenant";
|
|
11
|
+
import { isAdminActor } from "../lib/is-admin-actor";
|
|
12
12
|
import { updateUserLifecycle } from "../lib/update-user-lifecycle";
|
|
13
13
|
|
|
14
14
|
// POST /api/user/restrict (S2.U6) — DSGVO Art. 18 Account-Freeze.
|
|
@@ -56,24 +56,8 @@ export const restrictAccountWrite = defineWriteHandler({
|
|
|
56
56
|
}),
|
|
57
57
|
);
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// membership in the caller's own tenant. Without this, a TenantAdmin
|
|
62
|
-
// from tenant A could restrict — and force-revoke the sessions of —
|
|
63
|
-
// a user who has never been a member of tenant A.
|
|
64
|
-
if (!isSystemAdminActor(event.user)) {
|
|
65
|
-
const membership = await fetchOne(ctx.db.raw, tenantMembershipsTable, {
|
|
66
|
-
userId: targetUserId,
|
|
67
|
-
tenantId: event.user.tenantId,
|
|
68
|
-
});
|
|
69
|
-
if (!membership) {
|
|
70
|
-
return writeFailure(
|
|
71
|
-
new AccessDeniedError({
|
|
72
|
-
details: { reason: "target_user_not_in_admin_tenant" },
|
|
73
|
-
}),
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
59
|
+
const outside = await denyIfTargetOutsideAdminTenant(ctx.db.raw, event.user, targetUserId);
|
|
60
|
+
if (outside) return outside;
|
|
77
61
|
}
|
|
78
62
|
|
|
79
63
|
// ctx.db.raw weil User-Entity tenant-agnostisch ist (analog
|
|
@@ -57,6 +57,7 @@ export function createRunForgetCleanupHandler(opts: RunForgetCleanupOptions = {}
|
|
|
57
57
|
registry: ctx.registry,
|
|
58
58
|
now: T.Now.instant(),
|
|
59
59
|
tenantModel,
|
|
60
|
+
...(ctx.searchAdapter !== undefined && { searchAdapter: ctx.searchAdapter }),
|
|
60
61
|
buildStorageProvider: makeTenantStorageProviderResolver({
|
|
61
62
|
registry: ctx.registry,
|
|
62
63
|
configResolver: ctx.configResolver,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
+
import type { SessionUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import {
|
|
4
|
+
AccessDeniedError,
|
|
5
|
+
type WriteFailure,
|
|
6
|
+
writeFailure,
|
|
7
|
+
} from "@cosmicdrift/kumiko-framework/errors";
|
|
8
|
+
import { tenantMembershipsTable } from "../../tenant";
|
|
9
|
+
import { isSystemAdminActor } from "./is-admin-actor";
|
|
10
|
+
|
|
11
|
+
export const TARGET_USER_NOT_IN_ADMIN_TENANT = "target_user_not_in_admin_tenant" as const;
|
|
12
|
+
|
|
13
|
+
type DbRaw = Parameters<typeof fetchOne>[0];
|
|
14
|
+
|
|
15
|
+
/** SystemAdmin skips; otherwise the target must have a membership in the actor's tenant. */
|
|
16
|
+
export async function denyIfTargetOutsideAdminTenant(
|
|
17
|
+
db: DbRaw,
|
|
18
|
+
actor: SessionUser,
|
|
19
|
+
targetUserId: string,
|
|
20
|
+
): Promise<WriteFailure | undefined> {
|
|
21
|
+
if (isSystemAdminActor(actor)) return undefined;
|
|
22
|
+
const membership = await fetchOne(db, tenantMembershipsTable, {
|
|
23
|
+
userId: targetUserId,
|
|
24
|
+
tenantId: actor.tenantId,
|
|
25
|
+
});
|
|
26
|
+
if (membership) return undefined;
|
|
27
|
+
return writeFailure(
|
|
28
|
+
new AccessDeniedError({
|
|
29
|
+
details: { reason: TARGET_USER_NOT_IN_ADMIN_TENANT },
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -49,6 +49,10 @@ import {
|
|
|
49
49
|
type UserDataDeleteStrategy,
|
|
50
50
|
type UserDataStorageProvider,
|
|
51
51
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
52
|
+
import {
|
|
53
|
+
purgeSearchDocumentsForSubject,
|
|
54
|
+
type SearchAdapter,
|
|
55
|
+
} from "@cosmicdrift/kumiko-framework/search";
|
|
52
56
|
import type { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
53
57
|
import { resolveRetentionPolicyForTenant } from "../data-retention";
|
|
54
58
|
import { decryptStoredPii } from "../shared";
|
|
@@ -116,6 +120,13 @@ export interface RunForgetCleanupArgs {
|
|
|
116
120
|
*/
|
|
117
121
|
readonly kms?: KmsAdapter;
|
|
118
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Search adapter for purging derived Meili docs of searchable subject-PII
|
|
125
|
+
* (#1610). Same seam as forget-subject. Omitted → no search purge (apps
|
|
126
|
+
* without Meili).
|
|
127
|
+
*/
|
|
128
|
+
readonly searchAdapter?: SearchAdapter;
|
|
129
|
+
|
|
119
130
|
/**
|
|
120
131
|
* App-level tenant-occupancy model (resolved from the `tenantModel` config by
|
|
121
132
|
* the cron/handler). The pipeline refines it PER TENANT with a sole-member
|
|
@@ -235,6 +246,7 @@ export async function runForgetCleanup(
|
|
|
235
246
|
buildStorageProvider,
|
|
236
247
|
appTenantModel,
|
|
237
248
|
kms,
|
|
249
|
+
searchAdapter: args.searchAdapter,
|
|
238
250
|
});
|
|
239
251
|
hookCallsAttempted += userResult.hookCallsAttempted;
|
|
240
252
|
errors.push(...userResult.errors);
|
|
@@ -298,6 +310,7 @@ async function processUser(args: {
|
|
|
298
310
|
buildStorageProvider?: (tenantId: TenantId) => Promise<UserDataStorageProvider | undefined>;
|
|
299
311
|
appTenantModel: TenantUserModel;
|
|
300
312
|
kms?: KmsAdapter;
|
|
313
|
+
searchAdapter?: SearchAdapter;
|
|
301
314
|
}): Promise<ProcessUserResult> {
|
|
302
315
|
const { db, registry, userId, hookEntries, buildStorageProvider, appTenantModel, kms } = args;
|
|
303
316
|
const errors: ForgetCleanupError[] = [];
|
|
@@ -422,6 +435,16 @@ async function processUser(args: {
|
|
|
422
435
|
registry.features,
|
|
423
436
|
subjectIdToKey({ kind: "user", userId }),
|
|
424
437
|
);
|
|
438
|
+
if (args.searchAdapter) {
|
|
439
|
+
const subject = { kind: "user" as const, userId };
|
|
440
|
+
await purgeSearchDocumentsForSubject(
|
|
441
|
+
tx,
|
|
442
|
+
registry.features,
|
|
443
|
+
args.searchAdapter,
|
|
444
|
+
subjectIdToKey(subject),
|
|
445
|
+
subject,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
425
448
|
}
|
|
426
449
|
|
|
427
450
|
// Status-Flip in derselben Sub-Tx. Falls einer der Hooks oben
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.116.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "user-data-rights-defaults now registers EXT_USER_DATA export/delete hooks for six more bu…",
|
|
6
|
+
"detail": "user-data-rights-defaults now registers EXT_USER_DATA export/delete hooks for six more bundled entities: user-session (ip/userAgent), api-token, in-app-message, tenant-invitation, notification-preference and user-scoped config-value. Hooks no-op when the source feature isn't mounted. pii annotations added on the affected schema fields. - @cosmicdrift/kumiko-renderer-web@0.116.0 - @cosmicdrift/kumiko-framework@0.116.0 - @cosmicdrift/kumiko-dispatcher-live@0.116.0 - @cosmicdrift/kumiko-headless@0.116.0 - @cosmicdrift/kumiko-renderer@0.116.0"
|
|
7
|
+
}
|
|
8
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.123.2",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Consistency: user-profile's ProfileScreen adopts the shared `FormScreenShell` (centered `…",
|
|
6
|
+
"detail": "Consistency: user-profile's ProfileScreen adopts the shared `FormScreenShell` (centered `max-w-3xl` like all other settings screens, was left-aligned `max-w-5xl`). DataTable now sits on `bg-card` instead of a transparent surface — on themes with a colored page background (e.g. cream) lists previously didn't match the white cards; now they do. - @cosmicdrift/kumiko-renderer-web@0.123.2 - @cosmicdrift/kumiko-framework@0.123.2 - @cosmicdrift/kumiko-dispatcher-live@0.123.2 - @cosmicdrift/kumiko-headless@0.123.2 - @cosmicdrift/kumiko-renderer@0.123.2"
|
|
7
|
+
}
|
|
8
|
+
]
|