@cosmicdrift/kumiko-samples 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/package.json +1 -1
- package/packages/bundled-features/package.json +25 -13
- package/packages/bundled-features/src/agent-tools/changes.json +6 -0
- package/packages/bundled-features/src/agent-tools/index.ts +1 -1
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +42 -26
- package/packages/bundled-features/src/audit/feature.ts +2 -1
- package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
- package/packages/bundled-features/src/auth-email-password/changes.json +18 -0
- package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
- package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
- package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
- package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +16 -8
- package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +10 -2
- package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +16 -3
- package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +8 -2
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +23 -21
- package/packages/bundled-features/src/auth-email-password/web/default-auth-pages-gate.tsx +6 -3
- package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -1
- package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -0
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +16 -5
- package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +7 -1
- package/packages/bundled-features/src/auth-email-password/web/return-path.ts +35 -0
- package/packages/bundled-features/src/auth-email-password/web/session.tsx +6 -1
- package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +11 -3
- package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +12 -2
- package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
- package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
- package/packages/bundled-features/src/auth-mfa/config.ts +3 -7
- package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
- package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
- package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
- package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
- package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
- package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +17 -8
- package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
- package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
- package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
- package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
- package/packages/bundled-features/src/cap-counter/cap-reservation.ts +21 -8
- package/packages/bundled-features/src/cap-counter/changes.json +12 -0
- package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
- package/packages/bundled-features/src/cap-counter/feature.ts +3 -2
- package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
- package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
- package/packages/bundled-features/src/cap-overview/feature.ts +1 -0
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
- package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
- package/packages/bundled-features/src/config/changes.json +6 -0
- package/packages/bundled-features/src/config/feature.ts +6 -6
- package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
- package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
- package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/set.write.ts +22 -5
- package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
- package/packages/bundled-features/src/crypto-shredding/changes.json +12 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +9 -9
- package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
- package/packages/bundled-features/src/custom-fields/changes.json +6 -0
- package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/index.ts +4 -0
- package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
- package/packages/bundled-features/src/data-retention/feature.ts +1 -1
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
- package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
- package/packages/bundled-features/src/delivery/changes.json +12 -0
- package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
- package/packages/bundled-features/src/delivery/feature.ts +3 -2
- package/packages/bundled-features/src/delivery/index.ts +5 -0
- package/packages/bundled-features/src/delivery/jobs.ts +11 -11
- package/packages/bundled-features/src/delivery/system-channel-context.ts +33 -0
- package/packages/bundled-features/src/delivery/testing.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/append-missing-extract-event.ts +84 -0
- package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +16 -1
- package/packages/bundled-features/src/document-ingest-foundation/index.ts +11 -0
- package/packages/bundled-features/src/feature-toggles/feature.ts +1 -0
- package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
- package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
- package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
- package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
- package/packages/bundled-features/src/form-draft/feature.ts +1 -1
- package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
- package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
- package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
- package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
- package/packages/bundled-features/src/host-routing/changes.json +6 -0
- package/packages/bundled-features/src/host-routing/validate-options.ts +5 -0
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +2 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
- package/packages/bundled-features/src/jobs/changes.json +6 -0
- package/packages/bundled-features/src/jobs/events.ts +1 -1
- package/packages/bundled-features/src/jobs/feature.ts +1 -0
- package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
- package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
- package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
- package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
- package/packages/bundled-features/src/ledger/web/index.ts +1 -1
- package/packages/bundled-features/src/legal-pages/README.md +1 -1
- package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
- package/packages/bundled-features/src/legal-pages/seed.ts +5 -2
- package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
- package/packages/bundled-features/src/mail-transport-smtp/changes.json +12 -0
- package/packages/bundled-features/src/mail-transport-smtp/config-keys.ts +1 -1
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +13 -12
- package/packages/bundled-features/src/managed-pages/changes.json +6 -0
- package/packages/bundled-features/src/managed-pages/constants.ts +1 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +3 -1
- package/packages/bundled-features/src/managed-pages/index.ts +1 -0
- package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -1
- package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
- package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
- package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
- package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
- package/packages/bundled-features/src/secrets/changes.json +6 -0
- package/packages/bundled-features/src/secrets/feature.ts +9 -9
- package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
- package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
- package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
- package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
- package/packages/bundled-features/src/secrets/write-gate.ts +10 -3
- package/packages/bundled-features/src/seo/feature.ts +1 -1
- package/packages/bundled-features/src/sessions/changes.json +19 -0
- package/packages/bundled-features/src/sessions/feature.ts +2 -1
- package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
- package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
- package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/sessions/index.ts +1 -1
- package/packages/bundled-features/src/sessions/screens.ts +8 -4
- package/packages/bundled-features/src/sessions/session-callbacks.ts +1 -9
- package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
- package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
- package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
- package/packages/bundled-features/src/shared/index.ts +15 -0
- package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +36 -4
- package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
- package/packages/bundled-features/src/shared/run-in-sub-transaction.ts +5 -5
- package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
- package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
- package/packages/bundled-features/src/step-dispatcher/changes.json +6 -0
- package/packages/bundled-features/src/step-dispatcher/deliver-dispatch-request.ts +17 -3
- package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
- package/packages/bundled-features/src/step-dispatcher/mail-runner.ts +2 -1
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
- package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
- package/packages/bundled-features/src/tags/feature.ts +1 -0
- package/packages/bundled-features/src/template-resolver/api.ts +3 -3
- package/packages/bundled-features/src/template-resolver/changes.json +54 -0
- package/packages/bundled-features/src/template-resolver/document-ops.ts +20 -1
- package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +36 -11
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +32 -7
- package/packages/bundled-features/src/template-resolver/handlers/patch-draft.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/send-test-status.query.ts +22 -19
- package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/seeding.ts +22 -5
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +68 -19
- package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
- package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
- package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
- package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
- package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
- package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +6 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
- package/packages/bundled-features/src/template-resolver/web/qualified-names.ts +1 -0
- package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
- package/packages/bundled-features/src/tenant/changes.json +19 -0
- package/packages/bundled-features/src/tenant/feature.ts +1 -0
- package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
- package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -3
- package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
- package/packages/bundled-features/src/tenant/index.ts +5 -0
- package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
- package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
- package/packages/bundled-features/src/tenant/seeding.ts +65 -62
- package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
- package/packages/bundled-features/src/tenant/testing.ts +77 -0
- package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/changes.json +6 -0
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
- package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
- package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +42 -4
- package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
- package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
- package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
- package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
- package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
- package/packages/bundled-features/src/user/feature.ts +1 -0
- package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
- package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
- package/packages/bundled-features/src/user/index.ts +2 -0
- package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
- package/packages/bundled-features/src/user/principal-status.ts +1 -1
- package/packages/bundled-features/src/user/seeding.ts +24 -6
- package/packages/bundled-features/src/user/testing.ts +22 -0
- package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
- package/packages/bundled-features/src/user-data-rights/README.md +1 -1
- package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
- package/packages/bundled-features/src/user-data-rights/changes.json +36 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +30 -24
- package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
- package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
- package/packages/bundled-features/src/user-data-rights/index.ts +8 -1
- package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
- package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
- package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
- package/packages/bundled-features/src/user-data-rights/web/constants.ts +16 -0
- package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -0
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
- package/packages/bundled-features/src/waitlist/changes.json +18 -0
- package/packages/bundled-features/src/waitlist/feature.ts +1 -0
- package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
- package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +34 -10
- package/packages/bundled-features/src/waitlist/import.ts +23 -3
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +8 -2
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
- package/packages/bundled-features/src/waitlist/options.ts +2 -1
- package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
- package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
- package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
- package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
- package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
- package/samples/apps/config-demo/src/app/server.ts +1 -1
- package/samples/apps/styleguide/src/app/client.tsx +2 -0
- package/samples/apps/styleguide/src/app/seed.ts +6 -4
- package/samples/apps/styleguide/src/app/server.ts +6 -3
- package/samples/apps/styleguide/src/features/widgets/feature.ts +15 -4
- package/samples/apps/styleguide/src/features/workboard/feature.ts +185 -0
- package/samples/apps/styleguide/src/features/workboard/i18n.ts +27 -0
- package/samples/apps/styleguide/src/features/workboard/seed.ts +121 -0
- package/samples/apps/styleguide/src/features/workboard/web.ts +13 -0
- package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +9 -0
- package/samples/apps/use-all-bundled/bin/main.ts +2 -0
- package/samples/apps/use-all-bundled/feature-manifest.json +17 -1
- package/samples/apps/use-all-bundled/kumiko/schema.ts +2 -0
- package/samples/apps/use-all-bundled/kumiko.config.ts +2 -0
- package/samples/apps/use-all-bundled/package.json +2 -1
- package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +2 -0
- package/samples/apps/use-all-bundled/src/app/notes-feature.ts +2 -0
- package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
- package/samples/apps/use-all-bundled/src/bundled-lists.ts +15 -0
- package/samples/apps/user-data-rights-demo/README.md +1 -1
- package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
- package/samples/recipes/api-worker-split/README.md +2 -1
- package/samples/recipes/api-worker-split/src/feature.ts +2 -1
- package/samples/recipes/cross-feature-events/README.md +5 -5
- package/samples/recipes/cross-feature-events/package.json +1 -1
- package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
- package/samples/recipes/event-sourcing/README.md +5 -5
- package/samples/recipes/event-sourcing/package.json +1 -1
- package/samples/recipes/event-sourcing/src/feature.ts +15 -15
- package/samples/recipes/files-post-processing/README.md +1 -1
- package/samples/recipes/files-post-processing/package.json +1 -1
- package/samples/recipes/files-post-processing/src/feature.ts +6 -4
- package/samples/recipes/golden-path/src/feature.ts +2 -1
- package/samples/recipes/lane-routing/package.json +1 -1
- package/samples/recipes/legal-pages/README.md +2 -2
- package/samples/recipes/lifecycle-hooks/README.md +4 -0
- package/samples/recipes/lifecycle-hooks/src/feature.ts +26 -1
- package/samples/recipes/list-table-options/README.md +50 -0
- package/samples/recipes/list-table-options/package.json +11 -0
- package/samples/recipes/list-table-options/src/feature.ts +221 -0
- package/samples/recipes/managed-config/README.md +3 -3
- package/samples/recipes/pdf-audit-export/src/feature.ts +2 -1
- package/samples/recipes/search/README.md +1 -1
- package/samples/recipes/search/src/feature.ts +3 -3
- package/samples/recipes/secrets-demo/README.md +1 -1
- package/samples/recipes/secrets-demo/src/feature.ts +5 -2
- package/samples/recipes/state-machine/src/events.ts +4 -3
- package/samples/recipes/state-machine/src/reducer.ts +1 -1
- package/samples/recipes/tenant-isolation/README.md +1 -0
- package/samples/recipes/tenant-isolation/src/feature.ts +36 -0
- package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
- package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
|
@@ -168,9 +168,9 @@ async function issueInvitation(
|
|
|
168
168
|
ctx: HandlerContext,
|
|
169
169
|
refuseAccepted = false,
|
|
170
170
|
): Promise<WriteResult<InviteCreateData>> {
|
|
171
|
-
if (!ctx.redis) {
|
|
171
|
+
if (!ctx.services.redis) {
|
|
172
172
|
return writeFailure(
|
|
173
|
-
new InternalError({ message: "invite-create requires ctx.redis for token store" }),
|
|
173
|
+
new InternalError({ message: "invite-create requires ctx.services.redis for token store" }),
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -197,7 +197,7 @@ async function issueInvitation(
|
|
|
197
197
|
const existingVersion = existing["version"] as number; // @cast-boundary db-row
|
|
198
198
|
// At most one live invite token per invitation: invalidate
|
|
199
199
|
// whatever's there before minting the new one.
|
|
200
|
-
await invalidateExistingInviteToken(ctx.redis, invitationId);
|
|
200
|
+
await invalidateExistingInviteToken(ctx.services.redis, invitationId);
|
|
201
201
|
|
|
202
202
|
const updateResult = await executor.update(
|
|
203
203
|
{
|
|
@@ -237,7 +237,7 @@ async function issueInvitation(
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
const token = generateToken();
|
|
240
|
-
await storeInviteToken(ctx.redis, { invitationId, token, ttlSeconds });
|
|
240
|
+
await storeInviteToken(ctx.services.redis, { invitationId, token, ttlSeconds });
|
|
241
241
|
|
|
242
242
|
const locale = request.locale ?? resolveHandlerMailLocale(ctx, opts.locale);
|
|
243
243
|
|
|
@@ -52,9 +52,9 @@ export const inviteInfoQuery = defineQueryHandler({
|
|
|
52
52
|
"Anonymous, read-only invite lookup for the invite-acceptance page: reveals the invited " +
|
|
53
53
|
"email and whether an account already exists for it, without consuming the token.",
|
|
54
54
|
handler: async (query, ctx) => {
|
|
55
|
-
if (!ctx.redis) throwInvalidInviteToken();
|
|
55
|
+
if (!ctx.services.redis) throwInvalidInviteToken();
|
|
56
56
|
|
|
57
|
-
const invitationId = await getInvitationIdForToken(ctx.redis, query.payload.token);
|
|
57
|
+
const invitationId = await getInvitationIdForToken(ctx.services.redis, query.payload.token);
|
|
58
58
|
if (!invitationId) throwInvalidInviteToken();
|
|
59
59
|
|
|
60
60
|
type InvitationRow = {
|
package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts
CHANGED
|
@@ -103,16 +103,16 @@ export function createInviteSignupCompleteHandler(opts: InviteSignupCompleteOpti
|
|
|
103
103
|
reason: INVITE_SIGNUP_COMPLETE_ESCAPE_HATCH_REASON,
|
|
104
104
|
},
|
|
105
105
|
handler: async (event, ctx) => {
|
|
106
|
-
if (!ctx.redis) {
|
|
106
|
+
if (!ctx.services.redis) {
|
|
107
107
|
return writeFailure(
|
|
108
|
-
new InternalError({ message: "invite-signup-complete requires ctx.redis" }),
|
|
108
|
+
new InternalError({ message: "invite-signup-complete requires ctx.services.redis" }),
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
const invitationId = await getInvitationIdForToken(ctx.redis, event.payload.token);
|
|
112
|
+
const invitationId = await getInvitationIdForToken(ctx.services.redis, event.payload.token);
|
|
113
113
|
if (!invitationId) return invalidInviteToken();
|
|
114
114
|
|
|
115
|
-
const burn = await burnInviteToken(ctx.redis, event.payload.token);
|
|
115
|
+
const burn = await burnInviteToken(ctx.services.redis, event.payload.token);
|
|
116
116
|
if (burn === "already-used") return invalidInviteToken();
|
|
117
117
|
|
|
118
118
|
type InvitationRow = {
|
|
@@ -187,6 +187,8 @@ export function createInviteSignupCompleteHandler(opts: InviteSignupCompleteOpti
|
|
|
187
187
|
db: dbConn,
|
|
188
188
|
tenantId: invitationTenantId,
|
|
189
189
|
mode: "system",
|
|
190
|
+
reason:
|
|
191
|
+
"invitation tenant differs from the caller's tenant, the accept step updates the invitation row there",
|
|
190
192
|
});
|
|
191
193
|
const updateResult = await invitationExecutor.update(
|
|
192
194
|
{
|
|
@@ -214,7 +216,7 @@ export function createInviteSignupCompleteHandler(opts: InviteSignupCompleteOpti
|
|
|
214
216
|
mergedRoles,
|
|
215
217
|
);
|
|
216
218
|
|
|
217
|
-
await deleteInviteToken(ctx.redis, { invitationId, token: event.payload.token });
|
|
219
|
+
await deleteInviteToken(ctx.services.redis, { invitationId, token: event.payload.token });
|
|
218
220
|
|
|
219
221
|
if (mfaGate !== undefined) {
|
|
220
222
|
committed = true;
|
|
@@ -239,8 +241,8 @@ export function createInviteSignupCompleteHandler(opts: InviteSignupCompleteOpti
|
|
|
239
241
|
},
|
|
240
242
|
};
|
|
241
243
|
} finally {
|
|
242
|
-
if (!committed && ctx.redis) {
|
|
243
|
-
await unburnInviteToken(ctx.redis, event.payload.token);
|
|
244
|
+
if (!committed && ctx.services.redis) {
|
|
245
|
+
await unburnInviteToken(ctx.services.redis, event.payload.token);
|
|
244
246
|
}
|
|
245
247
|
}
|
|
246
248
|
},
|
|
@@ -35,7 +35,7 @@ export type LoginHandlerOptions = {
|
|
|
35
35
|
readonly strictEmailVerification?: boolean;
|
|
36
36
|
// Brute-force protection: after N wrong-password attempts the account
|
|
37
37
|
// locks for the configured duration. State lives in Redis (see
|
|
38
|
-
// lockout-store.ts) — if ctx.redis is unset, lockout is skipped and the
|
|
38
|
+
// lockout-store.ts) — if ctx.services.redis is unset, lockout is skipped and the
|
|
39
39
|
// handler falls back to classic invalid-credentials. Counter is monotonic
|
|
40
40
|
// and only resets on a successful login, so a re-lock after the cooldown
|
|
41
41
|
// happens on the FIRST miss, not the Nth (strict semantic — favours
|
|
@@ -123,10 +123,10 @@ export async function gateCountUnknownUser(
|
|
|
123
123
|
lockoutDurationMinutes: number,
|
|
124
124
|
): Promise<GateOutcome<undefined>> {
|
|
125
125
|
const subject = unknownEmailLockoutSubject(ctx, email);
|
|
126
|
-
if (!ctx.redis || subject === undefined) return ok(undefined);
|
|
126
|
+
if (!ctx.services.redis || subject === undefined) return ok(undefined);
|
|
127
127
|
const locked = await gateEnforceLockout(ctx, subject);
|
|
128
128
|
if (!locked.ok) return locked;
|
|
129
|
-
await recordFailedAttempt(ctx.redis, subject, maxFailedAttempts, lockoutDurationMinutes);
|
|
129
|
+
await recordFailedAttempt(ctx.services.redis, subject, maxFailedAttempts, lockoutDurationMinutes);
|
|
130
130
|
return ok(undefined);
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -138,8 +138,8 @@ export async function gateEnforceLockout(
|
|
|
138
138
|
ctx: HandlerContext,
|
|
139
139
|
userId: string,
|
|
140
140
|
): Promise<GateOutcome<undefined>> {
|
|
141
|
-
if (!ctx.redis) return ok(undefined);
|
|
142
|
-
const state = await getLockoutState(ctx.redis, userId);
|
|
141
|
+
if (!ctx.services.redis) return ok(undefined);
|
|
142
|
+
const state = await getLockoutState(ctx.services.redis, userId);
|
|
143
143
|
if (state?.lockedUntil !== null && state?.lockedUntil !== undefined) {
|
|
144
144
|
const now = Date.now();
|
|
145
145
|
if (state.lockedUntil > now) {
|
|
@@ -160,15 +160,20 @@ export async function gateVerifyPassword(
|
|
|
160
160
|
): Promise<GateOutcome<undefined>> {
|
|
161
161
|
const passwordOk = await verifyPassword(found.passwordHash, password);
|
|
162
162
|
if (!passwordOk) {
|
|
163
|
-
if (ctx.redis) {
|
|
164
|
-
await recordFailedAttempt(
|
|
163
|
+
if (ctx.services.redis) {
|
|
164
|
+
await recordFailedAttempt(
|
|
165
|
+
ctx.services.redis,
|
|
166
|
+
found.id,
|
|
167
|
+
maxFailedAttempts,
|
|
168
|
+
lockoutDurationMinutes,
|
|
169
|
+
);
|
|
165
170
|
}
|
|
166
171
|
return reject(invalidCredentials());
|
|
167
172
|
}
|
|
168
173
|
// Clear before MFA — MFA has its own attempt-cap; don't password-lock
|
|
169
174
|
// users who occasionally mistype across otherwise-successful logins.
|
|
170
|
-
if (ctx.redis) {
|
|
171
|
-
await clearLockoutState(ctx.redis, found.id);
|
|
175
|
+
if (ctx.services.redis) {
|
|
176
|
+
await clearLockoutState(ctx.services.redis, found.id);
|
|
172
177
|
}
|
|
173
178
|
return ok(undefined);
|
|
174
179
|
}
|
|
@@ -216,7 +221,7 @@ export async function gateResolveMembership(
|
|
|
216
221
|
const globalRoles = parseRoles(found.roles ?? null);
|
|
217
222
|
|
|
218
223
|
for (const candidate of candidates) {
|
|
219
|
-
const active = await ctx.resolveActiveMembership(found.id, candidate.tenantId);
|
|
224
|
+
const active = await ctx.auth.resolveActiveMembership(found.id, candidate.tenantId);
|
|
220
225
|
if (active.kind === "rejected") {
|
|
221
226
|
// A blocked principal is blocked for every tenant, so stop here;
|
|
222
227
|
// not_a_member/tenant_teardown are per-tenant — try the next candidate.
|
|
@@ -275,7 +280,7 @@ export async function gateBuildSession(
|
|
|
275
280
|
...(timezone !== null && timezone !== undefined && { timezone }),
|
|
276
281
|
...(locale !== null && locale !== undefined && { locale }),
|
|
277
282
|
};
|
|
278
|
-
const claims = await ctx.
|
|
283
|
+
const claims = await ctx.auth.resolveClaims(baseSession);
|
|
279
284
|
const session: SessionUser =
|
|
280
285
|
Object.keys(claims).length > 0 ? { ...baseSession, claims } : baseSession;
|
|
281
286
|
return { kind: "auth-session", session };
|
|
@@ -284,7 +289,7 @@ export async function gateBuildSession(
|
|
|
284
289
|
// Login — unauthenticated entry point. The route is wired public (no JWT
|
|
285
290
|
// middleware), dispatching with the framework's anonymous identity for the
|
|
286
291
|
// handler's access check. Everything inside the handler goes through
|
|
287
|
-
// ctx.queryAs(system, ...) so the user feature stays the single owner of its
|
|
292
|
+
// ctx.dispatch.queryAs(system, ...) so the user feature stays the single owner of its
|
|
288
293
|
// table.
|
|
289
294
|
export function createLoginHandler(opts: LoginHandlerOptions = {}) {
|
|
290
295
|
const strictVerification = opts.strictEmailVerification === true;
|
|
@@ -305,8 +310,8 @@ export function createLoginHandler(opts: LoginHandlerOptions = {}) {
|
|
|
305
310
|
grants: ["systemIdentity", "unsafeRaw"],
|
|
306
311
|
reason:
|
|
307
312
|
"Unauthenticated login has no caller identity yet — it looks up the user row by " +
|
|
308
|
-
"email via ctx.queryAs(SYSTEM, ...) and resolves tenant membership via " +
|
|
309
|
-
"ctx.resolveActiveMembership before a session exists. It also reads the MFA " +
|
|
313
|
+
"email via ctx.dispatch.queryAs(SYSTEM, ...) and resolves tenant membership via " +
|
|
314
|
+
"ctx.auth.resolveActiveMembership before a session exists. It also reads the MFA " +
|
|
310
315
|
"enrollment of the tenant named in the signed login/setup token, not the guest " +
|
|
311
316
|
"dispatch tenant, via the mfaStatusChecker callback.",
|
|
312
317
|
},
|
|
@@ -318,7 +323,9 @@ export function createLoginHandler(opts: LoginHandlerOptions = {}) {
|
|
|
318
323
|
const systemUser = createSystemUser(SYSTEM_USER_ID);
|
|
319
324
|
|
|
320
325
|
const foundRow = parseAuthUserRow(
|
|
321
|
-
await ctx.queryAs(systemUser, UserQueries.findForAuth, {
|
|
326
|
+
await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
|
|
327
|
+
email: event.payload.email,
|
|
328
|
+
}),
|
|
322
329
|
);
|
|
323
330
|
if (!foundRow?.passwordHash || foundRow.isDeleted) {
|
|
324
331
|
const unknown = await gateCountUnknownUser(
|
|
@@ -352,7 +359,7 @@ export function createLoginHandler(opts: LoginHandlerOptions = {}) {
|
|
|
352
359
|
if (!statusGate.ok) return statusGate.result;
|
|
353
360
|
|
|
354
361
|
// Still needed for candidate ORDER (preferred tenant first) in gateResolveMembership.
|
|
355
|
-
const memberships = (await ctx.queryAs(systemUser, TENANT_MEMBERSHIPS_QUERY, {
|
|
362
|
+
const memberships = (await ctx.dispatch.queryAs(systemUser, TENANT_MEMBERSHIPS_QUERY, {
|
|
356
363
|
userId: found.id,
|
|
357
364
|
})) as Array<Membership>; // @cast-boundary db-runner
|
|
358
365
|
|
package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts
CHANGED
|
@@ -31,6 +31,7 @@ export function createRequestAccountUnlockHandler(opts: RequestAccountUnlockOpti
|
|
|
31
31
|
// empty) lockout state — harmless no-op if they weren't locked.
|
|
32
32
|
extraSilentSkip: () => false,
|
|
33
33
|
notificationType: AuthMailNotificationTypes.accountUnlock,
|
|
34
|
+
forwardsReturnTo: false,
|
|
34
35
|
},
|
|
35
36
|
opts,
|
|
36
37
|
);
|
|
@@ -28,6 +28,7 @@ export function createRequestEmailVerificationHandler(opts: RequestEmailVerifica
|
|
|
28
28
|
// caller sees the same 200 regardless of whether a token was minted.
|
|
29
29
|
extraSilentSkip: (user) => user.emailVerified === true,
|
|
30
30
|
notificationType: AuthMailNotificationTypes.emailVerification,
|
|
31
|
+
forwardsReturnTo: false,
|
|
31
32
|
},
|
|
32
33
|
opts,
|
|
33
34
|
);
|
package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts
CHANGED
|
@@ -30,6 +30,7 @@ export function createRequestPasswordResetHandler(opts: RequestPasswordResetOpti
|
|
|
30
30
|
// state. The sessions feature handles the post-change revocation.
|
|
31
31
|
extraSilentSkip: () => false,
|
|
32
32
|
notificationType: AuthMailNotificationTypes.passwordReset,
|
|
33
|
+
forwardsReturnTo: true,
|
|
33
34
|
},
|
|
34
35
|
opts,
|
|
35
36
|
);
|
|
@@ -31,7 +31,7 @@ export function createResetPasswordHandler(opts: ResetPasswordOptions) {
|
|
|
31
31
|
grants: ["systemIdentity"],
|
|
32
32
|
reason:
|
|
33
33
|
"Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
|
|
34
|
-
"target user via ctx.writeAs(SYSTEM, user:update) to set the new password hash.",
|
|
34
|
+
"target user via ctx.dispatch.writeAs(SYSTEM, user:update) to set the new password hash.",
|
|
35
35
|
},
|
|
36
36
|
agent: { expose: false },
|
|
37
37
|
handler: async (event, ctx) => {
|
|
@@ -51,7 +51,8 @@ export function createResetPasswordHandler(opts: ResetPasswordOptions) {
|
|
|
51
51
|
|
|
52
52
|
return runConfirmTokenFlow(ctx, verify.userId, verify.expiresAtMs, {
|
|
53
53
|
purpose: "reset",
|
|
54
|
-
redisRequiredMessage:
|
|
54
|
+
redisRequiredMessage:
|
|
55
|
+
"password-reset requires ctx.services.redis to enforce token single-use",
|
|
55
56
|
invalidToken: invalidResetToken,
|
|
56
57
|
buildChanges: async () => ({
|
|
57
58
|
passwordHash: await hashPassword(event.payload.newPassword),
|
|
@@ -119,16 +119,16 @@ async function claimBoundHandover(
|
|
|
119
119
|
): Promise<ClaimedHandover | undefined> {
|
|
120
120
|
const provider = findSignupHandoverProvider(ctx.registry);
|
|
121
121
|
if (!provider) {
|
|
122
|
-
ctx.log?.warn(
|
|
122
|
+
ctx.obs.log?.warn(
|
|
123
123
|
"signup-confirm: handover binding present but no signupHandover provider mounted",
|
|
124
124
|
);
|
|
125
125
|
return undefined;
|
|
126
126
|
}
|
|
127
127
|
const claim = provider.mintClaim(binding);
|
|
128
|
-
const claimResult = await ctx.writeAs(session, claim.qualifiedName, claim.payload);
|
|
128
|
+
const claimResult = await ctx.dispatch.writeAs(session, claim.qualifiedName, claim.payload);
|
|
129
129
|
if (claimResult.isSuccess) return { entityType: binding.entityType, id: binding.rowId };
|
|
130
130
|
if (!isBenignClaimRejection(claimResult.error)) throw reraiseAsKumikoError(claimResult.error);
|
|
131
|
-
ctx.log?.warn("signup-confirm: handover grant did not redeem", {
|
|
131
|
+
ctx.obs.log?.warn("signup-confirm: handover grant did not redeem", {
|
|
132
132
|
entityType: binding.entityType,
|
|
133
133
|
});
|
|
134
134
|
return undefined;
|
|
@@ -146,22 +146,22 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
|
|
|
146
146
|
reason: SIGNUP_CONFIRM_PROVISION_REASON,
|
|
147
147
|
},
|
|
148
148
|
handler: async (event, ctx) => {
|
|
149
|
-
if (!ctx.redis) {
|
|
149
|
+
if (!ctx.services.redis) {
|
|
150
150
|
return writeFailure(
|
|
151
151
|
new InternalError({
|
|
152
|
-
message: "signup-confirm requires ctx.redis for token consumption",
|
|
152
|
+
message: "signup-confirm requires ctx.services.redis for token consumption",
|
|
153
153
|
}),
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// Token lookup: nonexistent, expired, and already-consumed tokens all
|
|
158
158
|
// collapse to invalid_signup_token to prevent enumeration.
|
|
159
|
-
const email = await getEmailForSignupToken(ctx.redis, event.payload.token);
|
|
159
|
+
const email = await getEmailForSignupToken(ctx.services.redis, event.payload.token);
|
|
160
160
|
if (!email) return invalidSignupToken();
|
|
161
161
|
|
|
162
162
|
// Single-use burn: of two parallel confirmations from different tabs,
|
|
163
163
|
// one succeeds and the other receives invalid_signup_token.
|
|
164
|
-
const burn = await burnSignupToken(ctx.redis, event.payload.token);
|
|
164
|
+
const burn = await burnSignupToken(ctx.services.redis, event.payload.token);
|
|
165
165
|
if (burn === "already-used") return invalidSignupToken();
|
|
166
166
|
|
|
167
167
|
let committed = false;
|
|
@@ -229,7 +229,7 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
|
|
|
229
229
|
// Claim BEFORE spending the signup token or the binding below and
|
|
230
230
|
// before `committed` flips: if the claim throws, the `finally` still
|
|
231
231
|
// unburns and the activation link stays retryable.
|
|
232
|
-
const handoverBinding = await getSignupHandover(ctx.redis, event.payload.token);
|
|
232
|
+
const handoverBinding = await getSignupHandover(ctx.services.redis, event.payload.token);
|
|
233
233
|
const handover = handoverBinding
|
|
234
234
|
? await claimBoundHandover(ctx, session, handoverBinding)
|
|
235
235
|
: undefined;
|
|
@@ -245,8 +245,8 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
|
|
|
245
245
|
|
|
246
246
|
// Drop both token lookup keys; the burn key stays for its remaining
|
|
247
247
|
// TTL as replay protection.
|
|
248
|
-
await deleteSignupToken(ctx.redis, { email, token: event.payload.token });
|
|
249
|
-
if (handoverBinding) await deleteSignupHandover(ctx.redis, event.payload.token);
|
|
248
|
+
await deleteSignupToken(ctx.services.redis, { email, token: event.payload.token });
|
|
249
|
+
if (handoverBinding) await deleteSignupHandover(ctx.services.redis, event.payload.token);
|
|
250
250
|
|
|
251
251
|
if (mfaGate !== undefined) {
|
|
252
252
|
committed = true;
|
|
@@ -275,10 +275,10 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
|
|
|
275
275
|
},
|
|
276
276
|
};
|
|
277
277
|
} finally {
|
|
278
|
-
if (!committed && ctx.redis) {
|
|
278
|
+
if (!committed && ctx.services.redis) {
|
|
279
279
|
// Release the burn so a retry after a DB hiccup is not blocked.
|
|
280
280
|
// Keep the token lookup keys so the user can click the email link again.
|
|
281
|
-
await unburnSignupToken(ctx.redis, event.payload.token);
|
|
281
|
+
await unburnSignupToken(ctx.services.redis, event.payload.token);
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
},
|
|
@@ -31,6 +31,7 @@ import { resolveHandlerMailLocale } from "../magic-link-mail.ts";
|
|
|
31
31
|
import { AUTH_SELF_REGISTRATION_FEATURE } from "../self-registration-toggle.ts";
|
|
32
32
|
import { issueSignupActivation } from "../signup-activation.ts";
|
|
33
33
|
import { storeSignupHandover, takeOverSignupHandoverForResend } from "../signup-token-store.ts";
|
|
34
|
+
import { MAX_RETURN_PATH_LENGTH } from "../web/return-path.ts";
|
|
34
35
|
|
|
35
36
|
const SignupRequestSchema = z.object({
|
|
36
37
|
email: z.email(),
|
|
@@ -43,6 +44,8 @@ const SignupRequestSchema = z.object({
|
|
|
43
44
|
token: z.string().min(1),
|
|
44
45
|
})
|
|
45
46
|
.optional(),
|
|
47
|
+
// A bad returnTo must not void the signup; the mail builder drops unsafe values.
|
|
48
|
+
returnTo: z.string().max(MAX_RETURN_PATH_LENGTH).optional().catch(undefined),
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
const HANDOVER_GRANT_VERIFY_REASON =
|
|
@@ -102,13 +105,13 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
|
|
|
102
105
|
// No email address here — this fires on every no-op request, PII
|
|
103
106
|
// must not accumulate in logs just for the "was this toggled off"
|
|
104
107
|
// signal a support ticket needs.
|
|
105
|
-
ctx.log?.info("signup-request skipped: self-registration disabled");
|
|
108
|
+
ctx.obs.log?.info("signup-request skipped: self-registration disabled");
|
|
106
109
|
return { isSuccess: true, data: { kind: "no-op" } };
|
|
107
110
|
}
|
|
108
|
-
if (!ctx.redis) {
|
|
111
|
+
if (!ctx.services.redis) {
|
|
109
112
|
return writeFailure(
|
|
110
113
|
new InternalError({
|
|
111
|
-
message: "signup-request requires ctx.redis for the activation-token store",
|
|
114
|
+
message: "signup-request requires ctx.services.redis for the activation-token store",
|
|
112
115
|
}),
|
|
113
116
|
);
|
|
114
117
|
}
|
|
@@ -122,10 +125,10 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
|
|
|
122
125
|
// resend without a fresh `handover` in the payload (the common case:
|
|
123
126
|
// the visitor just retyped their email) must still carry the earlier
|
|
124
127
|
// verified grant over to the new token instead of silently dropping it.
|
|
125
|
-
const priorBinding = await takeOverSignupHandoverForResend(ctx.redis, email);
|
|
128
|
+
const priorBinding = await takeOverSignupHandoverForResend(ctx.services.redis, email);
|
|
126
129
|
|
|
127
130
|
const locale = resolveHandlerMailLocale(ctx, opts.locale);
|
|
128
|
-
const redis = ctx.redis;
|
|
131
|
+
const redis = ctx.services.redis;
|
|
129
132
|
|
|
130
133
|
const issued = await issueSignupActivation({
|
|
131
134
|
redis,
|
|
@@ -133,6 +136,7 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
|
|
|
133
136
|
email,
|
|
134
137
|
appUrl: opts.appUrl,
|
|
135
138
|
timeZone: ctx.tz.user,
|
|
139
|
+
...(event.payload.returnTo !== undefined && { returnTo: event.payload.returnTo }),
|
|
136
140
|
...(opts.tokenTtlMinutes !== undefined && { tokenTtlMinutes: opts.tokenTtlMinutes }),
|
|
137
141
|
...(opts.appName !== undefined && { appName: opts.appName }),
|
|
138
142
|
...(locale !== undefined && { locale }),
|
|
@@ -149,7 +153,7 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
|
|
|
149
153
|
const { entityType, token: grantToken } = event.payload.handover;
|
|
150
154
|
const provider = findSignupHandoverProvider(ctx.registry);
|
|
151
155
|
if (!provider) {
|
|
152
|
-
ctx.log?.warn(
|
|
156
|
+
ctx.obs.log?.warn(
|
|
153
157
|
"signup-request: handover payload present but no signupHandover provider mounted",
|
|
154
158
|
);
|
|
155
159
|
} else {
|
|
@@ -163,10 +167,14 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
|
|
|
163
167
|
if (verified) {
|
|
164
168
|
binding = verified;
|
|
165
169
|
} else {
|
|
166
|
-
ctx.log?.warn("signup-request: handover grant did not verify", {
|
|
170
|
+
ctx.obs.log?.warn("signup-request: handover grant did not verify", {
|
|
171
|
+
entityType,
|
|
172
|
+
});
|
|
167
173
|
}
|
|
168
174
|
} catch {
|
|
169
|
-
ctx.log?.warn("signup-request: handover grant verification threw", {
|
|
175
|
+
ctx.obs.log?.warn("signup-request: handover grant verification threw", {
|
|
176
|
+
entityType,
|
|
177
|
+
});
|
|
170
178
|
}
|
|
171
179
|
}
|
|
172
180
|
}
|
package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts
CHANGED
|
@@ -43,10 +43,12 @@ export function createSwitchTenantMfaGateHandler(
|
|
|
43
43
|
"Runs the MFA gate of the login for a tenant switch and answers with an MFA challenge, a setup requirement, or clearance.",
|
|
44
44
|
handler: async (event, ctx) => {
|
|
45
45
|
const { userId, tenantId } = event.payload;
|
|
46
|
-
const active = await ctx.resolveActiveMembership(userId, tenantId);
|
|
46
|
+
const active = await ctx.auth.resolveActiveMembership(userId, tenantId);
|
|
47
47
|
if (active.kind === "rejected") return noMembership();
|
|
48
48
|
const userRow = parseAuthUserRow(
|
|
49
|
-
await ctx.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
|
|
49
|
+
await ctx.dispatch.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
|
|
50
|
+
id: userId,
|
|
51
|
+
}),
|
|
50
52
|
);
|
|
51
53
|
const roles = buildSessionRoles(parseRoles(userRow?.roles ?? null), active.membership.roles);
|
|
52
54
|
const mfaGate = await gateEnforceMfa(ctx, opts, userId, tenantId, roles);
|
|
@@ -22,9 +22,12 @@ import { type AuthUserRow, parseAuthUserRow } from "../auth-user-row.ts";
|
|
|
22
22
|
import { AUTH_EMAIL_PAYLOAD_RATE_LIMITS } from "../email-rate-limits.ts";
|
|
23
23
|
import type { AuthMailLocale } from "../email-templates.ts";
|
|
24
24
|
import { dispatchMagicLinkMail, resolveHandlerMailLocale } from "../magic-link-mail.ts";
|
|
25
|
+
import { MAX_RETURN_PATH_LENGTH } from "../web/return-path.ts";
|
|
25
26
|
|
|
26
27
|
const RequestTokenSchema = z.object({
|
|
27
28
|
email: z.email(),
|
|
29
|
+
// A bad returnTo must not void the request; the mail builder drops unsafe values.
|
|
30
|
+
returnTo: z.string().max(MAX_RETURN_PATH_LENGTH).optional().catch(undefined),
|
|
28
31
|
});
|
|
29
32
|
|
|
30
33
|
// What the route layer reads off `result.data` after dispatching. Identical
|
|
@@ -67,6 +70,9 @@ export type TokenRequestSpec<TName extends string, TSuccessKind extends string>
|
|
|
67
70
|
// Trigger-less r.notification dispatched via ctx.notify on success; the
|
|
68
71
|
// recipient is an anonymous email, so route:{email} delivers directly.
|
|
69
72
|
readonly notificationType: string;
|
|
73
|
+
// Only flows whose landing page can continue to a page (password reset) put the request's
|
|
74
|
+
// returnTo into the mail link; verification and unlock ignore it.
|
|
75
|
+
readonly forwardsReturnTo: boolean;
|
|
70
76
|
};
|
|
71
77
|
|
|
72
78
|
export type TokenRequestOptions = {
|
|
@@ -105,7 +111,7 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
|
|
|
105
111
|
grants: ["systemIdentity"],
|
|
106
112
|
reason:
|
|
107
113
|
"Anonymous requester has no session — looks up the target user by email via " +
|
|
108
|
-
"ctx.queryAs(SYSTEM, user:findForAuth) to mint the out-of-band token.",
|
|
114
|
+
"ctx.dispatch.queryAs(SYSTEM, user:findForAuth) to mint the out-of-band token.",
|
|
109
115
|
},
|
|
110
116
|
description: spec.description,
|
|
111
117
|
handler: async (event, ctx) => {
|
|
@@ -122,7 +128,7 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
|
|
|
122
128
|
const systemUser = createSystemUser(event.user.tenantId);
|
|
123
129
|
|
|
124
130
|
const user = parseAuthUserRow(
|
|
125
|
-
await ctx.queryAs(systemUser, UserQueries.findForAuth, {
|
|
131
|
+
await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
|
|
126
132
|
email: event.payload.email,
|
|
127
133
|
}),
|
|
128
134
|
);
|
|
@@ -159,6 +165,8 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
|
|
|
159
165
|
timeZone: recipientTimeZone(user, ctx.tz.user),
|
|
160
166
|
...(opts.appName !== undefined && { appName: opts.appName }),
|
|
161
167
|
locale,
|
|
168
|
+
...(spec.forwardsReturnTo &&
|
|
169
|
+
event.payload.returnTo !== undefined && { returnTo: event.payload.returnTo }),
|
|
162
170
|
},
|
|
163
171
|
);
|
|
164
172
|
|
|
@@ -32,7 +32,7 @@ export function createVerifyEmailHandler(opts: VerifyEmailOptions) {
|
|
|
32
32
|
grants: ["systemIdentity"],
|
|
33
33
|
reason:
|
|
34
34
|
"Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
|
|
35
|
-
"target user via ctx.writeAs(SYSTEM, user:update) to set emailVerified.",
|
|
35
|
+
"target user via ctx.dispatch.writeAs(SYSTEM, user:update) to set emailVerified.",
|
|
36
36
|
},
|
|
37
37
|
agent: { expose: false },
|
|
38
38
|
handler: async (event, ctx) => {
|
|
@@ -49,7 +49,8 @@ export function createVerifyEmailHandler(opts: VerifyEmailOptions) {
|
|
|
49
49
|
|
|
50
50
|
return runConfirmTokenFlow<VerifyEmailData>(ctx, verify.userId, verify.expiresAtMs, {
|
|
51
51
|
purpose: "verify",
|
|
52
|
-
redisRequiredMessage:
|
|
52
|
+
redisRequiredMessage:
|
|
53
|
+
"email-verification requires ctx.services.redis to enforce token single-use",
|
|
53
54
|
invalidToken: invalidVerificationToken,
|
|
54
55
|
buildChanges: async () => ({ emailVerified: true }),
|
|
55
56
|
successData: { kind: "verified" },
|
|
@@ -12,6 +12,7 @@ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
|
12
12
|
import { mailT, resolveMailLocale } from "@cosmicdrift/kumiko-framework/i18n";
|
|
13
13
|
import { type AuthMailLocale, tokenMailExpiry } from "./email-templates.ts";
|
|
14
14
|
import { authMailTemplateLocale } from "./mail-notifications.ts";
|
|
15
|
+
import { isSafeReturnPath, RETURN_TO_QUERY_PARAM } from "./web/return-path.ts";
|
|
15
16
|
|
|
16
17
|
// Per-flow constants: which notification type to dispatch. fallbackAppName
|
|
17
18
|
// replaces the translated default when the flow has no app name of its own.
|
|
@@ -38,15 +39,23 @@ export type MagicLinkMailParams = {
|
|
|
38
39
|
readonly timeZone?: string;
|
|
39
40
|
// Invite mails name the role the recipient is invited as.
|
|
40
41
|
readonly role?: string;
|
|
42
|
+
/** Page the landing page should lead to afterwards; an unsafe value is left out of the link. */
|
|
43
|
+
readonly returnTo?: string;
|
|
41
44
|
};
|
|
42
45
|
|
|
43
46
|
function resolveAppUrl(appUrl: string | ((locale: string) => string), locale: string): string {
|
|
44
47
|
return typeof appUrl === "function" ? appUrl(locale) : appUrl;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
function
|
|
50
|
+
function appendTokenAndReturnTo(
|
|
51
|
+
appUrl: string,
|
|
52
|
+
token: string,
|
|
53
|
+
returnTo: string | undefined,
|
|
54
|
+
): string {
|
|
55
|
+
const query = new URLSearchParams({ token });
|
|
56
|
+
if (isSafeReturnPath(returnTo)) query.set(RETURN_TO_QUERY_PARAM, returnTo);
|
|
48
57
|
const sep = appUrl.includes("?") ? "&" : "?";
|
|
49
|
-
return `${appUrl}${sep}
|
|
58
|
+
return `${appUrl}${sep}${query.toString()}`;
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
// X-Locale on the request wins over the handler's static opts.locale, then
|
|
@@ -93,7 +102,11 @@ export async function dispatchMagicLinkMail(
|
|
|
93
102
|
data: {
|
|
94
103
|
appName:
|
|
95
104
|
params.appName ?? spec.fallbackAppName ?? mailT(templateLocale, "auth.mail.appNameDefault"),
|
|
96
|
-
actionUrl:
|
|
105
|
+
actionUrl: appendTokenAndReturnTo(
|
|
106
|
+
resolveAppUrl(params.appUrl, urlLocale),
|
|
107
|
+
params.token,
|
|
108
|
+
params.returnTo,
|
|
109
|
+
),
|
|
97
110
|
validFor,
|
|
98
111
|
validUntil,
|
|
99
112
|
...(params.role !== undefined && { role: params.role }),
|
|
@@ -20,10 +20,14 @@ import {
|
|
|
20
20
|
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
21
21
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
22
22
|
import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
23
|
-
import
|
|
23
|
+
import {
|
|
24
|
+
createSystemUser,
|
|
25
|
+
type SessionUser,
|
|
26
|
+
SYSTEM_TENANT_ID,
|
|
27
|
+
type TenantId,
|
|
28
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
24
29
|
import { ConflictError } from "@cosmicdrift/kumiko-framework/errors";
|
|
25
30
|
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
26
|
-
import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
|
|
27
31
|
import { hashPassword } from "../shared/index.ts";
|
|
28
32
|
// kumiko-lint-ignore cross-feature-import auth-tests need user+tenant seed-helpers
|
|
29
33
|
import { type SeedTenantHooks, seedTenant, seedTenantMembership } from "../tenant/seeding.ts";
|
|
@@ -224,10 +228,10 @@ export async function seedAdmin(
|
|
|
224
228
|
db: DbConnection,
|
|
225
229
|
options: SeedAdminOptions,
|
|
226
230
|
): Promise<{ id: string }> {
|
|
227
|
-
const
|
|
231
|
+
const byOption = options.by === undefined ? {} : { by: options.by };
|
|
228
232
|
|
|
229
233
|
for (const m of options.memberships) {
|
|
230
|
-
await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName,
|
|
234
|
+
await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, ...byOption });
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
const { id: userId } = await seedUserWithPassword(db, {
|
|
@@ -236,7 +240,7 @@ export async function seedAdmin(
|
|
|
236
240
|
displayName: options.displayName,
|
|
237
241
|
...(options.globalRoles !== undefined && { roles: options.globalRoles }),
|
|
238
242
|
...(options.emailVerified !== undefined && { emailVerified: options.emailVerified }),
|
|
239
|
-
|
|
243
|
+
...byOption,
|
|
240
244
|
});
|
|
241
245
|
|
|
242
246
|
for (const m of options.memberships) {
|
|
@@ -244,7 +248,7 @@ export async function seedAdmin(
|
|
|
244
248
|
userId,
|
|
245
249
|
tenantId: m.tenantId,
|
|
246
250
|
roles: m.roles,
|
|
247
|
-
|
|
251
|
+
...byOption,
|
|
248
252
|
});
|
|
249
253
|
}
|
|
250
254
|
|
|
@@ -372,7 +376,8 @@ export async function seedAdminGuarded(
|
|
|
372
376
|
const existingId = await findExistingUserIdForEmail(db, kms, options.email);
|
|
373
377
|
if (existingId === undefined) return seedAdmin(db, options);
|
|
374
378
|
|
|
375
|
-
const
|
|
379
|
+
const byOption = options.by === undefined ? {} : { by: options.by };
|
|
380
|
+
const by = options.by ?? createSystemUser(SYSTEM_TENANT_ID);
|
|
376
381
|
const existingRow = await fetchOne(db, userTable, { id: existingId });
|
|
377
382
|
if (existingRow) {
|
|
378
383
|
// Re-write the email so email_bidx is recomputed; without it the account
|
|
@@ -384,12 +389,12 @@ export async function seedAdminGuarded(
|
|
|
384
389
|
}
|
|
385
390
|
}
|
|
386
391
|
for (const m of options.memberships) {
|
|
387
|
-
await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName,
|
|
392
|
+
await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, ...byOption });
|
|
388
393
|
await seedTenantMembership(db, {
|
|
389
394
|
userId: existingId,
|
|
390
395
|
tenantId: m.tenantId,
|
|
391
396
|
roles: m.roles,
|
|
392
|
-
|
|
397
|
+
...byOption,
|
|
393
398
|
});
|
|
394
399
|
}
|
|
395
400
|
return { id: existingId };
|
|
@@ -28,6 +28,7 @@ export type IssueSignupActivationArgs = {
|
|
|
28
28
|
readonly locale?: string;
|
|
29
29
|
readonly timeZone?: string;
|
|
30
30
|
readonly tokenTtlMinutes?: number;
|
|
31
|
+
readonly returnTo?: string;
|
|
31
32
|
/** Runs after the token is stored and before the mail goes out. */
|
|
32
33
|
readonly onTokenStored?: (stored: {
|
|
33
34
|
readonly token: string;
|
|
@@ -75,6 +76,7 @@ export async function issueSignupActivation(
|
|
|
75
76
|
...(args.timeZone !== undefined && { timeZone: args.timeZone }),
|
|
76
77
|
...(args.appName !== undefined && { appName: args.appName }),
|
|
77
78
|
...(args.locale !== undefined && { locale: args.locale }),
|
|
79
|
+
...(args.returnTo !== undefined && { returnTo: args.returnTo }),
|
|
78
80
|
},
|
|
79
81
|
);
|
|
80
82
|
|