@cosmicdrift/kumiko-bundled-features 0.388.0 → 0.390.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/dist/agent-tools/index.d.ts +1 -1
- package/dist/agent-tools/index.js +1 -1
- package/dist/agent-tools/tool-dispatch.d.ts +8 -3
- package/dist/agent-tools/tool-dispatch.js +36 -25
- package/dist/audit/feature.js +2 -1
- package/dist/auth-email-password/auth-user-row.js +1 -1
- package/dist/auth-email-password/bootstrap.js +6 -1
- package/dist/auth-email-password/feature.js +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +7 -7
- package/dist/auth-email-password/handlers/confirm-account-unlock.write.js +3 -3
- package/dist/auth-email-password/handlers/confirm-token-flow.js +4 -4
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +8 -7
- package/dist/auth-email-password/handlers/invite-accept.write.js +10 -7
- package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
- package/dist/auth-email-password/handlers/invite-info.query.js +2 -2
- package/dist/auth-email-password/handlers/invite-signup-complete.write.js +8 -7
- package/dist/auth-email-password/handlers/login.write.js +17 -15
- package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -0
- package/dist/auth-email-password/handlers/request-account-unlock.write.js +1 -0
- package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -0
- package/dist/auth-email-password/handlers/request-email-verification.write.js +1 -0
- package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -0
- package/dist/auth-email-password/handlers/request-password-reset.write.js +1 -0
- package/dist/auth-email-password/handlers/reset-password.write.js +2 -2
- package/dist/auth-email-password/handlers/signup-confirm.write.js +12 -12
- package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -0
- package/dist/auth-email-password/handlers/signup-request.write.js +16 -8
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.js +4 -2
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +2 -0
- package/dist/auth-email-password/handlers/token-request-handler.js +7 -2
- package/dist/auth-email-password/handlers/verify-email.write.js +2 -2
- package/dist/auth-email-password/magic-link-mail.d.ts +2 -0
- package/dist/auth-email-password/magic-link-mail.js +7 -3
- package/dist/auth-email-password/seeding.d.ts +1 -1
- package/dist/auth-email-password/seeding.js +9 -8
- package/dist/auth-email-password/signup-activation.d.ts +1 -0
- package/dist/auth-email-password/signup-activation.js +1 -0
- package/dist/auth-email-password/web/auth-client.d.ts +2 -2
- package/dist/auth-email-password/web/auth-client.js +8 -4
- package/dist/auth-email-password/web/auth-redirect.d.ts +5 -1
- package/dist/auth-email-password/web/auth-redirect.js +20 -25
- package/dist/auth-email-password/web/default-auth-pages-gate.js +6 -3
- package/dist/auth-email-password/web/forgot-password-screen.d.ts +4 -1
- package/dist/auth-email-password/web/forgot-password-screen.js +7 -2
- package/dist/auth-email-password/web/index.d.ts +1 -0
- package/dist/auth-email-password/web/index.js +1 -0
- package/dist/auth-email-password/web/login-screen.js +11 -6
- package/dist/auth-email-password/web/reset-password-screen.js +7 -1
- package/dist/auth-email-password/web/return-path.d.ts +4 -0
- package/dist/auth-email-password/web/return-path.js +34 -0
- package/dist/auth-email-password/web/session.js +6 -1
- package/dist/auth-email-password/web/signup-complete-screen.js +11 -3
- package/dist/auth-email-password/web/signup-screen.d.ts +4 -1
- package/dist/auth-email-password/web/signup-screen.js +8 -3
- package/dist/auth-foundation/feature.js +12 -4
- package/dist/auth-mfa/config.d.ts +1 -1
- package/dist/auth-mfa/config.js +3 -7
- package/dist/auth-mfa/handlers/disable.write.js +5 -5
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +19 -14
- package/dist/auth-mfa/handlers/enable-confirm.write.js +3 -3
- package/dist/auth-mfa/handlers/enable-start-preauth.write.js +6 -1
- package/dist/auth-mfa/handlers/enable-start.write.js +1 -1
- package/dist/auth-mfa/handlers/reencrypt.job.js +14 -9
- package/dist/auth-mfa/handlers/regenerate-recovery.write.js +4 -4
- package/dist/auth-mfa/handlers/verify.write.js +19 -14
- package/dist/auth-mfa/mfa-code-verifier.js +9 -4
- package/dist/auth-mfa/mfa-status-checker.js +13 -6
- package/dist/auth-mfa/verify-factor.js +1 -1
- package/dist/billing-foundation/billing-info-query.js +2 -2
- package/dist/billing-foundation/consumer-protection/consent-gate.js +1 -1
- package/dist/billing-foundation/consumer-protection/issue-confirmation.js +5 -5
- package/dist/billing-foundation/consumer-protection/register-consent.js +1 -1
- package/dist/billing-foundation/consumer-protection/register-termination.js +1 -1
- package/dist/billing-foundation/consumer-protection/termination-open-cancels.d.ts +4 -0
- package/dist/billing-foundation/consumer-protection/termination-open-cancels.js +14 -0
- package/dist/billing-foundation/consumer-protection/termination-provider-lock.d.ts +2 -0
- package/dist/billing-foundation/consumer-protection/termination-provider-lock.js +3 -0
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +4 -4
- package/dist/billing-foundation/consumer-protection/termination-record.js +11 -48
- package/dist/billing-foundation/consumer-protection/termination-request.js +10 -8
- package/dist/billing-foundation/consumer-protection/termination-revoke.js +10 -12
- package/dist/billing-foundation/events.js +1 -1
- package/dist/billing-foundation/feature.js +3 -3
- package/dist/billing-foundation/get-subscription-for-tenant.d.ts +1 -1
- package/dist/billing-foundation/handlers/process-event.write.js +5 -5
- package/dist/billing-foundation/handlers/process-payment-event.write.js +3 -3
- package/dist/billing-foundation/plan-catalog.js +2 -2
- package/dist/billing-foundation/projection.js +1 -1
- package/dist/billing-foundation/types.js +1 -1
- package/dist/cap-counter/aggregate-id.d.ts +1 -1
- package/dist/cap-counter/aggregate-id.js +1 -1
- package/dist/cap-counter/book-cap-usage.d.ts +2 -2
- package/dist/cap-counter/book-cap-usage.js +3 -3
- package/dist/cap-counter/cap-reservation.js +16 -7
- package/dist/cap-counter/constants.js +1 -1
- package/dist/cap-counter/feature.js +3 -2
- package/dist/cap-counter/handlers/increment-rolling.write.js +1 -1
- package/dist/cap-counter/reservation-chain.js +1 -1
- package/dist/cap-counter/with-cap-enforcement.d.ts +3 -3
- package/dist/cap-counter/with-cap-enforcement.js +1 -1
- package/dist/cap-overview/feature.js +1 -0
- package/dist/cap-overview/handlers/caps-usage.query.js +1 -1
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +1 -1
- package/dist/compliance-profiles/seeding.d.ts +1 -1
- package/dist/compliance-profiles/seeding.js +9 -3
- package/dist/config/feature.js +6 -6
- package/dist/config/handlers/cascade.query.js +1 -1
- package/dist/config/handlers/readiness.query.js +1 -1
- package/dist/config/handlers/reencrypt.job.js +23 -13
- package/dist/config/handlers/reset.write.js +3 -3
- package/dist/config/handlers/selected-extensions.query.js +1 -1
- package/dist/config/handlers/set.write.js +15 -5
- package/dist/config/handlers/values.query.js +1 -1
- package/dist/crypto-shredding/handlers/forget-subject.write.js +9 -9
- package/dist/crypto-shredding/resolve-file-bytes-deleter.js +3 -3
- package/dist/custom-fields/handlers/clear-custom-field.write.js +1 -1
- package/dist/custom-fields/handlers/delete-system-field.write.js +1 -1
- package/dist/custom-fields/handlers/delete-tenant-field.write.js +1 -1
- package/dist/custom-fields/handlers/set-custom-field.write.js +1 -1
- package/dist/custom-fields/index.d.ts +1 -0
- package/dist/custom-fields/index.js +1 -0
- package/dist/custom-fields/lib/parse-serialized-field.js +1 -1
- package/dist/data-retention/feature.js +1 -1
- package/dist/data-retention/run-retention-cleanup.js +12 -2
- package/dist/delivery/attempt-log.js +1 -1
- package/dist/delivery/delivery-service.js +5 -5
- package/dist/delivery/feature.js +3 -2
- package/dist/delivery/index.d.ts +1 -0
- package/dist/delivery/index.js +1 -0
- package/dist/delivery/jobs.js +11 -11
- package/dist/delivery/system-channel-context.d.ts +15 -0
- package/dist/delivery/system-channel-context.js +5 -0
- package/dist/document-ingest-foundation/append-missing-extract-event.d.ts +21 -0
- package/dist/document-ingest-foundation/append-missing-extract-event.js +42 -0
- package/dist/document-ingest-foundation/events.d.ts +11 -0
- package/dist/document-ingest-foundation/events.js +12 -1
- package/dist/document-ingest-foundation/index.d.ts +2 -1
- package/dist/document-ingest-foundation/index.js +2 -1
- package/dist/feature-toggles/feature.js +1 -0
- package/dist/feature-toggles/handlers/registered.query.js +1 -1
- package/dist/feature-toggles/handlers/set.write.js +1 -1
- package/dist/feature-toggles/toggle-sync-signal.js +1 -1
- package/dist/file-derivatives/feature.d.ts +1 -1
- package/dist/file-derivatives/feature.js +1 -1
- package/dist/file-derivatives/handlers/public-variant-by-file-ref.query.js +1 -1
- package/dist/file-derivatives/handlers/public-variant.query.js +5 -5
- package/dist/file-foundation/feature.js +1 -1
- package/dist/file-foundation/read-file-ref-content.d.ts +4 -2
- package/dist/file-foundation/read-file-ref-content.js +3 -3
- package/dist/file-provider-s3/feature.js +1 -1
- package/dist/files-tenant-data/handlers/sweep-orphaned-derivatives.job.js +8 -8
- package/dist/form-draft/feature.js +1 -1
- package/dist/form-draft/handlers/cleanup.job.js +22 -12
- package/dist/form-draft/handlers/discard.write.js +3 -3
- package/dist/form-draft/handlers/get.query.js +1 -1
- package/dist/foundation-shared/config-helpers.d.ts +1 -1
- package/dist/foundation-shared/config-helpers.js +1 -1
- package/dist/host-routing/validate-options.js +3 -0
- package/dist/inbound-mail-foundation/events.js +1 -1
- package/dist/inbound-mail-foundation/feature.js +2 -1
- package/dist/inbound-mail-foundation/handlers/account-state.d.ts +3 -1
- package/dist/inbound-mail-foundation/handlers/account-state.js +1 -1
- package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
- package/dist/inbound-mail-foundation/handlers/disconnect-account.write.js +1 -1
- package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +10 -10
- package/dist/inbound-mail-foundation/handlers/update-account.write.js +1 -1
- package/dist/inbound-mail-foundation/projection.js +1 -1
- package/dist/inbound-mail-foundation/watch-supervisor.js +1 -1
- package/dist/inbound-provider-imap/feature.js +1 -1
- package/dist/jobs/events.js +1 -1
- package/dist/jobs/feature.js +1 -0
- package/dist/jobs/handlers/consumer.write.js +1 -1
- package/dist/jobs/handlers/projection-rebuild.job.js +1 -1
- package/dist/jobs/handlers/reindex-entity.job.js +5 -5
- package/dist/jobs/handlers/retention-cleanup.job.js +1 -1
- package/dist/jobs/handlers/retry.write.js +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.js +1 -1
- package/dist/jobs/handlers/trigger.write.js +2 -2
- package/dist/jobs/job-run-logger.d.ts +1 -0
- package/dist/jobs/job-run-logger.js +38 -18
- package/dist/ledger/web/index.js +1 -1
- package/dist/legal-pages/lib/boot-check.d.ts +10 -4
- package/dist/legal-pages/lib/boot-check.js +2 -2
- package/dist/legal-pages/seed.d.ts +3 -1
- package/dist/legal-pages/seed.js +2 -2
- package/dist/mail-foundation/feature.d.ts +2 -2
- package/dist/mail-transport-smtp/config-keys.js +1 -1
- package/dist/mail-transport-smtp/feature.js +7 -6
- package/dist/managed-pages/constants.d.ts +1 -0
- package/dist/managed-pages/constants.js +1 -0
- package/dist/managed-pages/feature.js +3 -1
- package/dist/managed-pages/index.d.ts +1 -0
- package/dist/managed-pages/index.js +1 -0
- package/dist/managed-pages/seeding.d.ts +1 -1
- package/dist/managed-pages/seeding.js +10 -4
- package/dist/notes-history/handlers/add-note.write.js +2 -2
- package/dist/personal-access-tokens/feature.js +2 -1
- package/dist/personal-access-tokens/handlers/create.write.js +2 -2
- package/dist/personal-access-tokens/handlers/revoke.write.js +1 -1
- package/dist/rate-limiting/handlers/status.query.js +2 -2
- package/dist/readiness/handlers/status.query.js +1 -1
- package/dist/renderer-foundation/api.d.ts +4 -2
- package/dist/renderer-foundation/api.js +2 -2
- package/dist/secrets/announce-secret-change.d.ts +6 -2
- package/dist/secrets/announce-secret-change.js +3 -3
- package/dist/secrets/feature.d.ts +1 -1
- package/dist/secrets/feature.js +8 -8
- package/dist/secrets/handlers/delete.write.js +1 -1
- package/dist/secrets/handlers/rotate.job.js +13 -8
- package/dist/secrets/handlers/set.write.js +1 -1
- package/dist/secrets/secrets-context.js +16 -6
- package/dist/secrets/write-gate.js +8 -2
- package/dist/seo/feature.js +1 -1
- package/dist/sessions/feature.js +2 -1
- package/dist/sessions/handlers/cleanup.job.js +2 -2
- package/dist/sessions/handlers/detail.query.d.ts +1 -0
- package/dist/sessions/handlers/detail.query.js +5 -2
- package/dist/sessions/handlers/list.query.d.ts +1 -0
- package/dist/sessions/handlers/list.query.js +5 -2
- package/dist/sessions/handlers/mine.query.d.ts +1 -0
- package/dist/sessions/handlers/mine.query.js +5 -2
- package/dist/sessions/handlers/revoke-all-for-user.write.js +3 -3
- package/dist/sessions/handlers/revoke-all-others.write.js +1 -1
- package/dist/sessions/handlers/revoke.write.js +1 -1
- package/dist/sessions/index.d.ts +1 -1
- package/dist/sessions/index.js +1 -1
- package/dist/sessions/screens.js +8 -4
- package/dist/sessions/session-callbacks.d.ts +0 -2
- package/dist/sessions/session-callbacks.js +1 -3
- package/dist/sessions/session-revoked-event.js +1 -1
- package/dist/sessions/web/session-device-cell.js +10 -12
- package/dist/sessions/web/user-agent.d.ts +1 -0
- package/dist/sessions/web/user-agent.js +11 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +2 -0
- package/dist/shared/purge-files-reaching-subject.d.ts +1 -0
- package/dist/shared/purge-files-reaching-subject.js +31 -4
- package/dist/shared/require-for-tenant.d.ts +1 -1
- package/dist/shared/require-for-tenant.js +6 -4
- package/dist/shared/run-in-sub-transaction.js +5 -5
- package/dist/shared/stream-tenant-backfill.d.ts +28 -0
- package/dist/shared/stream-tenant-backfill.js +119 -0
- package/dist/shared/stream-tenant-derived-state.d.ts +25 -0
- package/dist/shared/stream-tenant-derived-state.js +68 -0
- package/dist/step-dispatcher/deliver-dispatch-request.js +16 -3
- package/dist/step-dispatcher/feature.js +1 -1
- package/dist/step-dispatcher/mail-runner.d.ts +1 -0
- package/dist/subscription-dodo/plugin-methods.js +1 -1
- package/dist/subscription-dodo/runtime.js +4 -4
- package/dist/subscription-stripe/plugin-methods.js +3 -3
- package/dist/subscription-stripe/runtime.js +4 -4
- package/dist/tags/feature.js +1 -0
- package/dist/template-resolver/api.d.ts +4 -2
- package/dist/template-resolver/api.js +2 -2
- package/dist/template-resolver/document-ops.d.ts +5 -0
- package/dist/template-resolver/document-ops.js +18 -2
- package/dist/template-resolver/feature.js +2 -2
- package/dist/template-resolver/handlers/bundle-import.d.ts +4 -3
- package/dist/template-resolver/handlers/export-bundle.query.js +1 -1
- package/dist/template-resolver/handlers/layout-source.query.d.ts +4 -0
- package/dist/template-resolver/handlers/layout-source.query.js +26 -6
- package/dist/template-resolver/handlers/overview.query.js +17 -9
- package/dist/template-resolver/handlers/patch-draft.write.js +1 -1
- package/dist/template-resolver/handlers/send-test-shared.js +1 -1
- package/dist/template-resolver/handlers/send-test-status.query.d.ts +2 -1
- package/dist/template-resolver/handlers/send-test-status.query.js +18 -15
- package/dist/template-resolver/handlers/send-test.write.js +2 -2
- package/dist/template-resolver/handlers/tenant-override-selection.d.ts +3 -1
- package/dist/template-resolver/handlers/tenant-override-selection.js +1 -1
- package/dist/template-resolver/handlers/tenant-overrides.query.js +1 -1
- package/dist/template-resolver/handlers/usage.query.js +1 -1
- package/dist/template-resolver/seeding.d.ts +1 -0
- package/dist/template-resolver/seeding.js +17 -4
- package/dist/template-resolver/store-template-resource.d.ts +7 -6
- package/dist/template-resolver/store-template-resource.js +48 -8
- package/dist/template-resolver/template-resources.d.ts +2 -2
- package/dist/template-resolver/template-resources.js +2 -2
- package/dist/template-resolver/testing.js +2 -2
- package/dist/template-resolver/web/document-insert-fields.d.ts +4 -0
- package/dist/template-resolver/web/document-insert-fields.js +69 -0
- package/dist/template-resolver/web/document-insert-model.d.ts +43 -0
- package/dist/template-resolver/web/document-insert-model.js +106 -0
- package/dist/template-resolver/web/document-insert-panel.d.ts +13 -0
- package/dist/template-resolver/web/document-insert-panel.js +49 -0
- package/dist/template-resolver/web/document-tiptap-bubble.d.ts +4 -0
- package/dist/template-resolver/web/document-tiptap-bubble.js +85 -0
- package/dist/template-resolver/web/document-tiptap-content.d.ts +3 -0
- package/dist/template-resolver/web/document-tiptap-content.js +12 -0
- package/dist/template-resolver/web/document-tiptap-context.d.ts +41 -0
- package/dist/template-resolver/web/document-tiptap-context.js +43 -0
- package/dist/template-resolver/web/document-tiptap-editor.js +50 -495
- package/dist/template-resolver/web/document-tiptap-helpers.d.ts +44 -0
- package/dist/template-resolver/web/document-tiptap-helpers.js +129 -0
- package/dist/template-resolver/web/document-tiptap-layout.d.ts +4 -0
- package/dist/template-resolver/web/document-tiptap-layout.js +24 -0
- package/dist/template-resolver/web/document-tiptap-toolbar-groups.d.ts +6 -0
- package/dist/template-resolver/web/document-tiptap-toolbar-groups.js +33 -0
- package/dist/template-resolver/web/document-tiptap-toolbar.d.ts +3 -0
- package/dist/template-resolver/web/document-tiptap-toolbar.js +91 -0
- package/dist/template-resolver/web/editor-handle.d.ts +1 -0
- package/dist/template-resolver/web/editor-handle.js +15 -0
- package/dist/template-resolver/web/i18n.js +3 -0
- package/dist/template-resolver/web/placeholder-line-editor.js +16 -5
- package/dist/template-resolver/web/qualified-names.d.ts +1 -0
- package/dist/template-resolver/web/qualified-names.js +1 -0
- package/dist/template-resolver/web/use-document-tiptap-model.d.ts +54 -0
- package/dist/template-resolver/web/use-document-tiptap-model.js +96 -0
- package/dist/tenant/feature.js +1 -0
- package/dist/tenant/handlers/cancel-invitation.write.js +1 -1
- package/dist/tenant/handlers/memberships.query.js +1 -1
- package/dist/tenant/handlers/remove-member.write.js +2 -2
- package/dist/tenant/handlers/team-list.query.js +3 -3
- package/dist/tenant/handlers/update-member-roles.write.js +1 -1
- package/dist/tenant/index.d.ts +1 -0
- package/dist/tenant/index.js +1 -0
- package/dist/tenant/invited-membership.js +6 -1
- package/dist/tenant/schema/tenant.d.ts +2 -2
- package/dist/tenant/schema/tenant.js +4 -1
- package/dist/tenant/seeding.d.ts +2 -2
- package/dist/tenant/seeding.js +52 -52
- package/dist/tenant/stream-tenant-backfill.d.ts +21 -0
- package/dist/tenant/stream-tenant-backfill.js +63 -0
- package/dist/tenant/testing.d.ts +12 -0
- package/dist/tenant/testing.js +33 -0
- package/dist/tenant-handover/events.js +1 -1
- package/dist/tenant-handover/handlers/claim.write.js +1 -1
- package/dist/tenant-lifecycle/feature.js +5 -5
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +14 -5
- package/dist/tenant-lifecycle/handlers/request-destruction.write.js +12 -4
- package/dist/tenant-lifecycle/run-tenant-destroy.js +23 -13
- package/dist/tenant-lifecycle/stages.js +28 -2
- package/dist/tenant-lifecycle/testing.d.ts +1 -1
- package/dist/tenant-lifecycle/testing.js +6 -5
- package/dist/tier-engine/entity.js +1 -1
- package/dist/tier-engine/feature.js +8 -3
- package/dist/tier-engine/handlers/active-tier.query.js +3 -3
- package/dist/tier-engine/handlers/get-tenant-tier.query.js +6 -1
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +4 -4
- package/dist/user/db/queries/stream-tenant-backfill.d.ts +20 -1
- package/dist/user/db/queries/stream-tenant-backfill.js +40 -77
- package/dist/user/feature.js +1 -0
- package/dist/user/handlers/find-for-auth.query.js +2 -2
- package/dist/user/handlers/update-roles.js +2 -2
- package/dist/user/index.d.ts +1 -1
- package/dist/user/index.js +1 -1
- package/dist/user/invited-global-roles.js +6 -1
- package/dist/user/principal-status.js +1 -1
- package/dist/user/seeding.d.ts +1 -1
- package/dist/user/seeding.js +20 -5
- package/dist/user/testing.d.ts +3 -0
- package/dist/user/testing.js +10 -0
- package/dist/user-data-rights/audit-download.js +12 -2
- package/dist/user-data-rights/feature.js +30 -26
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +1 -1
- package/dist/user-data-rights/handlers/deletion-grace-period.js +1 -1
- package/dist/user-data-rights/handlers/download-by-job.query.js +2 -2
- package/dist/user-data-rights/handlers/download-by-token.query.js +2 -2
- package/dist/user-data-rights/handlers/request-deletion.write.js +1 -1
- package/dist/user-data-rights/handlers/restrict-account.write.js +4 -4
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.js +8 -6
- package/dist/user-data-rights/index.d.ts +1 -1
- package/dist/user-data-rights/index.js +1 -1
- package/dist/user-data-rights/lib/mail-transport-resolver.js +1 -1
- package/dist/user-data-rights/lib/storage-provider-resolver.js +1 -1
- package/dist/user-data-rights/lib/update-user-lifecycle.js +6 -1
- package/dist/user-data-rights/run-export-jobs.js +7 -2
- package/dist/user-data-rights/run-forget-cleanup.d.ts +1 -0
- package/dist/user-data-rights/run-forget-cleanup.js +9 -1
- package/dist/user-data-rights/run-user-export.js +7 -1
- package/dist/user-data-rights/web/constants.d.ts +14 -0
- package/dist/user-data-rights/web/constants.js +14 -0
- package/dist/user-data-rights/web/index.d.ts +1 -0
- package/dist/user-data-rights/web/index.js +1 -0
- package/dist/user-profile/handlers/change-email.write.js +8 -6
- package/dist/waitlist/feature.js +1 -0
- package/dist/waitlist/handlers/export-purge.write.js +1 -1
- package/dist/waitlist/handlers/invite.write.js +2 -2
- package/dist/waitlist/handlers/reject.write.js +1 -1
- package/dist/waitlist/handlers/submit.write.js +20 -11
- package/dist/waitlist/import.d.ts +2 -1
- package/dist/waitlist/import.js +22 -2
- package/dist/waitlist/legacy-migrate.js +8 -3
- package/dist/waitlist/link-on-signup.js +6 -1
- package/dist/waitlist/options.d.ts +2 -1
- package/dist/workflow-runner/db/queries/due-runs.js +1 -1
- package/dist/workflow-runner/event-subscriber.js +4 -3
- package/dist/workflow-runner/event-trigger.js +2 -1
- package/dist/workflow-runner/feature.js +2 -2
- package/dist/workflow-runner/handlers/resume-run.write.js +6 -6
- package/dist/workflow-runner/runner.d.ts +3 -1
- package/dist/workflow-runner/runner.js +3 -3
- package/package.json +41 -23
- package/src/agent-tools/changes.json +6 -0
- package/src/auth-email-password/changes.json +18 -0
- package/src/auth-mfa/changes.json +6 -0
- package/src/billing-foundation/changes.json +6 -0
- package/src/cap-counter/changes.json +12 -0
- package/src/config/changes.json +6 -0
- package/src/crypto-shredding/changes.json +12 -0
- package/src/custom-fields/changes.json +6 -0
- package/src/delivery/changes.json +12 -0
- package/src/document-ingest-foundation/changes.json +6 -0
- package/src/host-routing/changes.json +6 -0
- package/src/jobs/changes.json +6 -0
- package/src/mail-transport-smtp/changes.json +12 -0
- package/src/managed-pages/changes.json +6 -0
- package/src/secrets/changes.json +6 -0
- package/src/sessions/changes.json +19 -0
- package/src/step-dispatcher/changes.json +6 -0
- package/src/template-resolver/changes.json +54 -0
- package/src/tenant/changes.json +19 -0
- package/src/tenant-lifecycle/changes.json +6 -0
- package/src/user-data-rights/changes.json +36 -0
- package/src/waitlist/changes.json +18 -0
- package/src/workflow-runner/changes.json +6 -0
|
@@ -34,12 +34,12 @@ function unknownEmailLockoutSubject(ctx, email) {
|
|
|
34
34
|
}
|
|
35
35
|
export async function gateCountUnknownUser(ctx, email, maxFailedAttempts, lockoutDurationMinutes) {
|
|
36
36
|
const subject = unknownEmailLockoutSubject(ctx, email);
|
|
37
|
-
if (!ctx.redis || subject === undefined)
|
|
37
|
+
if (!ctx.services.redis || subject === undefined)
|
|
38
38
|
return ok(undefined);
|
|
39
39
|
const locked = await gateEnforceLockout(ctx, subject);
|
|
40
40
|
if (!locked.ok)
|
|
41
41
|
return locked;
|
|
42
|
-
await recordFailedAttempt(ctx.redis, subject, maxFailedAttempts, lockoutDurationMinutes);
|
|
42
|
+
await recordFailedAttempt(ctx.services.redis, subject, maxFailedAttempts, lockoutDurationMinutes);
|
|
43
43
|
return ok(undefined);
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
@@ -47,9 +47,9 @@ export async function gateCountUnknownUser(ctx, email, maxFailedAttempts, lockou
|
|
|
47
47
|
* Fail-open without Redis (IP rate-limit still covers partially).
|
|
48
48
|
*/
|
|
49
49
|
export async function gateEnforceLockout(ctx, userId) {
|
|
50
|
-
if (!ctx.redis)
|
|
50
|
+
if (!ctx.services.redis)
|
|
51
51
|
return ok(undefined);
|
|
52
|
-
const state = await getLockoutState(ctx.redis, userId);
|
|
52
|
+
const state = await getLockoutState(ctx.services.redis, userId);
|
|
53
53
|
if (state?.lockedUntil !== null && state?.lockedUntil !== undefined) {
|
|
54
54
|
const now = Date.now();
|
|
55
55
|
if (state.lockedUntil > now) {
|
|
@@ -63,15 +63,15 @@ export async function gateEnforceLockout(ctx, userId) {
|
|
|
63
63
|
export async function gateVerifyPassword(ctx, found, password, maxFailedAttempts, lockoutDurationMinutes) {
|
|
64
64
|
const passwordOk = await verifyPassword(found.passwordHash, password);
|
|
65
65
|
if (!passwordOk) {
|
|
66
|
-
if (ctx.redis) {
|
|
67
|
-
await recordFailedAttempt(ctx.redis, found.id, maxFailedAttempts, lockoutDurationMinutes);
|
|
66
|
+
if (ctx.services.redis) {
|
|
67
|
+
await recordFailedAttempt(ctx.services.redis, found.id, maxFailedAttempts, lockoutDurationMinutes);
|
|
68
68
|
}
|
|
69
69
|
return reject(invalidCredentials());
|
|
70
70
|
}
|
|
71
71
|
// Clear before MFA — MFA has its own attempt-cap; don't password-lock
|
|
72
72
|
// users who occasionally mistype across otherwise-successful logins.
|
|
73
|
-
if (ctx.redis) {
|
|
74
|
-
await clearLockoutState(ctx.redis, found.id);
|
|
73
|
+
if (ctx.services.redis) {
|
|
74
|
+
await clearLockoutState(ctx.services.redis, found.id);
|
|
75
75
|
}
|
|
76
76
|
return ok(undefined);
|
|
77
77
|
}
|
|
@@ -105,7 +105,7 @@ export async function gateResolveMembership(ctx, found, memberships) {
|
|
|
105
105
|
: memberships;
|
|
106
106
|
const globalRoles = parseRoles(found.roles ?? null);
|
|
107
107
|
for (const candidate of candidates) {
|
|
108
|
-
const active = await ctx.resolveActiveMembership(found.id, candidate.tenantId);
|
|
108
|
+
const active = await ctx.auth.resolveActiveMembership(found.id, candidate.tenantId);
|
|
109
109
|
if (active.kind === "rejected") {
|
|
110
110
|
// A blocked principal is blocked for every tenant, so stop here;
|
|
111
111
|
// not_a_member/tenant_teardown are per-tenant — try the next candidate.
|
|
@@ -149,14 +149,14 @@ export async function gateBuildSession(ctx, userId, tenantId, mergedRoles, timez
|
|
|
149
149
|
...(timezone !== null && timezone !== undefined && { timezone }),
|
|
150
150
|
...(locale !== null && locale !== undefined && { locale }),
|
|
151
151
|
};
|
|
152
|
-
const claims = await ctx.
|
|
152
|
+
const claims = await ctx.auth.resolveClaims(baseSession);
|
|
153
153
|
const session = Object.keys(claims).length > 0 ? { ...baseSession, claims } : baseSession;
|
|
154
154
|
return { kind: "auth-session", session };
|
|
155
155
|
}
|
|
156
156
|
// Login — unauthenticated entry point. The route is wired public (no JWT
|
|
157
157
|
// middleware), dispatching with the framework's anonymous identity for the
|
|
158
158
|
// handler's access check. Everything inside the handler goes through
|
|
159
|
-
// ctx.queryAs(system, ...) so the user feature stays the single owner of its
|
|
159
|
+
// ctx.dispatch.queryAs(system, ...) so the user feature stays the single owner of its
|
|
160
160
|
// table.
|
|
161
161
|
export function createLoginHandler(opts = {}) {
|
|
162
162
|
const strictVerification = opts.strictEmailVerification === true;
|
|
@@ -173,8 +173,8 @@ export function createLoginHandler(opts = {}) {
|
|
|
173
173
|
escapeHatch: {
|
|
174
174
|
grants: ["systemIdentity", "unsafeRaw"],
|
|
175
175
|
reason: "Unauthenticated login has no caller identity yet — it looks up the user row by " +
|
|
176
|
-
"email via ctx.queryAs(SYSTEM, ...) and resolves tenant membership via " +
|
|
177
|
-
"ctx.resolveActiveMembership before a session exists. It also reads the MFA " +
|
|
176
|
+
"email via ctx.dispatch.queryAs(SYSTEM, ...) and resolves tenant membership via " +
|
|
177
|
+
"ctx.auth.resolveActiveMembership before a session exists. It also reads the MFA " +
|
|
178
178
|
"enrollment of the tenant named in the signed login/setup token, not the guest " +
|
|
179
179
|
"dispatch tenant, via the mfaStatusChecker callback.",
|
|
180
180
|
},
|
|
@@ -183,7 +183,9 @@ export function createLoginHandler(opts = {}) {
|
|
|
183
183
|
description: "Signs a user in with email and password, running the lockout, email-verification, account-status, tenant-membership and MFA gates, and answering with a session or with an MFA challenge or setup requirement.",
|
|
184
184
|
handler: async (event, ctx) => {
|
|
185
185
|
const systemUser = createSystemUser(SYSTEM_USER_ID);
|
|
186
|
-
const foundRow = parseAuthUserRow(await ctx.queryAs(systemUser, UserQueries.findForAuth, {
|
|
186
|
+
const foundRow = parseAuthUserRow(await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
|
|
187
|
+
email: event.payload.email,
|
|
188
|
+
}));
|
|
187
189
|
if (!foundRow?.passwordHash || foundRow.isDeleted) {
|
|
188
190
|
const unknown = await gateCountUnknownUser(ctx, event.payload.email, maxFailedAttempts, lockoutDurationMinutes);
|
|
189
191
|
if (!unknown.ok)
|
|
@@ -206,7 +208,7 @@ export function createLoginHandler(opts = {}) {
|
|
|
206
208
|
if (!statusGate.ok)
|
|
207
209
|
return statusGate.result;
|
|
208
210
|
// Still needed for candidate ORDER (preferred tenant first) in gateResolveMembership.
|
|
209
|
-
const memberships = (await ctx.queryAs(systemUser, TENANT_MEMBERSHIPS_QUERY, {
|
|
211
|
+
const memberships = (await ctx.dispatch.queryAs(systemUser, TENANT_MEMBERSHIPS_QUERY, {
|
|
210
212
|
userId: found.id,
|
|
211
213
|
})); // @cast-boundary db-runner
|
|
212
214
|
const membershipGate = await gateResolveMembership(ctx, found, memberships);
|
|
@@ -3,4 +3,5 @@ export type RequestAccountUnlockOptions = TokenRequestOptions;
|
|
|
3
3
|
export type RequestUnlockData = TokenRequestData<"unlock-requested">;
|
|
4
4
|
export declare function createRequestAccountUnlockHandler(opts: RequestAccountUnlockOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"request-account-unlock", import("zod").ZodObject<{
|
|
5
5
|
email: import("zod").ZodEmail;
|
|
6
|
+
returnTo: import("zod").ZodCatch<import("zod").ZodOptional<import("zod").ZodString>>;
|
|
6
7
|
}, import("zod/v4/core").$strip>, TokenRequestData<"unlock-requested">, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -18,5 +18,6 @@ export function createRequestAccountUnlockHandler(opts) {
|
|
|
18
18
|
// empty) lockout state — harmless no-op if they weren't locked.
|
|
19
19
|
extraSilentSkip: () => false,
|
|
20
20
|
notificationType: AuthMailNotificationTypes.accountUnlock,
|
|
21
|
+
forwardsReturnTo: false,
|
|
21
22
|
}, opts);
|
|
22
23
|
}
|
|
@@ -3,4 +3,5 @@ export type RequestEmailVerificationOptions = TokenRequestOptions;
|
|
|
3
3
|
export type RequestVerificationData = TokenRequestData<"verification-requested">;
|
|
4
4
|
export declare function createRequestEmailVerificationHandler(opts: RequestEmailVerificationOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"request-email-verification", import("zod").ZodObject<{
|
|
5
5
|
email: import("zod").ZodEmail;
|
|
6
|
+
returnTo: import("zod").ZodCatch<import("zod").ZodOptional<import("zod").ZodString>>;
|
|
6
7
|
}, import("zod/v4/core").$strip>, TokenRequestData<"verification-requested">, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -17,5 +17,6 @@ export function createRequestEmailVerificationHandler(opts) {
|
|
|
17
17
|
// caller sees the same 200 regardless of whether a token was minted.
|
|
18
18
|
extraSilentSkip: (user) => user.emailVerified === true,
|
|
19
19
|
notificationType: AuthMailNotificationTypes.emailVerification,
|
|
20
|
+
forwardsReturnTo: false,
|
|
20
21
|
}, opts);
|
|
21
22
|
}
|
|
@@ -3,4 +3,5 @@ export type RequestPasswordResetOptions = TokenRequestOptions;
|
|
|
3
3
|
export type RequestResetData = TokenRequestData<"reset-requested">;
|
|
4
4
|
export declare function createRequestPasswordResetHandler(opts: RequestPasswordResetOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"request-password-reset", import("zod").ZodObject<{
|
|
5
5
|
email: import("zod").ZodEmail;
|
|
6
|
+
returnTo: import("zod").ZodCatch<import("zod").ZodOptional<import("zod").ZodString>>;
|
|
6
7
|
}, import("zod/v4/core").$strip>, TokenRequestData<"reset-requested">, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -17,5 +17,6 @@ export function createRequestPasswordResetHandler(opts) {
|
|
|
17
17
|
// state. The sessions feature handles the post-change revocation.
|
|
18
18
|
extraSilentSkip: () => false,
|
|
19
19
|
notificationType: AuthMailNotificationTypes.passwordReset,
|
|
20
|
+
forwardsReturnTo: true,
|
|
20
21
|
}, opts);
|
|
21
22
|
}
|
|
@@ -25,7 +25,7 @@ export function createResetPasswordHandler(opts) {
|
|
|
25
25
|
escapeHatch: {
|
|
26
26
|
grants: ["systemIdentity"],
|
|
27
27
|
reason: "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
|
|
28
|
-
"target user via ctx.writeAs(SYSTEM, user:update) to set the new password hash.",
|
|
28
|
+
"target user via ctx.dispatch.writeAs(SYSTEM, user:update) to set the new password hash.",
|
|
29
29
|
},
|
|
30
30
|
agent: { expose: false },
|
|
31
31
|
handler: async (event, ctx) => {
|
|
@@ -42,7 +42,7 @@ export function createResetPasswordHandler(opts) {
|
|
|
42
42
|
return invalidResetToken();
|
|
43
43
|
return runConfirmTokenFlow(ctx, verify.userId, verify.expiresAtMs, {
|
|
44
44
|
purpose: "reset",
|
|
45
|
-
redisRequiredMessage: "password-reset requires ctx.redis to enforce token single-use",
|
|
45
|
+
redisRequiredMessage: "password-reset requires ctx.services.redis to enforce token single-use",
|
|
46
46
|
invalidToken: invalidResetToken,
|
|
47
47
|
buildChanges: async () => ({
|
|
48
48
|
passwordHash: await hashPassword(event.payload.newPassword),
|
|
@@ -55,16 +55,16 @@ function isBenignClaimRejection(error) {
|
|
|
55
55
|
async function claimBoundHandover(ctx, session, binding) {
|
|
56
56
|
const provider = findSignupHandoverProvider(ctx.registry);
|
|
57
57
|
if (!provider) {
|
|
58
|
-
ctx.log?.warn("signup-confirm: handover binding present but no signupHandover provider mounted");
|
|
58
|
+
ctx.obs.log?.warn("signup-confirm: handover binding present but no signupHandover provider mounted");
|
|
59
59
|
return undefined;
|
|
60
60
|
}
|
|
61
61
|
const claim = provider.mintClaim(binding);
|
|
62
|
-
const claimResult = await ctx.writeAs(session, claim.qualifiedName, claim.payload);
|
|
62
|
+
const claimResult = await ctx.dispatch.writeAs(session, claim.qualifiedName, claim.payload);
|
|
63
63
|
if (claimResult.isSuccess)
|
|
64
64
|
return { entityType: binding.entityType, id: binding.rowId };
|
|
65
65
|
if (!isBenignClaimRejection(claimResult.error))
|
|
66
66
|
throw reraiseAsKumikoError(claimResult.error);
|
|
67
|
-
ctx.log?.warn("signup-confirm: handover grant did not redeem", {
|
|
67
|
+
ctx.obs.log?.warn("signup-confirm: handover grant did not redeem", {
|
|
68
68
|
entityType: binding.entityType,
|
|
69
69
|
});
|
|
70
70
|
return undefined;
|
|
@@ -81,19 +81,19 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
81
81
|
reason: SIGNUP_CONFIRM_PROVISION_REASON,
|
|
82
82
|
},
|
|
83
83
|
handler: async (event, ctx) => {
|
|
84
|
-
if (!ctx.redis) {
|
|
84
|
+
if (!ctx.services.redis) {
|
|
85
85
|
return writeFailure(new InternalError({
|
|
86
|
-
message: "signup-confirm requires ctx.redis for token consumption",
|
|
86
|
+
message: "signup-confirm requires ctx.services.redis for token consumption",
|
|
87
87
|
}));
|
|
88
88
|
}
|
|
89
89
|
// Token lookup: nonexistent, expired, and already-consumed tokens all
|
|
90
90
|
// collapse to invalid_signup_token to prevent enumeration.
|
|
91
|
-
const email = await getEmailForSignupToken(ctx.redis, event.payload.token);
|
|
91
|
+
const email = await getEmailForSignupToken(ctx.services.redis, event.payload.token);
|
|
92
92
|
if (!email)
|
|
93
93
|
return invalidSignupToken();
|
|
94
94
|
// Single-use burn: of two parallel confirmations from different tabs,
|
|
95
95
|
// one succeeds and the other receives invalid_signup_token.
|
|
96
|
-
const burn = await burnSignupToken(ctx.redis, event.payload.token);
|
|
96
|
+
const burn = await burnSignupToken(ctx.services.redis, event.payload.token);
|
|
97
97
|
if (burn === "already-used")
|
|
98
98
|
return invalidSignupToken();
|
|
99
99
|
let committed = false;
|
|
@@ -154,7 +154,7 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
154
154
|
// Claim BEFORE spending the signup token or the binding below and
|
|
155
155
|
// before `committed` flips: if the claim throws, the `finally` still
|
|
156
156
|
// unburns and the activation link stays retryable.
|
|
157
|
-
const handoverBinding = await getSignupHandover(ctx.redis, event.payload.token);
|
|
157
|
+
const handoverBinding = await getSignupHandover(ctx.services.redis, event.payload.token);
|
|
158
158
|
const handover = handoverBinding
|
|
159
159
|
? await claimBoundHandover(ctx, session, handoverBinding)
|
|
160
160
|
: undefined;
|
|
@@ -162,9 +162,9 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
162
162
|
const mfaGate = await gateEnforceMfa(ctx, { mfaStatusChecker: opts.mfaStatusChecker }, provisioned.userId, provisioned.tenantId, session.roles);
|
|
163
163
|
// Drop both token lookup keys; the burn key stays for its remaining
|
|
164
164
|
// TTL as replay protection.
|
|
165
|
-
await deleteSignupToken(ctx.redis, { email, token: event.payload.token });
|
|
165
|
+
await deleteSignupToken(ctx.services.redis, { email, token: event.payload.token });
|
|
166
166
|
if (handoverBinding)
|
|
167
|
-
await deleteSignupHandover(ctx.redis, event.payload.token);
|
|
167
|
+
await deleteSignupHandover(ctx.services.redis, event.payload.token);
|
|
168
168
|
if (mfaGate !== undefined) {
|
|
169
169
|
committed = true;
|
|
170
170
|
return {
|
|
@@ -192,10 +192,10 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
192
192
|
};
|
|
193
193
|
}
|
|
194
194
|
finally {
|
|
195
|
-
if (!committed && ctx.redis) {
|
|
195
|
+
if (!committed && ctx.services.redis) {
|
|
196
196
|
// Release the burn so a retry after a DB hiccup is not blocked.
|
|
197
197
|
// Keep the token lookup keys so the user can click the email link again.
|
|
198
|
-
await unburnSignupToken(ctx.redis, event.payload.token);
|
|
198
|
+
await unburnSignupToken(ctx.services.redis, event.payload.token);
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
},
|
|
@@ -29,4 +29,5 @@ export declare function createSignupRequestHandler(opts: SignupRequestOptions):
|
|
|
29
29
|
entityType: z.ZodString;
|
|
30
30
|
token: z.ZodString;
|
|
31
31
|
}, z.core.$strip>>;
|
|
32
|
+
returnTo: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
32
33
|
}, z.core.$strip>, SignupRequestData, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -29,6 +29,7 @@ import { resolveHandlerMailLocale } from "../magic-link-mail.js";
|
|
|
29
29
|
import { AUTH_SELF_REGISTRATION_FEATURE } from "../self-registration-toggle.js";
|
|
30
30
|
import { issueSignupActivation } from "../signup-activation.js";
|
|
31
31
|
import { storeSignupHandover, takeOverSignupHandoverForResend } from "../signup-token-store.js";
|
|
32
|
+
import { MAX_RETURN_PATH_LENGTH } from "../web/return-path.js";
|
|
32
33
|
const SignupRequestSchema = z.object({
|
|
33
34
|
email: z.email(),
|
|
34
35
|
// Cross-device try-first handover: see shared/signup-handover.ts for why the
|
|
@@ -40,6 +41,8 @@ const SignupRequestSchema = z.object({
|
|
|
40
41
|
token: z.string().min(1),
|
|
41
42
|
})
|
|
42
43
|
.optional(),
|
|
44
|
+
// A bad returnTo must not void the signup; the mail builder drops unsafe values.
|
|
45
|
+
returnTo: z.string().max(MAX_RETURN_PATH_LENGTH).optional().catch(undefined),
|
|
43
46
|
});
|
|
44
47
|
const HANDOVER_GRANT_VERIFY_REASON = "verifies a tenant-handover grant against its root row in the source tenant (read-only anchor lookup) before binding it to the signup token";
|
|
45
48
|
export function createSignupRequestHandler(opts) {
|
|
@@ -70,12 +73,12 @@ export function createSignupRequestHandler(opts) {
|
|
|
70
73
|
// No email address here — this fires on every no-op request, PII
|
|
71
74
|
// must not accumulate in logs just for the "was this toggled off"
|
|
72
75
|
// signal a support ticket needs.
|
|
73
|
-
ctx.log?.info("signup-request skipped: self-registration disabled");
|
|
76
|
+
ctx.obs.log?.info("signup-request skipped: self-registration disabled");
|
|
74
77
|
return { isSuccess: true, data: { kind: "no-op" } };
|
|
75
78
|
}
|
|
76
|
-
if (!ctx.redis) {
|
|
79
|
+
if (!ctx.services.redis) {
|
|
77
80
|
return writeFailure(new InternalError({
|
|
78
|
-
message: "signup-request requires ctx.redis for the activation-token store",
|
|
81
|
+
message: "signup-request requires ctx.services.redis for the activation-token store",
|
|
79
82
|
}));
|
|
80
83
|
}
|
|
81
84
|
// Email normalization lives in the store (signup-token-store). The
|
|
@@ -86,15 +89,16 @@ export function createSignupRequestHandler(opts) {
|
|
|
86
89
|
// resend without a fresh `handover` in the payload (the common case:
|
|
87
90
|
// the visitor just retyped their email) must still carry the earlier
|
|
88
91
|
// verified grant over to the new token instead of silently dropping it.
|
|
89
|
-
const priorBinding = await takeOverSignupHandoverForResend(ctx.redis, email);
|
|
92
|
+
const priorBinding = await takeOverSignupHandoverForResend(ctx.services.redis, email);
|
|
90
93
|
const locale = resolveHandlerMailLocale(ctx, opts.locale);
|
|
91
|
-
const redis = ctx.redis;
|
|
94
|
+
const redis = ctx.services.redis;
|
|
92
95
|
const issued = await issueSignupActivation({
|
|
93
96
|
redis,
|
|
94
97
|
notify: ctx.notify,
|
|
95
98
|
email,
|
|
96
99
|
appUrl: opts.appUrl,
|
|
97
100
|
timeZone: ctx.tz.user,
|
|
101
|
+
...(event.payload.returnTo !== undefined && { returnTo: event.payload.returnTo }),
|
|
98
102
|
...(opts.tokenTtlMinutes !== undefined && { tokenTtlMinutes: opts.tokenTtlMinutes }),
|
|
99
103
|
...(opts.appName !== undefined && { appName: opts.appName }),
|
|
100
104
|
...(locale !== undefined && { locale }),
|
|
@@ -111,7 +115,7 @@ export function createSignupRequestHandler(opts) {
|
|
|
111
115
|
const { entityType, token: grantToken } = event.payload.handover;
|
|
112
116
|
const provider = findSignupHandoverProvider(ctx.registry);
|
|
113
117
|
if (!provider) {
|
|
114
|
-
ctx.log?.warn("signup-request: handover payload present but no signupHandover provider mounted");
|
|
118
|
+
ctx.obs.log?.warn("signup-request: handover payload present but no signupHandover provider mounted");
|
|
115
119
|
}
|
|
116
120
|
else {
|
|
117
121
|
try {
|
|
@@ -125,11 +129,15 @@ export function createSignupRequestHandler(opts) {
|
|
|
125
129
|
binding = verified;
|
|
126
130
|
}
|
|
127
131
|
else {
|
|
128
|
-
ctx.log?.warn("signup-request: handover grant did not verify", {
|
|
132
|
+
ctx.obs.log?.warn("signup-request: handover grant did not verify", {
|
|
133
|
+
entityType,
|
|
134
|
+
});
|
|
129
135
|
}
|
|
130
136
|
}
|
|
131
137
|
catch {
|
|
132
|
-
ctx.log?.warn("signup-request: handover grant verification threw", {
|
|
138
|
+
ctx.obs.log?.warn("signup-request: handover grant verification threw", {
|
|
139
|
+
entityType,
|
|
140
|
+
});
|
|
133
141
|
}
|
|
134
142
|
}
|
|
135
143
|
}
|
|
@@ -24,10 +24,12 @@ export function createSwitchTenantMfaGateHandler(opts) {
|
|
|
24
24
|
description: "Runs the MFA gate of the login for a tenant switch and answers with an MFA challenge, a setup requirement, or clearance.",
|
|
25
25
|
handler: async (event, ctx) => {
|
|
26
26
|
const { userId, tenantId } = event.payload;
|
|
27
|
-
const active = await ctx.resolveActiveMembership(userId, tenantId);
|
|
27
|
+
const active = await ctx.auth.resolveActiveMembership(userId, tenantId);
|
|
28
28
|
if (active.kind === "rejected")
|
|
29
29
|
return noMembership();
|
|
30
|
-
const userRow = parseAuthUserRow(await ctx.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
|
|
30
|
+
const userRow = parseAuthUserRow(await ctx.dispatch.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
|
|
31
|
+
id: userId,
|
|
32
|
+
}));
|
|
31
33
|
const roles = buildSessionRoles(parseRoles(userRow?.roles ?? null), active.membership.roles);
|
|
32
34
|
const mfaGate = await gateEnforceMfa(ctx, opts, userId, tenantId, roles);
|
|
33
35
|
return { isSuccess: true, data: mfaGate ?? { kind: "mfa-gate-clear", roles } };
|
|
@@ -25,6 +25,7 @@ export type TokenRequestSpec<TName extends string, TSuccessKind extends string>
|
|
|
25
25
|
readonly notConfiguredI18nKey: string;
|
|
26
26
|
readonly extraSilentSkip: (user: AuthUserRow) => boolean;
|
|
27
27
|
readonly notificationType: string;
|
|
28
|
+
readonly forwardsReturnTo: boolean;
|
|
28
29
|
};
|
|
29
30
|
export type TokenRequestOptions = {
|
|
30
31
|
readonly hmacSecret: string;
|
|
@@ -41,4 +42,5 @@ export type TokenRequestOptions = {
|
|
|
41
42
|
};
|
|
42
43
|
export declare function createTokenRequestHandler<TName extends string, TSuccessKind extends string>(spec: TokenRequestSpec<TName, TSuccessKind>, opts: TokenRequestOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<TName, z.ZodObject<{
|
|
43
44
|
email: z.ZodEmail;
|
|
45
|
+
returnTo: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
44
46
|
}, z.core.$strip>, TokenRequestData<TSuccessKind>, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -19,8 +19,11 @@ import { UserQueries } from "../../user/index.js";
|
|
|
19
19
|
import { parseAuthUserRow } from "../auth-user-row.js";
|
|
20
20
|
import { AUTH_EMAIL_PAYLOAD_RATE_LIMITS } from "../email-rate-limits.js";
|
|
21
21
|
import { dispatchMagicLinkMail, resolveHandlerMailLocale } from "../magic-link-mail.js";
|
|
22
|
+
import { MAX_RETURN_PATH_LENGTH } from "../web/return-path.js";
|
|
22
23
|
const RequestTokenSchema = z.object({
|
|
23
24
|
email: z.email(),
|
|
25
|
+
// A bad returnTo must not void the request; the mail builder drops unsafe values.
|
|
26
|
+
returnTo: z.string().max(MAX_RETURN_PATH_LENGTH).optional().catch(undefined),
|
|
24
27
|
});
|
|
25
28
|
// The requester is anonymous, so ctx.tz.user is only the tenant default; the recipient's
|
|
26
29
|
// own profile zone is the one the expiry should be read in.
|
|
@@ -38,7 +41,7 @@ export function createTokenRequestHandler(spec, opts) {
|
|
|
38
41
|
escapeHatch: {
|
|
39
42
|
grants: ["systemIdentity"],
|
|
40
43
|
reason: "Anonymous requester has no session — looks up the target user by email via " +
|
|
41
|
-
"ctx.queryAs(SYSTEM, user:findForAuth) to mint the out-of-band token.",
|
|
44
|
+
"ctx.dispatch.queryAs(SYSTEM, user:findForAuth) to mint the out-of-band token.",
|
|
42
45
|
},
|
|
43
46
|
description: spec.description,
|
|
44
47
|
handler: async (event, ctx) => {
|
|
@@ -50,7 +53,7 @@ export function createTokenRequestHandler(spec, opts) {
|
|
|
50
53
|
}));
|
|
51
54
|
}
|
|
52
55
|
const systemUser = createSystemUser(event.user.tenantId);
|
|
53
|
-
const user = parseAuthUserRow(await ctx.queryAs(systemUser, UserQueries.findForAuth, {
|
|
56
|
+
const user = parseAuthUserRow(await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
|
|
54
57
|
email: event.payload.email,
|
|
55
58
|
}));
|
|
56
59
|
// Silent-success branches all return the SAME shape with kind="no-op".
|
|
@@ -79,6 +82,8 @@ export function createTokenRequestHandler(spec, opts) {
|
|
|
79
82
|
timeZone: recipientTimeZone(user, ctx.tz.user),
|
|
80
83
|
...(opts.appName !== undefined && { appName: opts.appName }),
|
|
81
84
|
locale,
|
|
85
|
+
...(spec.forwardsReturnTo &&
|
|
86
|
+
event.payload.returnTo !== undefined && { returnTo: event.payload.returnTo }),
|
|
82
87
|
});
|
|
83
88
|
const data = {
|
|
84
89
|
kind: spec.successKind,
|
|
@@ -23,7 +23,7 @@ export function createVerifyEmailHandler(opts) {
|
|
|
23
23
|
escapeHatch: {
|
|
24
24
|
grants: ["systemIdentity"],
|
|
25
25
|
reason: "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
|
|
26
|
-
"target user via ctx.writeAs(SYSTEM, user:update) to set emailVerified.",
|
|
26
|
+
"target user via ctx.dispatch.writeAs(SYSTEM, user:update) to set emailVerified.",
|
|
27
27
|
},
|
|
28
28
|
agent: { expose: false },
|
|
29
29
|
handler: async (event, ctx) => {
|
|
@@ -37,7 +37,7 @@ export function createVerifyEmailHandler(opts) {
|
|
|
37
37
|
return invalidVerificationToken();
|
|
38
38
|
return runConfirmTokenFlow(ctx, verify.userId, verify.expiresAtMs, {
|
|
39
39
|
purpose: "verify",
|
|
40
|
-
redisRequiredMessage: "email-verification requires ctx.redis to enforce token single-use",
|
|
40
|
+
redisRequiredMessage: "email-verification requires ctx.services.redis to enforce token single-use",
|
|
41
41
|
invalidToken: invalidVerificationToken,
|
|
42
42
|
buildChanges: async () => ({ emailVerified: true }),
|
|
43
43
|
successData: { kind: "verified" },
|
|
@@ -15,6 +15,8 @@ export type MagicLinkMailParams = {
|
|
|
15
15
|
readonly issuedAt?: string;
|
|
16
16
|
readonly timeZone?: string;
|
|
17
17
|
readonly role?: string;
|
|
18
|
+
/** Page the landing page should lead to afterwards; an unsafe value is left out of the link. */
|
|
19
|
+
readonly returnTo?: string;
|
|
18
20
|
};
|
|
19
21
|
export declare function resolveHandlerMailLocale(ctx: {
|
|
20
22
|
readonly locale?: string;
|
|
@@ -10,12 +10,16 @@ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
|
10
10
|
import { mailT, resolveMailLocale } from "@cosmicdrift/kumiko-framework/i18n";
|
|
11
11
|
import { tokenMailExpiry } from "./email-templates.js";
|
|
12
12
|
import { authMailTemplateLocale } from "./mail-notifications.js";
|
|
13
|
+
import { isSafeReturnPath, RETURN_TO_QUERY_PARAM } from "./web/return-path.js";
|
|
13
14
|
function resolveAppUrl(appUrl, locale) {
|
|
14
15
|
return typeof appUrl === "function" ? appUrl(locale) : appUrl;
|
|
15
16
|
}
|
|
16
|
-
function
|
|
17
|
+
function appendTokenAndReturnTo(appUrl, token, returnTo) {
|
|
18
|
+
const query = new URLSearchParams({ token });
|
|
19
|
+
if (isSafeReturnPath(returnTo))
|
|
20
|
+
query.set(RETURN_TO_QUERY_PARAM, returnTo);
|
|
17
21
|
const sep = appUrl.includes("?") ? "&" : "?";
|
|
18
|
-
return `${appUrl}${sep}
|
|
22
|
+
return `${appUrl}${sep}${query.toString()}`;
|
|
19
23
|
}
|
|
20
24
|
// X-Locale on the request wins over the handler's static opts.locale, then
|
|
21
25
|
// ctx.locale's resolved default (always set — boot default / "en"). Reading
|
|
@@ -49,7 +53,7 @@ export async function dispatchMagicLinkMail(notify, spec, params) {
|
|
|
49
53
|
route: { email: params.email },
|
|
50
54
|
data: {
|
|
51
55
|
appName: params.appName ?? spec.fallbackAppName ?? mailT(templateLocale, "auth.mail.appNameDefault"),
|
|
52
|
-
actionUrl:
|
|
56
|
+
actionUrl: appendTokenAndReturnTo(resolveAppUrl(params.appUrl, urlLocale), params.token, params.returnTo),
|
|
53
57
|
validFor,
|
|
54
58
|
validUntil,
|
|
55
59
|
...(params.role !== undefined && { role: params.role }),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
-
import type
|
|
2
|
+
import { type SessionUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { type SeedTenantHooks } from "../tenant/seeding.ts";
|
|
4
4
|
export { seedTenant, seedTenantMembership } from "../tenant/seeding.ts";
|
|
5
5
|
export { seedUser } from "../user/seeding.ts";
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
// sub-paths themselves.
|
|
14
14
|
import { configuredBlindIndexKey, configuredPiiSubjectKms, decryptPiiFieldValues, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
15
15
|
import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
16
|
+
import { createSystemUser, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
|
|
16
17
|
import { ConflictError } from "@cosmicdrift/kumiko-framework/errors";
|
|
17
18
|
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
18
|
-
import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
|
|
19
19
|
import { hashPassword } from "../shared/index.js";
|
|
20
20
|
// kumiko-lint-ignore cross-feature-import auth-tests need user+tenant seed-helpers
|
|
21
21
|
import { seedTenant, seedTenantMembership } from "../tenant/seeding.js";
|
|
@@ -129,9 +129,9 @@ hooks) {
|
|
|
129
129
|
* persistent-DB mode succeeds. Returns the userId.
|
|
130
130
|
*/
|
|
131
131
|
export async function seedAdmin(db, options) {
|
|
132
|
-
const
|
|
132
|
+
const byOption = options.by === undefined ? {} : { by: options.by };
|
|
133
133
|
for (const m of options.memberships) {
|
|
134
|
-
await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName,
|
|
134
|
+
await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, ...byOption });
|
|
135
135
|
}
|
|
136
136
|
const { id: userId } = await seedUserWithPassword(db, {
|
|
137
137
|
email: options.email,
|
|
@@ -139,14 +139,14 @@ export async function seedAdmin(db, options) {
|
|
|
139
139
|
displayName: options.displayName,
|
|
140
140
|
...(options.globalRoles !== undefined && { roles: options.globalRoles }),
|
|
141
141
|
...(options.emailVerified !== undefined && { emailVerified: options.emailVerified }),
|
|
142
|
-
|
|
142
|
+
...byOption,
|
|
143
143
|
});
|
|
144
144
|
for (const m of options.memberships) {
|
|
145
145
|
await seedTenantMembership(db, {
|
|
146
146
|
userId,
|
|
147
147
|
tenantId: m.tenantId,
|
|
148
148
|
roles: m.roles,
|
|
149
|
-
|
|
149
|
+
...byOption,
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
return { id: userId };
|
|
@@ -239,7 +239,8 @@ export async function seedAdminGuarded(db, options) {
|
|
|
239
239
|
const existingId = await findExistingUserIdForEmail(db, kms, options.email);
|
|
240
240
|
if (existingId === undefined)
|
|
241
241
|
return seedAdmin(db, options);
|
|
242
|
-
const
|
|
242
|
+
const byOption = options.by === undefined ? {} : { by: options.by };
|
|
243
|
+
const by = options.by ?? createSystemUser(SYSTEM_TENANT_ID);
|
|
243
244
|
const existingRow = await fetchOne(db, userTable, { id: existingId });
|
|
244
245
|
if (existingRow) {
|
|
245
246
|
// Re-write the email so email_bidx is recomputed; without it the account
|
|
@@ -251,12 +252,12 @@ export async function seedAdminGuarded(db, options) {
|
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
for (const m of options.memberships) {
|
|
254
|
-
await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName,
|
|
255
|
+
await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, ...byOption });
|
|
255
256
|
await seedTenantMembership(db, {
|
|
256
257
|
userId: existingId,
|
|
257
258
|
tenantId: m.tenantId,
|
|
258
259
|
roles: m.roles,
|
|
259
|
-
|
|
260
|
+
...byOption,
|
|
260
261
|
});
|
|
261
262
|
}
|
|
262
263
|
return { id: existingId };
|
|
@@ -10,6 +10,7 @@ export type IssueSignupActivationArgs = {
|
|
|
10
10
|
readonly locale?: string;
|
|
11
11
|
readonly timeZone?: string;
|
|
12
12
|
readonly tokenTtlMinutes?: number;
|
|
13
|
+
readonly returnTo?: string;
|
|
13
14
|
/** Runs after the token is stored and before the mail goes out. */
|
|
14
15
|
readonly onTokenStored?: (stored: {
|
|
15
16
|
readonly token: string;
|
|
@@ -35,6 +35,7 @@ export async function issueSignupActivation(args) {
|
|
|
35
35
|
...(args.timeZone !== undefined && { timeZone: args.timeZone }),
|
|
36
36
|
...(args.appName !== undefined && { appName: args.appName }),
|
|
37
37
|
...(args.locale !== undefined && { locale: args.locale }),
|
|
38
|
+
...(args.returnTo !== undefined && { returnTo: args.returnTo }),
|
|
38
39
|
});
|
|
39
40
|
return { email, token, expiresAt };
|
|
40
41
|
}
|
|
@@ -54,7 +54,7 @@ export type AuthTokenFailure = {
|
|
|
54
54
|
};
|
|
55
55
|
export type ResetPasswordFailure = AuthTokenFailure;
|
|
56
56
|
export type VerifyEmailFailure = AuthTokenFailure;
|
|
57
|
-
export declare function requestPasswordReset(email: string): Promise<{
|
|
57
|
+
export declare function requestPasswordReset(email: string, returnTo?: string): Promise<{
|
|
58
58
|
ok: true;
|
|
59
59
|
} | {
|
|
60
60
|
ok: false;
|
|
@@ -93,7 +93,7 @@ export declare function confirmAccountUnlock(token: string): Promise<{
|
|
|
93
93
|
export declare function requestSignup(email: string, handover?: {
|
|
94
94
|
readonly entityType: string;
|
|
95
95
|
readonly token: string;
|
|
96
|
-
}): Promise<{
|
|
96
|
+
}, returnTo?: string): Promise<{
|
|
97
97
|
ok: true;
|
|
98
98
|
} | {
|
|
99
99
|
ok: false;
|
|
@@ -115,12 +115,12 @@ async function parseTokenFailure(res) {
|
|
|
115
115
|
// email doesn't exist, the caller sees `{ ok: true }` — no account
|
|
116
116
|
// enumeration. Server only triggers the mail internally when a user is
|
|
117
117
|
// found. 429 → typed rate-limit failure. 5xx → unknown-error.
|
|
118
|
-
export async function requestPasswordReset(email) {
|
|
118
|
+
export async function requestPasswordReset(email, returnTo) {
|
|
119
119
|
const res = await fetch("/api/auth/request-password-reset", {
|
|
120
120
|
method: "POST",
|
|
121
121
|
credentials: "same-origin",
|
|
122
122
|
headers: { "Content-Type": "application/json", ...csrfHeader() },
|
|
123
|
-
body: JSON.stringify({ email }),
|
|
123
|
+
body: JSON.stringify({ email, ...(returnTo !== undefined && { returnTo }) }),
|
|
124
124
|
});
|
|
125
125
|
if (res.ok)
|
|
126
126
|
return { ok: true };
|
|
@@ -208,12 +208,16 @@ export async function requestSignup(email,
|
|
|
208
208
|
// grant so the server can verify + rebind it to the signup token. The grant never
|
|
209
209
|
// travels any further than this request — never in the URL, the mail,
|
|
210
210
|
// or the confirm response.
|
|
211
|
-
handover) {
|
|
211
|
+
handover, returnTo) {
|
|
212
212
|
const res = await fetch("/api/auth/signup-request", {
|
|
213
213
|
method: "POST",
|
|
214
214
|
credentials: "same-origin",
|
|
215
215
|
headers: { "Content-Type": "application/json", ...csrfHeader() },
|
|
216
|
-
body: JSON.stringify({
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
email,
|
|
218
|
+
...(handover !== undefined && { handover }),
|
|
219
|
+
...(returnTo !== undefined && { returnTo }),
|
|
220
|
+
}),
|
|
217
221
|
});
|
|
218
222
|
if (res.ok)
|
|
219
223
|
return { ok: true };
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { isSafeReturnPath } from "./return-path.ts";
|
|
1
2
|
export declare const NEXT_QUERY_PARAM = "next";
|
|
2
|
-
export declare
|
|
3
|
+
export declare const isSafeNextPath: typeof isSafeReturnPath;
|
|
3
4
|
export declare function readNextFromSearch(search: string): string | null;
|
|
5
|
+
export declare function readReturnToFromWindow(): string | null;
|
|
6
|
+
export declare function appendReturnTo(href: string, returnTo: string | null): string;
|
|
7
|
+
export declare function readNextFromWindow(): string | null;
|
|
4
8
|
export declare function assertNavigableUrl(url: string, optionName: string): void;
|
|
5
9
|
export declare function buildLoginRedirectUrl(loginUrl: string, returnPath: string, currentOrigin: string): string;
|
|
6
10
|
export declare function followNextAfterLogin(location?: Pick<Location, "search" | "pathname" | "replace">): boolean;
|