@cosmicdrift/kumiko-samples 0.362.0 → 0.364.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 +23 -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/audit/changes.json +6 -0
- package/packages/bundled-features/src/audit/handlers/list.query.ts +10 -5
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +29 -8
- package/packages/bundled-features/src/auth-email-password/changes.json +51 -0
- package/packages/bundled-features/src/auth-email-password/email-rate-limits.ts +16 -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/change-password.write.ts +6 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
- package/packages/bundled-features/src/auth-email-password/handlers/logout.write.ts +7 -2
- 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/signup-request.write.ts +2 -1
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +5 -13
- 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-email-password/web/auth-gate.tsx +24 -5
- 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 +43 -0
- package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +90 -44
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +22 -1
- 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 +118 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +11 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +39 -33
- 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-receipt-limit.ts +41 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +10 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +36 -20
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +9 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +35 -0
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +11 -3
- 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 +12 -6
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/i18n.ts +3 -0
- package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +20 -11
- package/packages/bundled-features/src/billing-foundation/types.ts +7 -2
- package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +2 -0
- package/packages/bundled-features/src/cap-counter/changes.json +6 -0
- package/packages/bundled-features/src/cap-counter/enforce-cap.ts +75 -5
- package/packages/bundled-features/src/cap-counter/index.ts +1 -0
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -0
- 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 +135 -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/changes.json +6 -0
- package/packages/bundled-features/src/config/handlers/set.write.ts +3 -7
- package/packages/bundled-features/src/config/resolver.ts +15 -12
- package/packages/bundled-features/src/config/write-helpers.ts +23 -0
- package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +48 -27
- package/packages/bundled-features/src/custom-fields/changes.json +6 -0
- package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
- package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +0 -1
- 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 +105 -0
- package/packages/bundled-features/src/delivery/changes.json +66 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +152 -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 +19 -2
- package/packages/bundled-features/src/delivery/jobs.ts +97 -17
- 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 +34 -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/pages.ts +6 -3
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
- package/packages/bundled-features/src/file-foundation/changes.json +6 -0
- package/packages/bundled-features/src/file-foundation/index.ts +7 -0
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +89 -0
- package/packages/bundled-features/src/files-tenant-data/changes.json +6 -0
- package/packages/bundled-features/src/files-tenant-data/hooks.ts +4 -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/changes.json +6 -0
- 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-access-token.ts +13 -8
- 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 +42 -25
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
- package/packages/bundled-features/src/jobs/changes.json +19 -0
- package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +12 -4
- package/packages/bundled-features/src/jobs/feature.ts +7 -4
- package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/list.query.ts +3 -1
- package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +5 -2
- package/packages/bundled-features/src/jobs/job-run-logger.ts +52 -17
- package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +7 -2
- package/packages/bundled-features/src/ledger/changes.json +7 -0
- package/packages/bundled-features/src/ledger/entity.ts +3 -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 +134 -35
- package/packages/bundled-features/src/renderer-pdf/changes.json +12 -0
- package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
- package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +162 -82
- package/packages/bundled-features/src/secrets/changes.json +6 -0
- package/packages/bundled-features/src/secrets/feature.ts +4 -3
- package/packages/bundled-features/src/secrets/secrets-context.ts +23 -3
- 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/changes.json +6 -0
- package/packages/bundled-features/src/shared/chunked-entity-migration.ts +4 -1
- 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 +42 -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/subscription-stripe/changes.json +6 -0
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -11
- package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +3 -1
- 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 +66 -8
- package/packages/bundled-features/src/template-resolver/api.ts +85 -9
- package/packages/bundled-features/src/template-resolver/changes.json +186 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +278 -0
- package/packages/bundled-features/src/template-resolver/constants.ts +49 -11
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
- package/packages/bundled-features/src/template-resolver/document-schema.ts +345 -0
- package/packages/bundled-features/src/template-resolver/feature.ts +59 -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 +160 -0
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +33 -0
- package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +52 -0
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +138 -0
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +9 -6
- 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 +151 -0
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -18
- package/packages/bundled-features/src/template-resolver/handlers/override-diff.ts +49 -0
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +323 -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 +135 -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 +196 -0
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +233 -0
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +137 -0
- package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +91 -17
- 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 +16 -77
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +31 -0
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
- package/packages/bundled-features/src/template-resolver/index.ts +111 -0
- package/packages/bundled-features/src/template-resolver/layout-appearance.ts +40 -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/pdf-constants.ts +12 -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 +34 -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 +171 -20
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
- package/packages/bundled-features/src/template-resolver/table.ts +27 -1
- 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-draft.ts +106 -0
- package/packages/bundled-features/src/template-resolver/template-meta.ts +192 -0
- package/packages/bundled-features/src/template-resolver/template-preview.ts +676 -0
- package/packages/bundled-features/src/template-resolver/template-resources.ts +114 -0
- package/packages/bundled-features/src/template-resolver/template-usage.ts +68 -0
- package/packages/bundled-features/src/template-resolver/template-validation.ts +230 -0
- package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +20 -13
- package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +215 -0
- package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +37 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +1065 -0
- package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +72 -0
- package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +121 -0
- package/packages/bundled-features/src/template-resolver/web/index.ts +26 -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 +176 -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/tier-engine/changes.json +6 -0
- package/packages/bundled-features/src/tier-engine/feature.ts +11 -3
- package/packages/bundled-features/src/user/changes.json +6 -0
- 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/resolve-display-names.ts +8 -15
- package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
- package/packages/bundled-features/src/user/seeding.ts +19 -5
- package/packages/bundled-features/src/user-data-rights/changes.json +13 -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/request-deletion.write.ts +5 -11
- 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/web/privacy-center-screen.tsx +2 -1
- package/packages/bundled-features/src/user-profile/changes.json +1 -1
- 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/waitlist/options.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +3 -3
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +7 -1
- package/samples/apps/ui-walkthrough/package.json +11 -0
- package/samples/apps/ui-walkthrough/playwright.config.ts +40 -7
- 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 +239 -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 +570 -0
- package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +72 -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/run-config.ts +41 -1
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +118 -21
- 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
|
@@ -30,6 +30,12 @@ import {
|
|
|
30
30
|
} from "./handlers/signup-request.write.js";
|
|
31
31
|
import { createSwitchTenantMfaGateHandler } from "./handlers/switch-tenant-mfa-gate.write.js";
|
|
32
32
|
import { createVerifyEmailHandler } from "./handlers/verify-email.write.js";
|
|
33
|
+
import {
|
|
34
|
+
AUTH_MAIL_VARIABLES,
|
|
35
|
+
authMailLocalizedTemplate,
|
|
36
|
+
INVITE_MAIL_VARIABLES,
|
|
37
|
+
sendsMagicLinkMail,
|
|
38
|
+
} from "./mail-notifications.js";
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* Env-vars contract for the `auth-email-password` feature.
|
|
@@ -199,17 +205,32 @@ export function createAuthEmailPasswordFeature(
|
|
|
199
205
|
// All five magic-link flows (reset/verify/signup/invite/unlock) dispatch
|
|
200
206
|
// via ctx.notify → delivery must be mounted. Fail closed at boot instead
|
|
201
207
|
// of silently dropping the mail.
|
|
202
|
-
if (
|
|
203
|
-
opts.passwordReset ||
|
|
204
|
-
opts.emailVerification ||
|
|
205
|
-
opts.signup ||
|
|
206
|
-
opts.invite ||
|
|
207
|
-
opts.accountUnlock
|
|
208
|
-
) {
|
|
209
|
-
r.requires("delivery");
|
|
210
|
-
}
|
|
208
|
+
if (sendsMagicLinkMail(opts)) r.requires("delivery");
|
|
211
209
|
r.envSchema(authEmailPasswordEnvSchema);
|
|
212
210
|
|
|
211
|
+
// Declared unconditionally: apps also issue the activation link from their
|
|
212
|
+
// own flows (issueSignupActivation) without enabling the signup option.
|
|
213
|
+
r.notification("password-reset", {
|
|
214
|
+
variables: AUTH_MAIL_VARIABLES,
|
|
215
|
+
localizedTemplate: authMailLocalizedTemplate("passwordReset"),
|
|
216
|
+
});
|
|
217
|
+
r.notification("account-unlock", {
|
|
218
|
+
variables: AUTH_MAIL_VARIABLES,
|
|
219
|
+
localizedTemplate: authMailLocalizedTemplate("accountUnlock"),
|
|
220
|
+
});
|
|
221
|
+
r.notification("email-verification", {
|
|
222
|
+
variables: AUTH_MAIL_VARIABLES,
|
|
223
|
+
localizedTemplate: authMailLocalizedTemplate("emailVerification"),
|
|
224
|
+
});
|
|
225
|
+
r.notification("signup-activation", {
|
|
226
|
+
variables: AUTH_MAIL_VARIABLES,
|
|
227
|
+
localizedTemplate: authMailLocalizedTemplate("signupActivation"),
|
|
228
|
+
});
|
|
229
|
+
r.notification("invite", {
|
|
230
|
+
variables: INVITE_MAIL_VARIABLES,
|
|
231
|
+
localizedTemplate: authMailLocalizedTemplate("invite"),
|
|
232
|
+
});
|
|
233
|
+
|
|
213
234
|
const handlers = {
|
|
214
235
|
login: r.writeHandler(
|
|
215
236
|
createLoginHandler({
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
access,
|
|
3
2
|
createSystemUser,
|
|
4
3
|
defineWriteHandler,
|
|
5
4
|
SYSTEM_TENANT_ID,
|
|
@@ -20,7 +19,12 @@ export const changePasswordWrite = defineWriteHandler({
|
|
|
20
19
|
oldPassword: z.string().min(1),
|
|
21
20
|
newPassword: passwordSchema,
|
|
22
21
|
}),
|
|
23
|
-
access: {
|
|
22
|
+
access: {
|
|
23
|
+
openToAll: {
|
|
24
|
+
reason:
|
|
25
|
+
"Self-service: acts only on event.user.id and re-checks the caller's current password; invited members hold only an app role, so a fixed role list would lock them out.",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
24
28
|
escapeHatch: {
|
|
25
29
|
grants: ["systemIdentity"],
|
|
26
30
|
reason:
|
|
@@ -55,12 +55,10 @@ import { GLOBAL_ROLE_OPTIONS } from "../../user/constants.js";
|
|
|
55
55
|
import { userTable } from "../../user/schema/user.js";
|
|
56
56
|
import { AUTH_INVITE_DEFAULT_TTL_MINUTES } from "../constants.js";
|
|
57
57
|
import type { AuthMailLocale } from "../email-templates.js";
|
|
58
|
-
import { renderInviteEmail } from "../email-templates.js";
|
|
59
58
|
import { inviteAlreadyAccepted } from "../errors.js";
|
|
60
59
|
import { invalidateExistingInviteToken, storeInviteToken } from "../invite-token-store.js";
|
|
61
60
|
import { dispatchMagicLinkMail, resolveHandlerMailLocale } from "../magic-link-mail.js";
|
|
62
|
-
|
|
63
|
-
const INVITE_NOTIFICATION_TYPE = "auth-email-password:invite";
|
|
61
|
+
import { AuthMailNotificationTypes } from "../mail-notifications.js";
|
|
64
62
|
|
|
65
63
|
type GlobalRole = (typeof GLOBAL_ROLE_OPTIONS)[number];
|
|
66
64
|
|
|
@@ -247,8 +245,8 @@ async function issueInvitation(
|
|
|
247
245
|
ctx.notify,
|
|
248
246
|
{
|
|
249
247
|
handlerName: "invite-create",
|
|
250
|
-
notificationType:
|
|
251
|
-
|
|
248
|
+
notificationType: AuthMailNotificationTypes.invite,
|
|
249
|
+
fallbackAppName: "Workspace",
|
|
252
250
|
},
|
|
253
251
|
{
|
|
254
252
|
email,
|
|
@@ -257,6 +255,7 @@ async function issueInvitation(
|
|
|
257
255
|
expiresAt: expiresAt.toString(),
|
|
258
256
|
issuedAt: issuedAt.toString(),
|
|
259
257
|
timeZone: ctx.tz.user,
|
|
258
|
+
role: request.role,
|
|
260
259
|
...(opts.appName !== undefined && { appName: opts.appName }),
|
|
261
260
|
locale,
|
|
262
261
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineWriteHandler, stepsPipeline } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
|
|
4
4
|
// Logout — JWT is stateless, so server-side we only return OK. A future
|
|
@@ -7,7 +7,12 @@ import * as z from "zod";
|
|
|
7
7
|
export const logoutWrite = defineWriteHandler({
|
|
8
8
|
name: "logout",
|
|
9
9
|
schema: z.object({}),
|
|
10
|
-
access: {
|
|
10
|
+
access: {
|
|
11
|
+
openToAll: {
|
|
12
|
+
reason:
|
|
13
|
+
"Self-service: touches no data, any signed-in member must be able to sign out regardless of which role they hold.",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
11
16
|
description:
|
|
12
17
|
"Acknowledges a sign-out for the authenticated caller so the client can discard its token; the JWT is stateless, so no server-side session state changes here.",
|
|
13
18
|
perform: stepsPipeline(({ r }) => [
|
package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AUTH_UNLOCK_DEFAULT_TTL_MINUTES, AuthErrors } from "../constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { AuthMailNotificationTypes } from "../mail-notifications.js";
|
|
3
3
|
import { signUnlockToken } from "../unlock-token.js";
|
|
4
4
|
import {
|
|
5
5
|
createTokenRequestHandler,
|
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
type TokenRequestOptions,
|
|
8
8
|
} from "./token-request-handler.js";
|
|
9
9
|
|
|
10
|
-
const UNLOCK_NOTIFICATION_TYPE = "auth-email-password:account-unlock";
|
|
11
|
-
|
|
12
10
|
export type RequestAccountUnlockOptions = TokenRequestOptions;
|
|
13
11
|
|
|
14
12
|
// Public shape re-exported for callers that build custom routes on top of
|
|
@@ -32,8 +30,7 @@ export function createRequestAccountUnlockHandler(opts: RequestAccountUnlockOpti
|
|
|
32
30
|
// locked right now. Confirming just clears the (possibly already
|
|
33
31
|
// empty) lockout state — harmless no-op if they weren't locked.
|
|
34
32
|
extraSilentSkip: () => false,
|
|
35
|
-
notificationType:
|
|
36
|
-
renderContent: renderUnlockAccountEmail,
|
|
33
|
+
notificationType: AuthMailNotificationTypes.accountUnlock,
|
|
37
34
|
},
|
|
38
35
|
opts,
|
|
39
36
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AUTH_VERIFY_DEFAULT_TTL_MINUTES, AuthErrors } from "../constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { AuthMailNotificationTypes } from "../mail-notifications.js";
|
|
3
3
|
import { signVerificationToken } from "../verification-token.js";
|
|
4
4
|
import {
|
|
5
5
|
createTokenRequestHandler,
|
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
type TokenRequestOptions,
|
|
8
8
|
} from "./token-request-handler.js";
|
|
9
9
|
|
|
10
|
-
const VERIFY_NOTIFICATION_TYPE = "auth-email-password:email-verification";
|
|
11
|
-
|
|
12
10
|
export type RequestEmailVerificationOptions = TokenRequestOptions;
|
|
13
11
|
|
|
14
12
|
export type RequestVerificationData = TokenRequestData<"verification-requested">;
|
|
@@ -29,8 +27,7 @@ export function createRequestEmailVerificationHandler(opts: RequestEmailVerifica
|
|
|
29
27
|
// unknown/deleted to keep the enumeration surface symmetric — the
|
|
30
28
|
// caller sees the same 200 regardless of whether a token was minted.
|
|
31
29
|
extraSilentSkip: (user) => user.emailVerified === true,
|
|
32
|
-
notificationType:
|
|
33
|
-
renderContent: renderVerifyEmail,
|
|
30
|
+
notificationType: AuthMailNotificationTypes.emailVerification,
|
|
34
31
|
},
|
|
35
32
|
opts,
|
|
36
33
|
);
|
package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AUTH_RESET_DEFAULT_TTL_MINUTES, AuthErrors } from "../constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { AuthMailNotificationTypes } from "../mail-notifications.js";
|
|
3
3
|
import { signResetToken } from "../reset-token.js";
|
|
4
4
|
import {
|
|
5
5
|
createTokenRequestHandler,
|
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
type TokenRequestOptions,
|
|
8
8
|
} from "./token-request-handler.js";
|
|
9
9
|
|
|
10
|
-
const RESET_NOTIFICATION_TYPE = "auth-email-password:password-reset";
|
|
11
|
-
|
|
12
10
|
export type RequestPasswordResetOptions = TokenRequestOptions;
|
|
13
11
|
|
|
14
12
|
// Public shape re-exported for callers that build custom routes on top of
|
|
@@ -31,8 +29,7 @@ export function createRequestPasswordResetHandler(opts: RequestPasswordResetOpti
|
|
|
31
29
|
// non-deleted user can initiate a reset regardless of verification
|
|
32
30
|
// state. The sessions feature handles the post-change revocation.
|
|
33
31
|
extraSilentSkip: () => false,
|
|
34
|
-
notificationType:
|
|
35
|
-
renderContent: renderResetPasswordEmail,
|
|
32
|
+
notificationType: AuthMailNotificationTypes.passwordReset,
|
|
36
33
|
},
|
|
37
34
|
opts,
|
|
38
35
|
);
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
} from "@cosmicdrift/kumiko-framework/errors";
|
|
37
37
|
import { generateUniqueName } from "@cosmicdrift/kumiko-framework/random";
|
|
38
38
|
import { generateId } from "@cosmicdrift/kumiko-framework/utils";
|
|
39
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
39
40
|
import * as z from "zod";
|
|
40
41
|
import {
|
|
41
42
|
findSignupHandoverProvider,
|
|
@@ -100,10 +101,8 @@ const SIGNUP_CONFIRM_PROVISION_REASON =
|
|
|
100
101
|
// match; this reads the same field the framework itself uses.
|
|
101
102
|
function isBenignClaimRejection(error: WriteErrorInfo): boolean {
|
|
102
103
|
return (
|
|
103
|
-
|
|
104
|
-
error.details
|
|
105
|
-
(error.details as Record<string, unknown>)["reason"] ===
|
|
106
|
-
SIGNUP_HANDOVER_BENIGN_CLAIM_REJECTION_CODE
|
|
104
|
+
isPlainObject(error.details) &&
|
|
105
|
+
error.details["reason"] === SIGNUP_HANDOVER_BENIGN_CLAIM_REJECTION_CODE
|
|
107
106
|
);
|
|
108
107
|
}
|
|
109
108
|
|
|
@@ -25,6 +25,7 @@ import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
|
25
25
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
26
26
|
import * as z from "zod";
|
|
27
27
|
import { findSignupHandoverProvider, type SignupHandoverBinding } from "../../shared/index.js";
|
|
28
|
+
import { AUTH_EMAIL_PAYLOAD_RATE_LIMITS } from "../email-rate-limits.js";
|
|
28
29
|
import type { AuthMailLocale } from "../email-templates.js";
|
|
29
30
|
import { resolveHandlerMailLocale } from "../magic-link-mail.js";
|
|
30
31
|
import { AUTH_SELF_REGISTRATION_FEATURE } from "../self-registration-toggle.js";
|
|
@@ -79,7 +80,7 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
|
|
|
79
80
|
schema: SignupRequestSchema,
|
|
80
81
|
access: { roles: ["anonymous"] },
|
|
81
82
|
rateLimit: { per: "ip+handler", limit: 10, windowSeconds: 60 },
|
|
82
|
-
additionalRateLimits:
|
|
83
|
+
additionalRateLimits: AUTH_EMAIL_PAYLOAD_RATE_LIMITS,
|
|
83
84
|
description:
|
|
84
85
|
"Starts magic-link self-registration by mailing a fresh activation link to an address and invalidating any link still outstanding for it; the answer looks the same whether or not the address is already registered.",
|
|
85
86
|
escapeHatch: { reason: HANDOVER_GRANT_VERIFY_REASON, grants: ["unsafeRaw"] },
|
|
@@ -19,11 +19,8 @@ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
|
19
19
|
import * as z from "zod";
|
|
20
20
|
import { UserQueries } from "../../user/index.js";
|
|
21
21
|
import { type AuthUserRow, parseAuthUserRow } from "../auth-user-row.js";
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
AuthMailLocale,
|
|
25
|
-
RenderTokenContentArgs,
|
|
26
|
-
} from "../email-templates.js";
|
|
22
|
+
import { AUTH_EMAIL_PAYLOAD_RATE_LIMITS } from "../email-rate-limits.js";
|
|
23
|
+
import type { AuthMailLocale } from "../email-templates.js";
|
|
27
24
|
import { dispatchMagicLinkMail, resolveHandlerMailLocale } from "../magic-link-mail.js";
|
|
28
25
|
|
|
29
26
|
const RequestTokenSchema = z.object({
|
|
@@ -67,13 +64,9 @@ export type TokenRequestSpec<TName extends string, TSuccessKind extends string>
|
|
|
67
64
|
// soft-deleted". Verification skips when emailVerified is already true;
|
|
68
65
|
// password-reset has no extra condition (returns false).
|
|
69
66
|
readonly extraSilentSkip: (user: AuthUserRow) => boolean;
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
// route:{email} path delivers directly and buildMessage falls back to data.
|
|
67
|
+
// Trigger-less r.notification dispatched via ctx.notify on success; the
|
|
68
|
+
// recipient is an anonymous email, so route:{email} delivers directly.
|
|
73
69
|
readonly notificationType: string;
|
|
74
|
-
// Builds the structured mail body from the magic-link + expiry. Per-flow
|
|
75
|
-
// (renderResetPasswordEmail / renderVerifyEmail).
|
|
76
|
-
readonly renderContent: (args: RenderTokenContentArgs) => AuthMailContent;
|
|
77
70
|
};
|
|
78
71
|
|
|
79
72
|
export type TokenRequestOptions = {
|
|
@@ -107,7 +100,7 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
|
|
|
107
100
|
schema: RequestTokenSchema,
|
|
108
101
|
access: { roles: ["anonymous"] },
|
|
109
102
|
rateLimit: { per: "ip+handler", limit: 10, windowSeconds: 60 },
|
|
110
|
-
additionalRateLimits:
|
|
103
|
+
additionalRateLimits: AUTH_EMAIL_PAYLOAD_RATE_LIMITS,
|
|
111
104
|
escapeHatch: {
|
|
112
105
|
grants: ["systemIdentity"],
|
|
113
106
|
reason:
|
|
@@ -154,7 +147,6 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
|
|
|
154
147
|
{
|
|
155
148
|
handlerName: spec.handlerName,
|
|
156
149
|
notificationType: spec.notificationType,
|
|
157
|
-
renderContent: spec.renderContent,
|
|
158
150
|
},
|
|
159
151
|
{
|
|
160
152
|
// Row value is ciphertext with an active KMS; the byte-exact
|
|
@@ -21,23 +21,7 @@ export type {
|
|
|
21
21
|
} from "./bootstrap.js";
|
|
22
22
|
export { BootstrapPlanError, BootstrapWriteError, bootstrapTenants } from "./bootstrap.js";
|
|
23
23
|
export { AUTH_EMAIL_PASSWORD_FEATURE, AuthErrors, AuthHandlers, AuthQueries } from "./constants.js";
|
|
24
|
-
|
|
25
|
-
// signup-activation, invite) emit structured AuthMailContent through delivery
|
|
26
|
-
// (ctx.notify).
|
|
27
|
-
export type {
|
|
28
|
-
AuthMailContent,
|
|
29
|
-
AuthMailLocale,
|
|
30
|
-
AuthMailSection,
|
|
31
|
-
RenderInviteEmailArgs,
|
|
32
|
-
RenderTokenContentArgs,
|
|
33
|
-
} from "./email-templates.js";
|
|
34
|
-
export {
|
|
35
|
-
renderActivationEmail,
|
|
36
|
-
renderInviteEmail,
|
|
37
|
-
renderResetPasswordEmail,
|
|
38
|
-
renderUnlockAccountEmail,
|
|
39
|
-
renderVerifyEmail,
|
|
40
|
-
} from "./email-templates.js";
|
|
24
|
+
export type { AuthMailLocale } from "./email-templates.js";
|
|
41
25
|
export type {
|
|
42
26
|
AccountLockoutOptions,
|
|
43
27
|
AccountUnlockOptions,
|
|
@@ -48,6 +32,7 @@ export type {
|
|
|
48
32
|
SignupOptions,
|
|
49
33
|
} from "./feature.js";
|
|
50
34
|
export { authEmailPasswordEnvSchema, createAuthEmailPasswordFeature } from "./feature.js";
|
|
35
|
+
export { AuthMailNotificationTypes } from "./mail-notifications.js";
|
|
51
36
|
export {
|
|
52
37
|
AUTH_SELF_REGISTRATION_FEATURE,
|
|
53
38
|
createAuthSelfRegistrationToggleFeature,
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
// Shared dispatch for the magic-link mails (password-reset, email-verification,
|
|
2
|
-
// signup-activation). All
|
|
3
|
-
// token to the app page
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
2
|
+
// account-unlock, signup-activation, invite). All follow the same delivery
|
|
3
|
+
// shape: append the token to the app page and notify the recipient email
|
|
4
|
+
// directly (route:{email} skips preferences — the recipient may not have a user
|
|
5
|
+
// account yet — and critical priority keeps a security mail non-unsubscribable).
|
|
6
|
+
// Each mail type is a trigger-less r.notification whose stored template renders
|
|
7
|
+
// from the variables passed here.
|
|
8
8
|
|
|
9
9
|
import { requestContext } from "@cosmicdrift/kumiko-framework/api";
|
|
10
10
|
import type { NotifyFn } from "@cosmicdrift/kumiko-framework/engine";
|
|
11
11
|
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
12
|
-
import { resolveMailLocale } from "@cosmicdrift/kumiko-framework/i18n";
|
|
13
|
-
import
|
|
12
|
+
import { mailT, resolveMailLocale } from "@cosmicdrift/kumiko-framework/i18n";
|
|
13
|
+
import { type AuthMailLocale, tokenMailExpiry } from "./email-templates.js";
|
|
14
|
+
import { authMailTemplateLocale } from "./mail-notifications.js";
|
|
14
15
|
|
|
15
|
-
// Per-flow constants: which notification type to dispatch
|
|
16
|
-
//
|
|
17
|
-
// renderActivationEmail), all unified on RenderTokenContentArgs → AuthMailContent.
|
|
16
|
+
// Per-flow constants: which notification type to dispatch. fallbackAppName
|
|
17
|
+
// replaces the translated default when the flow has no app name of its own.
|
|
18
18
|
export type MagicLinkMailSpec = {
|
|
19
19
|
readonly handlerName: string;
|
|
20
20
|
readonly notificationType: string;
|
|
21
|
-
readonly
|
|
21
|
+
readonly fallbackAppName?: string;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
// Per-request values: the recipient + the app page that receives the token, plus
|
|
@@ -36,6 +36,8 @@ export type MagicLinkMailParams = {
|
|
|
36
36
|
readonly locale?: AuthMailLocale;
|
|
37
37
|
readonly issuedAt?: string;
|
|
38
38
|
readonly timeZone?: string;
|
|
39
|
+
// Invite mails name the role the recipient is invited as.
|
|
40
|
+
readonly role?: string;
|
|
39
41
|
};
|
|
40
42
|
|
|
41
43
|
function resolveAppUrl(appUrl: string | ((locale: string) => string), locale: string): string {
|
|
@@ -75,18 +77,24 @@ export async function dispatchMagicLinkMail(
|
|
|
75
77
|
// mailT already root-falls-back; appUrl must use the same negotiated tag
|
|
76
78
|
// or language-in-path apps 404 on e.g. /de-AT/activate.
|
|
77
79
|
const urlLocale = resolveMailLocale(locale);
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
const { validFor, validUntil } = tokenMailExpiry(
|
|
81
|
+
{
|
|
82
|
+
expiresAt: params.expiresAt,
|
|
83
|
+
...(params.issuedAt !== undefined && { issuedAt: params.issuedAt }),
|
|
84
|
+
...(params.timeZone !== undefined && { timeZone: params.timeZone }),
|
|
85
|
+
},
|
|
86
|
+
locale,
|
|
87
|
+
);
|
|
86
88
|
await notify(spec.notificationType, {
|
|
87
89
|
route: { email: params.email },
|
|
88
|
-
data:
|
|
89
|
-
|
|
90
|
+
data: {
|
|
91
|
+
appName: params.appName ?? spec.fallbackAppName ?? mailT(locale, "auth.mail.appNameDefault"),
|
|
92
|
+
actionUrl: appendToken(resolveAppUrl(params.appUrl, urlLocale), params.token),
|
|
93
|
+
validFor,
|
|
94
|
+
validUntil,
|
|
95
|
+
...(params.role !== undefined && { role: params.role }),
|
|
96
|
+
},
|
|
97
|
+
locale: authMailTemplateLocale(spec.notificationType, locale),
|
|
90
98
|
priority: "critical",
|
|
91
99
|
});
|
|
92
100
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type NotificationLocalizedTemplate,
|
|
3
|
+
QnTypes,
|
|
4
|
+
qn,
|
|
5
|
+
type TemplateVariables,
|
|
6
|
+
templateVariable,
|
|
7
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
+
import {
|
|
9
|
+
hasMailTranslationKey,
|
|
10
|
+
listMailLocales,
|
|
11
|
+
mailT,
|
|
12
|
+
resolveMailLocale,
|
|
13
|
+
} from "@cosmicdrift/kumiko-framework/i18n";
|
|
14
|
+
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
15
|
+
import { AUTH_EMAIL_PASSWORD_FEATURE } from "./constants.js";
|
|
16
|
+
|
|
17
|
+
export const AUTH_MAIL_KINDS = {
|
|
18
|
+
passwordReset: { name: "password-reset", translationKey: "reset" },
|
|
19
|
+
accountUnlock: { name: "account-unlock", translationKey: "unlock" },
|
|
20
|
+
emailVerification: { name: "email-verification", translationKey: "verify" },
|
|
21
|
+
signupActivation: { name: "signup-activation", translationKey: "activation" },
|
|
22
|
+
invite: { name: "invite", translationKey: "invite" },
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
export type AuthMailKind = keyof typeof AUTH_MAIL_KINDS;
|
|
26
|
+
|
|
27
|
+
type MagicLinkFlowOptions = Partial<
|
|
28
|
+
Record<"passwordReset" | "emailVerification" | "signup" | "invite" | "accountUnlock", unknown>
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
export function sendsMagicLinkMail(flows: MagicLinkFlowOptions): boolean {
|
|
32
|
+
return Boolean(
|
|
33
|
+
flows.passwordReset ||
|
|
34
|
+
flows.emailVerification ||
|
|
35
|
+
flows.signup ||
|
|
36
|
+
flows.invite ||
|
|
37
|
+
flows.accountUnlock,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function authMailType(kind: AuthMailKind): string {
|
|
42
|
+
return qn(AUTH_EMAIL_PASSWORD_FEATURE, QnTypes.notify, AUTH_MAIL_KINDS[kind].name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const AuthMailNotificationTypes: Readonly<Record<AuthMailKind, string>> = {
|
|
46
|
+
passwordReset: authMailType("passwordReset"),
|
|
47
|
+
accountUnlock: authMailType("accountUnlock"),
|
|
48
|
+
emailVerification: authMailType("emailVerification"),
|
|
49
|
+
signupActivation: authMailType("signupActivation"),
|
|
50
|
+
invite: authMailType("invite"),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function subjectKey(kind: AuthMailKind): string {
|
|
54
|
+
return `auth.mail.${AUTH_MAIL_KINDS[kind].translationKey}.subject`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ALL_AUTH_MAIL_KINDS: readonly AuthMailKind[] = [
|
|
58
|
+
"passwordReset",
|
|
59
|
+
"accountUnlock",
|
|
60
|
+
"emailVerification",
|
|
61
|
+
"signupActivation",
|
|
62
|
+
"invite",
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const SUBJECT_KEY_BY_TYPE: ReadonlyMap<string, string> = new Map(
|
|
66
|
+
ALL_AUTH_MAIL_KINDS.map((kind) => [AuthMailNotificationTypes[kind], subjectKey(kind)]),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// A registered locale whose bundle lacks the subject key has no template (see
|
|
70
|
+
// authMailLocalizedTemplate); the renderer has no locale fallback, so the
|
|
71
|
+
// sender must pick a locale that has one or the mail would not go out at all.
|
|
72
|
+
export function authMailTemplateLocale(notificationType: string, requestedLocale: string): string {
|
|
73
|
+
const locale = resolveMailLocale(requestedLocale);
|
|
74
|
+
const key = SUBJECT_KEY_BY_TYPE.get(notificationType);
|
|
75
|
+
return key !== undefined && hasMailTranslationKey(locale, key) ? locale : "en";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const AUTH_MAIL_VARIABLES: TemplateVariables = {
|
|
79
|
+
appName: templateVariable.text({ required: true }),
|
|
80
|
+
actionUrl: templateVariable.link({ required: true }),
|
|
81
|
+
validFor: templateVariable.text({ required: true }),
|
|
82
|
+
validUntil: templateVariable.text({ required: true }),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const INVITE_MAIL_VARIABLES: TemplateVariables = {
|
|
86
|
+
...AUTH_MAIL_VARIABLES,
|
|
87
|
+
role: templateVariable.text({ required: true }),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Translation placeholder -> template variable. The markers survive HTML
|
|
91
|
+
// escaping, so only the translated text is escaped, never the placeholders.
|
|
92
|
+
const PLACEHOLDER_VARIABLES: Readonly<Record<string, string>> = {
|
|
93
|
+
app: "appName",
|
|
94
|
+
duration: "validFor",
|
|
95
|
+
when: "validUntil",
|
|
96
|
+
role: "role",
|
|
97
|
+
};
|
|
98
|
+
const MARKER_OPEN = "\uE000";
|
|
99
|
+
const MARKER_CLOSE = "\uE001";
|
|
100
|
+
const MARKER_PATTERN = new RegExp(`${MARKER_OPEN}(\\w+)${MARKER_CLOSE}`, "g");
|
|
101
|
+
|
|
102
|
+
function markers(): Readonly<Record<string, string>> {
|
|
103
|
+
return Object.fromEntries(
|
|
104
|
+
Object.keys(PLACEHOLDER_VARIABLES).map((name) => [
|
|
105
|
+
name,
|
|
106
|
+
`${MARKER_OPEN}${name}${MARKER_CLOSE}`,
|
|
107
|
+
]),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function translatedHtml(locale: string, key: string): string {
|
|
112
|
+
return escapeHtml(mailT(locale, key, markers())).replace(MARKER_PATTERN, (_, name: string) => {
|
|
113
|
+
const variable = PLACEHOLDER_VARIABLES[name];
|
|
114
|
+
return variable === undefined
|
|
115
|
+
? ""
|
|
116
|
+
: `<span data-variable="${escapeHtmlAttr(variable)}"></span>`;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function mailBody(locale: string, translationKey: string): string {
|
|
121
|
+
const html = (part: string) => translatedHtml(locale, `auth.mail.${translationKey}.${part}`);
|
|
122
|
+
return [
|
|
123
|
+
`<p>${html("greeting")}</p>`,
|
|
124
|
+
`<p>${html("intro")}</p>`,
|
|
125
|
+
`<p><a data-button href="{{actionUrl}}">${html("button")}</a></p>`,
|
|
126
|
+
`<p>${html("expiry")}</p>`,
|
|
127
|
+
`<p>${html("ignore")}</p>`,
|
|
128
|
+
].join("");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Evaluated when the registry is built, after every locale feature has
|
|
132
|
+
// registered its mail translations. English always ships; another locale only
|
|
133
|
+
// gets a template when it has its own subject, so a missing translation never
|
|
134
|
+
// silently becomes an English mail in that locale's slot.
|
|
135
|
+
export function authMailLocalizedTemplate(kind: AuthMailKind): () => NotificationLocalizedTemplate {
|
|
136
|
+
const { translationKey } = AUTH_MAIL_KINDS[kind];
|
|
137
|
+
const kindSubjectKey = subjectKey(kind);
|
|
138
|
+
return () => {
|
|
139
|
+
const locales = [
|
|
140
|
+
"en",
|
|
141
|
+
...listMailLocales().filter(
|
|
142
|
+
(locale) => locale !== "en" && hasMailTranslationKey(locale, kindSubjectKey),
|
|
143
|
+
),
|
|
144
|
+
];
|
|
145
|
+
return Object.fromEntries(
|
|
146
|
+
locales.map((locale) => [
|
|
147
|
+
locale,
|
|
148
|
+
{
|
|
149
|
+
body: mailBody(locale, translationKey),
|
|
150
|
+
meta: {
|
|
151
|
+
subject: mailT(locale, kindSubjectKey, { app: "{{appName}}" }),
|
|
152
|
+
category: "transactional" as const,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
]),
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -9,15 +9,15 @@ import type { NotifyFn } from "@cosmicdrift/kumiko-framework/engine";
|
|
|
9
9
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
10
10
|
import type { Redis } from "ioredis";
|
|
11
11
|
import { AUTH_SIGNUP_DEFAULT_TTL_MINUTES } from "./constants.js";
|
|
12
|
-
import { renderActivationEmail } from "./email-templates.js";
|
|
13
12
|
import { dispatchMagicLinkMail } from "./magic-link-mail.js";
|
|
13
|
+
import { AuthMailNotificationTypes } from "./mail-notifications.js";
|
|
14
14
|
import {
|
|
15
15
|
invalidateExistingSignupToken,
|
|
16
16
|
normalizeEmail,
|
|
17
17
|
storeSignupToken,
|
|
18
18
|
} from "./signup-token-store.js";
|
|
19
19
|
|
|
20
|
-
export const SIGNUP_ACTIVATION_NOTIFICATION_TYPE =
|
|
20
|
+
export const SIGNUP_ACTIVATION_NOTIFICATION_TYPE = AuthMailNotificationTypes.signupActivation;
|
|
21
21
|
|
|
22
22
|
export type IssueSignupActivationArgs = {
|
|
23
23
|
readonly redis: Redis;
|
|
@@ -65,7 +65,6 @@ export async function issueSignupActivation(
|
|
|
65
65
|
{
|
|
66
66
|
handlerName: "signup-request",
|
|
67
67
|
notificationType: SIGNUP_ACTIVATION_NOTIFICATION_TYPE,
|
|
68
|
-
renderContent: renderActivationEmail,
|
|
69
68
|
},
|
|
70
69
|
{
|
|
71
70
|
email,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// Screen rein konfigurieren, ohne den Gate selbst ersetzen zu müssen.
|
|
11
11
|
|
|
12
12
|
import { useLocale } from "@cosmicdrift/kumiko-renderer";
|
|
13
|
-
import { type ComponentType, type ReactNode, useEffect, useState } from "react";
|
|
13
|
+
import { type ComponentType, type ReactNode, useEffect, useMemo, useState } from "react";
|
|
14
14
|
import {
|
|
15
15
|
assertNavigableUrl,
|
|
16
16
|
buildLoginRedirectUrl,
|
|
@@ -204,6 +204,21 @@ export function resolveLoginRedirect(
|
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
// A throwing loginUrl builder must not take the whole app tree down in render;
|
|
208
|
+
// null falls through to the built-in login screen.
|
|
209
|
+
function resolveLoginRedirectOrNull(
|
|
210
|
+
loginUrl: string | LoginUrlBuilder,
|
|
211
|
+
locale: string,
|
|
212
|
+
location: Pick<Location, "origin" | "pathname" | "search" | "hash">,
|
|
213
|
+
): LoginRedirect | null {
|
|
214
|
+
try {
|
|
215
|
+
return resolveLoginRedirect(loginUrl, locale, location);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error("Auth gate: loginUrl could not be resolved, showing the built-in login", error);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
207
222
|
export function makeAuthGate(opts: AuthGateOptions = {}): ComponentType<{
|
|
208
223
|
children: ReactNode;
|
|
209
224
|
}> {
|
|
@@ -213,10 +228,14 @@ export function makeAuthGate(opts: AuthGateOptions = {}): ComponentType<{
|
|
|
213
228
|
function AuthGate({ children }: { readonly children: ReactNode }): ReactNode {
|
|
214
229
|
const { status } = useSession();
|
|
215
230
|
const locale = useLocale().locale();
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
231
|
+
const { origin, pathname, search, hash } = window.location;
|
|
232
|
+
const loginRedirect = useMemo(
|
|
233
|
+
() =>
|
|
234
|
+
loginUrl !== undefined && status === "unauthenticated"
|
|
235
|
+
? resolveLoginRedirectOrNull(loginUrl, locale, { origin, pathname, search, hash })
|
|
236
|
+
: null,
|
|
237
|
+
[status, locale, origin, pathname, search, hash],
|
|
238
|
+
);
|
|
220
239
|
const redirectUrl = loginRedirect?.kind === "redirect" ? loginRedirect.url : null;
|
|
221
240
|
// kumiko-lint-ignore no-raw-hooks Phase-3 conversion tracked in #2312
|
|
222
241
|
useEffect(() => {
|