@cosmicdrift/kumiko-samples 0.361.0 → 0.363.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 +1 -1
- package/packages/bundled-features/package.json +17 -6
- package/packages/bundled-features/src/admin-shell/changes.json +6 -0
- package/packages/bundled-features/src/admin-shell/feature.ts +8 -1
- package/packages/bundled-features/src/agent-tools/agent-manifest.ts +3 -4
- package/packages/bundled-features/src/agent-tools/filter-ops.ts +1 -3
- package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -3
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +2 -1
- package/packages/bundled-features/src/auth-email-password/changes.json +14 -0
- package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
- package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
- package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +3 -12
- package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
- package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
- package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
- package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
- package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
- package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
- package/packages/bundled-features/src/billing-foundation/changes.json +7 -0
- package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +38 -44
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +115 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +30 -30
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
- package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -6
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
- package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
- package/packages/bundled-features/src/channel-email/changes.json +19 -0
- package/packages/bundled-features/src/channel-email/email-channel.ts +122 -29
- package/packages/bundled-features/src/channel-email/feature.ts +6 -3
- package/packages/bundled-features/src/channel-email/index.ts +7 -1
- package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
- package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
- package/packages/bundled-features/src/channel-email/types.ts +23 -0
- package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
- package/packages/bundled-features/src/config/handlers/set.write.ts +1 -5
- package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
- package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
- package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
- package/packages/bundled-features/src/data-retention/changes.json +6 -0
- package/packages/bundled-features/src/data-retention/index.ts +1 -0
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
- package/packages/bundled-features/src/delivery/attachment-ref.ts +84 -0
- package/packages/bundled-features/src/delivery/changes.json +48 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +129 -4
- package/packages/bundled-features/src/delivery/constants.ts +2 -0
- package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
- package/packages/bundled-features/src/delivery/feature.ts +1 -1
- package/packages/bundled-features/src/delivery/index.ts +17 -2
- package/packages/bundled-features/src/delivery/jobs.ts +48 -5
- package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
- package/packages/bundled-features/src/delivery/testing.ts +9 -1
- package/packages/bundled-features/src/delivery/types.ts +28 -18
- package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
- package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
- package/packages/bundled-features/src/document-ingest-foundation/aggregate-id.ts +10 -0
- package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +3 -1
- package/packages/bundled-features/src/document-ingest-foundation/feature.ts +5 -3
- package/packages/bundled-features/src/document-ingest-foundation/index.ts +1 -0
- package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
- package/packages/bundled-features/src/file-foundation/index.ts +7 -0
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +86 -0
- package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
- package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
- package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
- package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +40 -25
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
- package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
- package/packages/bundled-features/src/ledger/recurring.ts +1 -3
- package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
- package/packages/bundled-features/src/managed-pages/changes.json +6 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
- package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
- package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
- package/packages/bundled-features/src/page-render/branding.ts +3 -0
- package/packages/bundled-features/src/page-render/markdown.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
- package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
- package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
- package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
- package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
- package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
- package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
- package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
- package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
- package/packages/bundled-features/src/renderer-foundation/types.ts +132 -35
- package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
- package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
- package/packages/bundled-features/src/sessions/feature.ts +2 -4
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
- package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
- package/packages/bundled-features/src/shared/index.ts +6 -0
- package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
- package/packages/bundled-features/src/shared/mail-template-body.ts +25 -0
- package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
- package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
- package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
- package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
- package/packages/bundled-features/src/template-resolver/README.md +63 -7
- package/packages/bundled-features/src/template-resolver/api.ts +83 -8
- package/packages/bundled-features/src/template-resolver/changes.json +118 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +214 -0
- package/packages/bundled-features/src/template-resolver/constants.ts +39 -11
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
- package/packages/bundled-features/src/template-resolver/document-schema.ts +323 -0
- package/packages/bundled-features/src/template-resolver/feature.ts +56 -6
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
- package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +142 -0
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +72 -0
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +83 -0
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +3 -3
- package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
- package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
- package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +108 -0
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +5 -7
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +250 -0
- package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
- package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
- package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +106 -0
- package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
- package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
- package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +184 -0
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +183 -0
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +55 -0
- package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +57 -10
- package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
- package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +15 -76
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +17 -0
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
- package/packages/bundled-features/src/template-resolver/index.ts +100 -0
- package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
- package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
- package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
- package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
- package/packages/bundled-features/src/template-resolver/qualified-names.ts +30 -0
- package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
- package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
- package/packages/bundled-features/src/template-resolver/seeding.ts +147 -17
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
- package/packages/bundled-features/src/template-resolver/table.ts +14 -0
- package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
- package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
- package/packages/bundled-features/src/template-resolver/template-meta.ts +129 -0
- package/packages/bundled-features/src/template-resolver/template-preview.ts +573 -0
- package/packages/bundled-features/src/template-resolver/template-resources.ts +74 -0
- package/packages/bundled-features/src/template-resolver/template-usage.ts +48 -0
- package/packages/bundled-features/src/template-resolver/template-validation.ts +229 -0
- package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +9 -6
- package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +162 -0
- package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +28 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +850 -0
- package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +61 -0
- package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +100 -0
- package/packages/bundled-features/src/template-resolver/web/index.ts +19 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +169 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
- package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
- package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
- package/packages/bundled-features/src/tenant/seeding.ts +41 -7
- package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
- package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
- package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/changes.json +14 -0
- package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
- package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
- package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +5 -1
- package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +13 -1
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
- package/packages/bundled-features/src/user-data-rights/index.ts +1 -0
- package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +6 -12
- package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
- package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +10 -1
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/download-attempt.userdata-hook.ts +58 -0
- package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
- package/packages/bundled-features/src/waitlist/changes.json +7 -0
- package/packages/bundled-features/src/waitlist/feature.ts +17 -0
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
- package/packages/bundled-features/src/waitlist/mail.ts +69 -18
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +1 -2
- package/samples/apps/ui-walkthrough/package.json +2 -0
- package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
- package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
- package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +154 -0
- package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
- package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
- package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +354 -0
- package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +68 -0
- package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
- package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
- package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +1 -1
- package/samples/apps/ui-walkthrough/src/run-config.ts +37 -1
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +123 -22
- package/samples/apps/use-all-bundled/kumiko/migrations/0044_download-attempt-ip-bidx.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/package.json +2 -0
- package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
- package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
- package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
- package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
- package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
- package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
- package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
- package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
- package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
- package/samples/recipes/delivery-notifications/README.md +26 -25
- package/samples/recipes/delivery-notifications/package.json +2 -1
- package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
- package/samples/recipes/seed-migration/README.md +1 -1
- package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
- package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
- package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
- package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
- package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
- package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
defineEntityDetailHandler,
|
|
4
4
|
type HandlerContext,
|
|
5
5
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
6
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
6
7
|
import { userEntity } from "../schema/user.js";
|
|
7
8
|
import { attachTenantLabels, dbForList } from "./list.query.js";
|
|
8
9
|
|
|
@@ -24,12 +25,9 @@ export const detailQuery = {
|
|
|
24
25
|
ctx: HandlerContext,
|
|
25
26
|
) => {
|
|
26
27
|
const result = await baseDetail.handler?.(query, ctx);
|
|
27
|
-
if (result
|
|
28
|
+
if (!isPlainObject(result)) return result;
|
|
28
29
|
if (!ctx.registry.features.has("tenant")) return result;
|
|
29
|
-
const [enriched] = await attachTenantLabels(
|
|
30
|
-
[result as Record<string, unknown>],
|
|
31
|
-
dbForList(ctx),
|
|
32
|
-
);
|
|
30
|
+
const [enriched] = await attachTenantLabels([result], dbForList(ctx));
|
|
33
31
|
return enriched ?? result;
|
|
34
32
|
},
|
|
35
33
|
};
|
|
@@ -8,8 +8,8 @@ export const rolesInputSchema = z.union([
|
|
|
8
8
|
z.string().transform((raw, ctx) => {
|
|
9
9
|
try {
|
|
10
10
|
const parsed: unknown = JSON.parse(raw);
|
|
11
|
-
if (Array.isArray(parsed) && parsed.every((v) => typeof v === "string")) {
|
|
12
|
-
return parsed
|
|
11
|
+
if (Array.isArray(parsed) && parsed.every((v): v is string => typeof v === "string")) {
|
|
12
|
+
return parsed;
|
|
13
13
|
}
|
|
14
14
|
} catch {
|
|
15
15
|
/* fall through */
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.363.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "GDPR default mails are r.notification declarations, render*Email is gone",
|
|
6
|
+
"detail": "export-ready, export-failed, deletion-requested and deletion-executed are declared as trigger-less notifications (user-data-rights:notify:<name>) with variables and localized templates built from the GDPR translations, so a tenant can override the text. The default export-ready, export-failed and deletion-requested mails send through ctx.notify to the user (export mails with immediate so the plain magic-link token never lands in a job payload). deletion-executed renders the seeded template through the mail renderer and sends over the tenant mail transport without a delivery log, because the user is already gone. App-provided send*Email callbacks still win. The defaults now need delivery with channel-email; a boot warning is logged once when mail-foundation and a mail transport are mounted without them. New exports are GdprMailNotificationTypes, gdprMailLocalizedTemplate, gdprMailTemplateLocale, registerGdprMails and formatGdprMailTimestamp.",
|
|
7
|
+
"migration": "renderExportReadyEmail, renderExportFailedEmail, renderDeletionRequestedEmail, renderDeletionExecutedEmail and RenderedEmail are removed from user-data-rights/email-templates; the translations stay. An app that mounts it without includeBundled needs template-resolver, renderer-foundation and @cosmicdrift/kumiko-renderer-mail-html at boot (composeRendererStack() from dev-server mounts all of them), plus a delivery with the channel-email feature; apps without the template tables need a schema migration (`kumiko schema generate`). Delivery preferences that referenced the mails use the new user-data-rights:notify:<name> types."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"version": "0.362.0",
|
|
11
|
+
"type": "fix",
|
|
12
|
+
"title": "download-attempt.ip and userAgent are encrypted personal data instead of plaintext",
|
|
13
|
+
"detail": "download-attempt.ip and userAgent are declared personal: { of: \"attemptedByUserId\", whenAbsent: \"tenant\" }, ip with find \"exact\" and userAgent with find \"none\". With a KMS configured, rows written by the logged-in job path are encrypted under the requesting user's key (a user forget makes IP and user agent unreadable), rows from the anonymous token path under the tenant key. The new ip_bidx column keeps the equality filter of list-download-attempts working across both kinds of rows, and the query now returns the decrypted values (an erased user's values come back as the erased sentinel). user-data-rights-defaults registers an EXT_USER_DATA hook for download-attempt: export lists the user's own attempts, delete purges the user's own attempts via the forget verb (projection row and blind index included, replayed on rebuild); owner-less token-path rows expire with the 90d hardDelete retention.",
|
|
14
|
+
"migration": "Apps that mount user-data-rights run kumiko-schema generate and apply: the migration adds the ip_bidx column and its index to read_download_attempts. Apps that mount user-data-rights without user-data-rights-defaults must register their own EXT_USER_DATA hook for the download-attempt entity, otherwise boot fails the PII hook coverage check. The annotation fingerprint of the boot PII backfill changes, so the first boot after the bump does one full pass: it encrypts existing download-attempt events (tenant key or user key) and rebuilds the projection including ip_bidx (KUMIKO_SKIP_PII_BACKFILL=1 turns it off)."
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"version": "0.354.0",
|
|
4
18
|
"type": "breaking",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
-
import type {
|
|
2
|
+
import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
4
4
|
import { userTable } from "../../../user/index.js";
|
|
5
5
|
|
|
6
6
|
export async function selectUsersDueForForgetCleanup(
|
|
7
|
-
db:
|
|
7
|
+
db: DbRunner,
|
|
8
8
|
status: string,
|
|
9
9
|
gracePeriodEndCutoff: Temporal.Instant | string,
|
|
10
10
|
): Promise<readonly { id: string }[]> {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Translations for the GDPR notification mails (the templates in
|
|
2
|
+
// mail-notifications.ts are built from them). English ships here; other
|
|
3
|
+
// languages register via @cosmicdrift/kumiko-locale-* (localeDe() /
|
|
3
4
|
// localeEs() call registerMailTranslations). Apps that want their own
|
|
4
5
|
// branding pass send*Email callbacks and these defaults do not run.
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
-
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
7
|
+
import { registerMailTranslations } from "@cosmicdrift/kumiko-framework/i18n";
|
|
8
8
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
9
9
|
|
|
10
10
|
export type GdprMailLocale = string;
|
|
@@ -41,151 +41,7 @@ export const GDPR_MAIL_EN: Readonly<Record<string, string>> = {
|
|
|
41
41
|
|
|
42
42
|
registerMailTranslations("en", GDPR_MAIL_EN);
|
|
43
43
|
|
|
44
|
-
export
|
|
45
|
-
readonly subject: string;
|
|
46
|
-
readonly html: string;
|
|
47
|
-
readonly text: string;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export type RenderExportReadyEmailArgs = {
|
|
51
|
-
readonly downloadUrl: string;
|
|
52
|
-
readonly expiresAt: string;
|
|
53
|
-
readonly locale?: GdprMailLocale;
|
|
54
|
-
readonly appName?: string;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export type RenderExportFailedEmailArgs = {
|
|
58
|
-
readonly locale?: GdprMailLocale;
|
|
59
|
-
readonly appName?: string;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
export type RenderDeletionRequestedEmailArgs = {
|
|
63
|
-
readonly gracePeriodEnd: string;
|
|
64
|
-
readonly locale?: GdprMailLocale;
|
|
65
|
-
readonly appName?: string;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export type RenderDeletionExecutedEmailArgs = {
|
|
69
|
-
readonly executedAt: string;
|
|
70
|
-
readonly locale?: GdprMailLocale;
|
|
71
|
-
readonly appName?: string;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function t(locale: string, key: string, params?: Readonly<Record<string, string>>): string {
|
|
75
|
-
return mailT(locale, key, params);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function appNameFor(args: { locale?: GdprMailLocale; appName?: string }): string {
|
|
79
|
-
const locale = args.locale ?? "en";
|
|
80
|
-
return args.appName ?? t(locale, "gdpr.mail.appNameDefault");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function renderExportReadyEmail(args: RenderExportReadyEmailArgs): RenderedEmail {
|
|
84
|
-
const locale = args.locale ?? "en";
|
|
85
|
-
const app = appNameFor(args);
|
|
86
|
-
const subject = t(locale, "gdpr.mail.exportReady.subject", { app });
|
|
87
|
-
const body = `
|
|
88
|
-
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
|
|
89
|
-
<p style="margin: 0 0 24px; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.exportReady.intro", { app }))}</p>
|
|
90
|
-
<p style="margin: 0 0 24px;">${renderButton({ url: args.downloadUrl, label: t(locale, "gdpr.mail.exportReady.button") })}</p>
|
|
91
|
-
<p style="margin: 0 0 8px; font-size: 13px; color: #555;">${escapeHtml(t(locale, "gdpr.mail.exportReady.expiry", { when: formatTimestamp(args.expiresAt) }))}</p>
|
|
92
|
-
${renderFallbackUrl({ url: args.downloadUrl, label: t(locale, "gdpr.mail.fallbackUrl") })}`;
|
|
93
|
-
const text = plainTextBody([
|
|
94
|
-
t(locale, "gdpr.mail.greeting"),
|
|
95
|
-
t(locale, "gdpr.mail.exportReady.intro", { app }),
|
|
96
|
-
`${t(locale, "gdpr.mail.exportReady.button")}: ${args.downloadUrl}`,
|
|
97
|
-
t(locale, "gdpr.mail.exportReady.expiry", { when: formatTimestamp(args.expiresAt) }),
|
|
98
|
-
]);
|
|
99
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function renderExportFailedEmail(args: RenderExportFailedEmailArgs): RenderedEmail {
|
|
103
|
-
const locale = args.locale ?? "en";
|
|
104
|
-
const app = appNameFor(args);
|
|
105
|
-
const subject = t(locale, "gdpr.mail.exportFailed.subject", { app });
|
|
106
|
-
const body = `
|
|
107
|
-
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
|
|
108
|
-
<p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.exportFailed.intro", { app }))}</p>`;
|
|
109
|
-
const text = plainTextBody([
|
|
110
|
-
t(locale, "gdpr.mail.greeting"),
|
|
111
|
-
t(locale, "gdpr.mail.exportFailed.intro", { app }),
|
|
112
|
-
]);
|
|
113
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function renderDeletionRequestedEmail(
|
|
117
|
-
args: RenderDeletionRequestedEmailArgs,
|
|
118
|
-
): RenderedEmail {
|
|
119
|
-
const locale = args.locale ?? "en";
|
|
120
|
-
const app = appNameFor(args);
|
|
121
|
-
const when = formatTimestamp(args.gracePeriodEnd);
|
|
122
|
-
const subject = t(locale, "gdpr.mail.deletionRequested.subject", { app });
|
|
123
|
-
const body = `
|
|
124
|
-
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
|
|
125
|
-
<p style="margin: 0 0 16px; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.deletionRequested.intro", { app, when }))}</p>
|
|
126
|
-
<p style="margin: 0; font-size: 13px; color: #555;">${escapeHtml(t(locale, "gdpr.mail.deletionRequested.cancel"))}</p>`;
|
|
127
|
-
const text = plainTextBody([
|
|
128
|
-
t(locale, "gdpr.mail.greeting"),
|
|
129
|
-
t(locale, "gdpr.mail.deletionRequested.intro", { app, when }),
|
|
130
|
-
t(locale, "gdpr.mail.deletionRequested.cancel"),
|
|
131
|
-
]);
|
|
132
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function renderDeletionExecutedEmail(args: RenderDeletionExecutedEmailArgs): RenderedEmail {
|
|
136
|
-
const locale = args.locale ?? "en";
|
|
137
|
-
const app = appNameFor(args);
|
|
138
|
-
const when = formatTimestamp(args.executedAt);
|
|
139
|
-
const subject = t(locale, "gdpr.mail.deletionExecuted.subject", { app });
|
|
140
|
-
const body = `
|
|
141
|
-
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
|
|
142
|
-
<p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.deletionExecuted.intro", { app, when }))}</p>`;
|
|
143
|
-
const text = plainTextBody([
|
|
144
|
-
t(locale, "gdpr.mail.greeting"),
|
|
145
|
-
t(locale, "gdpr.mail.deletionExecuted.intro", { app, when }),
|
|
146
|
-
]);
|
|
147
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function plainTextBody(paragraphs: readonly string[]): string {
|
|
151
|
-
return paragraphs.join("\n\n");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function wrapCell(bodyHtml: string): string {
|
|
155
|
-
return `<tr><td>${bodyHtml}</td></tr>`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function renderShell(args: { title: string; bodyHtml: string; locale: GdprMailLocale }): string {
|
|
159
|
-
return `<!DOCTYPE html>
|
|
160
|
-
<html lang="${escapeHtmlAttr(args.locale)}">
|
|
161
|
-
<head>
|
|
162
|
-
<meta charset="utf-8" />
|
|
163
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
164
|
-
<title>${escapeHtml(args.title)}</title>
|
|
165
|
-
</head>
|
|
166
|
-
<body style="margin: 0; padding: 0; background: #f7f7f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #1a1a1a;">
|
|
167
|
-
<table width="100%" cellpadding="0" cellspacing="0" style="padding: 24px 0;">
|
|
168
|
-
<tr>
|
|
169
|
-
<td align="center">
|
|
170
|
-
<table width="560" cellpadding="0" cellspacing="0" style="max-width: 560px; background: #ffffff; border-radius: 8px; padding: 32px;">
|
|
171
|
-
${args.bodyHtml}
|
|
172
|
-
</table>
|
|
173
|
-
</td>
|
|
174
|
-
</tr>
|
|
175
|
-
</table>
|
|
176
|
-
</body>
|
|
177
|
-
</html>`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function renderButton(args: { url: string; label: string }): string {
|
|
181
|
-
return `<a href="${escapeHtmlAttr(args.url)}" style="display: inline-block; background: #1a1a1a; color: #ffffff; padding: 12px 24px; border-radius: 6px; text-decoration: none; font-weight: 500;">${escapeHtml(args.label)}</a>`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function renderFallbackUrl(args: { url: string; label: string }): string {
|
|
185
|
-
return `<p style="margin: 24px 0 0; font-size: 12px; color: #666;">${escapeHtml(args.label)}<br /><a href="${escapeHtmlAttr(args.url)}" style="color: #1a1a1a; word-break: break-all;">${escapeHtml(args.url)}</a></p>`;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function formatTimestamp(iso: string): string {
|
|
44
|
+
export function formatGdprMailTimestamp(iso: string): string {
|
|
189
45
|
try {
|
|
190
46
|
const z = Temporal.Instant.from(iso).toZonedDateTimeISO("UTC");
|
|
191
47
|
return `${z.year}-${pad2(z.month)}-${pad2(z.day)} ${pad2(z.hour)}:${pad2(z.minute)} UTC`;
|
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
import { UNKNOWN_CLIENT_IP } from "@cosmicdrift/kumiko-framework/api";
|
|
3
3
|
import {
|
|
4
4
|
createSystemConfig,
|
|
5
|
+
createSystemUser,
|
|
5
6
|
defineFeature,
|
|
6
7
|
EXT_USER_DATA,
|
|
7
8
|
type FeatureDefinition,
|
|
8
9
|
i18nKey,
|
|
9
10
|
SYSTEM_USER_ID,
|
|
11
|
+
type TenantId,
|
|
10
12
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
13
|
+
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
11
14
|
import {
|
|
12
15
|
createMetricsHandle,
|
|
13
16
|
createNoopMetricsHandle,
|
|
14
17
|
} from "@cosmicdrift/kumiko-framework/observability";
|
|
18
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
19
|
+
import { createConfigAccessor } from "../config/index.js";
|
|
15
20
|
import { validateGdprHookCompleteness, validateGdprPiiHookCoverage } from "./boot-checks.js";
|
|
16
21
|
import {
|
|
17
22
|
EXPORT_SECTION_EXTENSION_NAME,
|
|
@@ -45,6 +50,8 @@ import { createRunForgetCleanupHandler } from "./handlers/run-forget-cleanup.wri
|
|
|
45
50
|
import { USER_DATA_RIGHTS_I18N } from "./i18n.js";
|
|
46
51
|
import {
|
|
47
52
|
type GdprMailDefaults,
|
|
53
|
+
isGdprMailDeliveryAvailable,
|
|
54
|
+
isMailRendererAvailable,
|
|
48
55
|
isMailTransportAvailable,
|
|
49
56
|
makeDefaultDeletionExecutedEmail,
|
|
50
57
|
makeDefaultExportFailedEmail,
|
|
@@ -53,6 +60,7 @@ import {
|
|
|
53
60
|
import { makeTenantMailTransportResolver } from "./lib/mail-transport-resolver.js";
|
|
54
61
|
import { resolveAppTenantModel } from "./lib/resolve-tenant-model.js";
|
|
55
62
|
import { makeTenantStorageProviderResolver } from "./lib/storage-provider-resolver.js";
|
|
63
|
+
import { registerGdprMails } from "./mail-notifications.js";
|
|
56
64
|
import {
|
|
57
65
|
EXPORT_CLEANUP_BACKLOG_AGE_METRIC,
|
|
58
66
|
EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC,
|
|
@@ -163,11 +171,22 @@ export type UserDataRightsOptions = {
|
|
|
163
171
|
readonly privacyCenterShowDeletion?: boolean;
|
|
164
172
|
};
|
|
165
173
|
|
|
174
|
+
function isMailTransportMountedWithoutDelivery(features: readonly FeatureDefinition[]): boolean {
|
|
175
|
+
const names = new Set(features.map((feature) => feature.name));
|
|
176
|
+
const hasTransport = [...names].some((name) => name.startsWith("mail-transport-"));
|
|
177
|
+
return (
|
|
178
|
+
names.has("mail-foundation") &&
|
|
179
|
+
hasTransport &&
|
|
180
|
+
!(names.has("delivery") && names.has("channel-email"))
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
166
184
|
export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): FeatureDefinition {
|
|
167
185
|
// One-shot operator warning (the export cron fires daily — warn once per
|
|
168
186
|
// process, not every run). Lives in the factory scope so the cron closure
|
|
169
187
|
// shares it across runs.
|
|
170
188
|
let warnedMissingExportUrl = false;
|
|
189
|
+
let warnedMissingMailDelivery = false;
|
|
171
190
|
const showDeletion = opts.privacyCenterShowDeletion ?? true;
|
|
172
191
|
return defineFeature("user-data-rights", (r) => {
|
|
173
192
|
r.describe(
|
|
@@ -197,7 +216,15 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
197
216
|
// EXT_USER_DATA channel owner: any r.useExtension(EXT_USER_DATA, ...)
|
|
198
217
|
// usage already requires user-data-rights to be mounted.
|
|
199
218
|
r.bootCheck(({ features }) => validateGdprHookCompleteness(features));
|
|
200
|
-
r.bootCheck(({ features }) =>
|
|
219
|
+
r.bootCheck(({ features }) => {
|
|
220
|
+
validateGdprPiiHookCoverage(features);
|
|
221
|
+
if (!warnedMissingMailDelivery && isMailTransportMountedWithoutDelivery(features)) {
|
|
222
|
+
warnedMissingMailDelivery = true;
|
|
223
|
+
createFallbackLogger("user-data-rights").warn(
|
|
224
|
+
"mail-foundation and a mail transport are mounted but delivery/channel-email are not — the default export and deletion-request mails need them and will not be sent",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
201
228
|
|
|
202
229
|
// App-level tenant-occupancy model. Default "multi-user" → tenant-scoped
|
|
203
230
|
// contributors (e.g. credit) NEVER erase tenant data on a per-user forget
|
|
@@ -312,6 +339,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
312
339
|
});
|
|
313
340
|
|
|
314
341
|
r.translations({ keys: USER_DATA_RIGHTS_I18N });
|
|
342
|
+
registerGdprMails(r);
|
|
315
343
|
// Privacy-center dot-form labels (i18nKey()-marked below), duplicated in
|
|
316
344
|
// web/i18n.ts's client bundle since privacy-center-screen.tsx renders
|
|
317
345
|
// them client-side.
|
|
@@ -520,10 +548,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
520
548
|
handler: async (c, { app, clientIp }) => {
|
|
521
549
|
const url = new URL(c.req.url);
|
|
522
550
|
const parsedBody = await c.req.json().catch(() => null);
|
|
523
|
-
const token =
|
|
524
|
-
parsedBody && typeof parsedBody === "object" && "token" in parsedBody
|
|
525
|
-
? (parsedBody as { token?: unknown }).token
|
|
526
|
-
: undefined;
|
|
551
|
+
const token = isPlainObject(parsedBody) ? parsedBody["token"] : undefined;
|
|
527
552
|
if (typeof token !== "string" || token.length === 0) {
|
|
528
553
|
return c.json({ error: "missing_token" }, 400);
|
|
529
554
|
}
|
|
@@ -605,28 +630,25 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
605
630
|
// versendet der Cron die Export-Notifications selbst (Default-Templates).
|
|
606
631
|
// Der Resolver baut den per-Tenant-Transport aus configResolver — gleiche
|
|
607
632
|
// Bruecke wie buildStorageProvider.
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
handlerName: "user-data-rights:run-export-jobs",
|
|
616
|
-
})
|
|
617
|
-
: undefined;
|
|
633
|
+
// The cron runs across tenants: each mail needs a notify bound to the
|
|
634
|
+
// recipient's tenant (template override, channel config).
|
|
635
|
+
const notifyFactory = ctx._notifyFactory;
|
|
636
|
+
const exportNotifyFor =
|
|
637
|
+
notifyFactory !== undefined && isGdprMailDeliveryAvailable(exportRegistry)
|
|
638
|
+
? (tenantId: TenantId) => notifyFactory(createSystemUser(tenantId), tenantId)
|
|
639
|
+
: undefined;
|
|
618
640
|
const sendExportReadyEmail =
|
|
619
641
|
opts.sendExportReadyEmail ??
|
|
620
|
-
(
|
|
621
|
-
? makeDefaultExportReadyEmail(
|
|
642
|
+
(exportNotifyFor && opts.appExportDownloadUrl !== undefined
|
|
643
|
+
? makeDefaultExportReadyEmail(exportNotifyFor, opts.mailDefaults)
|
|
622
644
|
: undefined);
|
|
623
645
|
const sendExportFailedEmail =
|
|
624
646
|
opts.sendExportFailedEmail ??
|
|
625
|
-
(
|
|
626
|
-
? makeDefaultExportFailedEmail(
|
|
647
|
+
(exportNotifyFor
|
|
648
|
+
? makeDefaultExportFailedEmail(exportNotifyFor, opts.mailDefaults)
|
|
627
649
|
: undefined);
|
|
628
650
|
if (
|
|
629
|
-
|
|
651
|
+
exportNotifyFor &&
|
|
630
652
|
!opts.sendExportReadyEmail &&
|
|
631
653
|
opts.appExportDownloadUrl === undefined &&
|
|
632
654
|
!warnedMissingExportUrl
|
|
@@ -634,7 +656,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
634
656
|
warnedMissingExportUrl = true;
|
|
635
657
|
// biome-ignore lint/suspicious/noConsole: one-shot operator visibility for misconfig
|
|
636
658
|
console.warn(
|
|
637
|
-
"[user-data-rights:run-export-jobs] mail
|
|
659
|
+
"[user-data-rights:run-export-jobs] mail delivery mounted but appExportDownloadUrl unset — default export-ready emails disabled (export-failed + deletion mails still send)",
|
|
638
660
|
);
|
|
639
661
|
}
|
|
640
662
|
|
|
@@ -697,22 +719,37 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
697
719
|
userId: forgetUserId,
|
|
698
720
|
});
|
|
699
721
|
|
|
700
|
-
//
|
|
701
|
-
// mail
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
registry: forgetRegistry,
|
|
705
|
-
configResolver: ctx.configResolver,
|
|
706
|
-
secrets: ctx.secrets,
|
|
707
|
-
db: forgetDb,
|
|
708
|
-
userId: forgetUserId,
|
|
709
|
-
handlerName: "user-data-rights:run-forget-cleanup",
|
|
710
|
-
})
|
|
711
|
-
: undefined;
|
|
722
|
+
// Without an app callback the default mail goes out when a mail transport and a
|
|
723
|
+
// mail renderer are mounted. The user is already erased here, so it bypasses the
|
|
724
|
+
// delivery log: an attempt row would be new personal data.
|
|
725
|
+
const forgetConfigResolver = ctx.configResolver;
|
|
712
726
|
const sendDeletionExecutedEmail =
|
|
713
727
|
opts.sendDeletionExecutedEmail ??
|
|
714
|
-
(
|
|
715
|
-
? makeDefaultDeletionExecutedEmail(
|
|
728
|
+
(isMailTransportAvailable(forgetRegistry) && isMailRendererAvailable(forgetRegistry)
|
|
729
|
+
? makeDefaultDeletionExecutedEmail(
|
|
730
|
+
{
|
|
731
|
+
registry: forgetRegistry,
|
|
732
|
+
db: forgetDb,
|
|
733
|
+
resolveTransport: makeTenantMailTransportResolver({
|
|
734
|
+
registry: forgetRegistry,
|
|
735
|
+
configResolver: forgetConfigResolver,
|
|
736
|
+
secrets: ctx.secrets,
|
|
737
|
+
db: forgetDb,
|
|
738
|
+
userId: forgetUserId,
|
|
739
|
+
handlerName: "user-data-rights:run-forget-cleanup",
|
|
740
|
+
}),
|
|
741
|
+
configFor: (tenantId) =>
|
|
742
|
+
forgetConfigResolver &&
|
|
743
|
+
createConfigAccessor(
|
|
744
|
+
forgetRegistry,
|
|
745
|
+
forgetConfigResolver,
|
|
746
|
+
tenantId,
|
|
747
|
+
forgetUserId,
|
|
748
|
+
forgetDb,
|
|
749
|
+
),
|
|
750
|
+
},
|
|
751
|
+
opts.mailDefaults,
|
|
752
|
+
)
|
|
716
753
|
: undefined);
|
|
717
754
|
|
|
718
755
|
const forgetResult = await runForgetCleanup({
|
|
@@ -71,12 +71,13 @@ export const cancelDeletionWrite = defineWriteHandler({
|
|
|
71
71
|
|
|
72
72
|
// gracePeriodEnd=null im Response symmetrisch zu request-deletion's
|
|
73
73
|
// ISO-Timestamp — Frontend kann beide Endpoints uniform behandeln.
|
|
74
|
+
const gracePeriodEnd: string | null = null;
|
|
74
75
|
return {
|
|
75
76
|
isSuccess: true as const,
|
|
76
77
|
data: {
|
|
77
78
|
userId: event.user.id,
|
|
78
79
|
status: USER_STATUS.Active,
|
|
79
|
-
gracePeriodEnd
|
|
80
|
+
gracePeriodEnd,
|
|
80
81
|
},
|
|
81
82
|
};
|
|
82
83
|
},
|
|
@@ -76,7 +76,11 @@ export const downloadByTokenQuery = defineQueryHandler({
|
|
|
76
76
|
})
|
|
77
77
|
.optional(),
|
|
78
78
|
}),
|
|
79
|
-
|
|
79
|
+
// public-intake: rejected attempts store the anonymous caller's IP in the audit row.
|
|
80
|
+
access: {
|
|
81
|
+
roles: ["anonymous", "Member", "User", "TenantAdmin", "SystemAdmin"],
|
|
82
|
+
personalData: "public-intake",
|
|
83
|
+
},
|
|
80
84
|
agent: { expose: false },
|
|
81
85
|
// Brute-Force-Schutz fuer Token-Hash-Probing. Anonymous-Endpoint mit
|
|
82
86
|
// 32-byte-Random-Token = 256 Bit Search-Space, aber rate-limit als
|
package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { selectMany, type WhereObject } from "@cosmicdrift/kumiko-framework/bun-
|
|
|
2
2
|
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
4
4
|
import * as z from "zod";
|
|
5
|
+
import { decryptStoredPii } from "../../shared/index.js";
|
|
5
6
|
import { downloadAttemptsTable } from "../schema/download-attempt.js";
|
|
6
7
|
|
|
7
8
|
// Operator-Query: invalid Download-Attempts (S2.U7).
|
|
8
9
|
// DPO-Sicht fuer Brute-Force-Detection. Tenant-isolated via WHERE.
|
|
9
10
|
|
|
10
11
|
const MAX_LIMIT = 100;
|
|
12
|
+
const REQUEST_ID = "user-data-rights:list-download-attempts";
|
|
11
13
|
|
|
12
14
|
export const listDownloadAttemptsQuery = defineQueryHandler({
|
|
13
15
|
name: "list-download-attempts",
|
|
@@ -44,6 +46,16 @@ export const listDownloadAttemptsQuery = defineQueryHandler({
|
|
|
44
46
|
limit: p.limit,
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
const decryptedRows = await Promise.all(
|
|
50
|
+
rows.map(async (row) => ({
|
|
51
|
+
...row,
|
|
52
|
+
ip: row.ip === null ? null : await decryptStoredPii(row.ip, "ip", REQUEST_ID),
|
|
53
|
+
userAgent:
|
|
54
|
+
row.userAgent === null
|
|
55
|
+
? null
|
|
56
|
+
: await decryptStoredPii(row.userAgent, "userAgent", REQUEST_ID),
|
|
57
|
+
})),
|
|
58
|
+
);
|
|
59
|
+
return { rows: decryptedRows };
|
|
48
60
|
},
|
|
49
61
|
});
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { createTransportForTenant } from "@cosmicdrift/kumiko-bundled-features/mail-foundation";
|
|
2
1
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
2
|
import { writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
3
|
import * as z from "zod";
|
|
5
4
|
import { USER_STATUS } from "../../user/index.js";
|
|
6
5
|
import {
|
|
7
6
|
type GdprMailDefaults,
|
|
8
|
-
|
|
7
|
+
isGdprMailDeliveryAvailable,
|
|
9
8
|
makeDefaultDeletionRequestedEmail,
|
|
10
9
|
} from "../lib/default-mailers.js";
|
|
11
10
|
import { resolveGracePeriod, startDeletionGracePeriod } from "./deletion-grace-period.js";
|
|
@@ -71,17 +70,12 @@ export function createRequestDeletionHandler(opts: RequestDeletionOptions = {})
|
|
|
71
70
|
if (!res.ok) return writeFailure(res.error);
|
|
72
71
|
const { gracePeriodEnd, userEmail, userLocale } = res;
|
|
73
72
|
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// Ohne gemounteten mail-transport bleibt send undefined → keine Mail.
|
|
73
|
+
// The app callback wins; otherwise the default mail goes through ctx.notify after
|
|
74
|
+
// the commit. Without delivery and an email channel there is no sender and no mail.
|
|
77
75
|
const send =
|
|
78
76
|
opts.sendDeletionRequestedEmail ??
|
|
79
|
-
(
|
|
80
|
-
? makeDefaultDeletionRequestedEmail(
|
|
81
|
-
(tenantId) =>
|
|
82
|
-
createTransportForTenant(ctx, tenantId, "user-data-rights:request-deletion"),
|
|
83
|
-
opts.mailDefaults,
|
|
84
|
-
)
|
|
77
|
+
(ctx.notify !== undefined && isGdprMailDeliveryAvailable(ctx.registry)
|
|
78
|
+
? makeDefaultDeletionRequestedEmail(ctx.notify, opts.mailDefaults)
|
|
85
79
|
: undefined);
|
|
86
80
|
|
|
87
81
|
// Best-effort Email-Notification. Send-Failure darf das Write nicht
|
|
@@ -25,6 +25,7 @@ export type { SendDeletionExecutedEmailFn } from "./run-forget-cleanup.js";
|
|
|
25
25
|
export { policyToStrategy, runForgetCleanup } from "./run-forget-cleanup.js";
|
|
26
26
|
export type { UserExportBundle } from "./run-user-export.js";
|
|
27
27
|
export { runUserExport } from "./run-user-export.js";
|
|
28
|
+
export { downloadAttemptEntity, downloadAttemptsTable } from "./schema/download-attempt.js";
|
|
28
29
|
export {
|
|
29
30
|
ACTIVE_JOB_CONSTRAINT,
|
|
30
31
|
EXPORT_JOB_STATUS,
|