@cosmicdrift/kumiko-samples 0.389.0 → 0.390.1
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 +27 -15
- package/packages/bundled-features/src/audit/feature.ts +1 -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 +6 -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/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 +12 -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 +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
- package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
- 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 +9 -3
- 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 +1 -1
- package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
- package/packages/bundled-features/src/cap-counter/feature.ts +2 -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/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/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 +4 -4
- package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +8 -8
- 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/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/delivery-service.ts +6 -5
- package/packages/bundled-features/src/delivery/feature.ts +2 -2
- package/packages/bundled-features/src/delivery/jobs.ts +11 -11
- package/packages/bundled-features/src/delivery/testing.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +1 -1
- 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/inbound-mail-foundation/events.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -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/changes.json +6 -0
- 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/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/legal-pages/README.md +1 -1
- package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
- package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
- package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -1
- package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +1 -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/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 +1 -1
- package/packages/bundled-features/src/seo/feature.ts +1 -1
- package/packages/bundled-features/src/sessions/changes.json +12 -0
- package/packages/bundled-features/src/sessions/feature.ts +1 -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/screens.ts +8 -4
- 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 +12 -2
- package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -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/feature.ts +1 -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/template-resolver/api.ts +3 -3
- package/packages/bundled-features/src/template-resolver/changes.json +18 -0
- 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/overview.query.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
- 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 +12 -2
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +18 -13
- 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/placeholder-line-editor.tsx +19 -6
- 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/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/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/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 +7 -3
- 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/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 +24 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +29 -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 +2 -0
- 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 +10 -0
- package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -1
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
- 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 +9 -4
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +6 -1
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -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/package.json +1 -1
- 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/server.ts +2 -2
- package/samples/apps/ui-walkthrough/package.json +1 -1
- package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
- package/samples/apps/use-all-bundled/package.json +1 -1
- package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
- package/samples/apps/user-data-rights-demo/README.md +1 -1
- package/samples/apps/user-data-rights-demo/package.json +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/lane-routing/package.json +1 -1
- package/samples/recipes/legal-pages/README.md +2 -2
- package/samples/recipes/lifecycle-hooks/README.md +2 -2
- package/samples/recipes/lifecycle-hooks/src/feature.ts +1 -1
- package/samples/recipes/managed-config/README.md +3 -3
- 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 +1 -1
- package/samples/recipes/state-machine/src/reducer.ts +1 -1
- package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
- package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
|
@@ -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
|
},
|
|
@@ -105,13 +105,13 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
|
|
|
105
105
|
// No email address here — this fires on every no-op request, PII
|
|
106
106
|
// must not accumulate in logs just for the "was this toggled off"
|
|
107
107
|
// signal a support ticket needs.
|
|
108
|
-
ctx.log?.info("signup-request skipped: self-registration disabled");
|
|
108
|
+
ctx.obs.log?.info("signup-request skipped: self-registration disabled");
|
|
109
109
|
return { isSuccess: true, data: { kind: "no-op" } };
|
|
110
110
|
}
|
|
111
|
-
if (!ctx.redis) {
|
|
111
|
+
if (!ctx.services.redis) {
|
|
112
112
|
return writeFailure(
|
|
113
113
|
new InternalError({
|
|
114
|
-
message: "signup-request requires ctx.redis for the activation-token store",
|
|
114
|
+
message: "signup-request requires ctx.services.redis for the activation-token store",
|
|
115
115
|
}),
|
|
116
116
|
);
|
|
117
117
|
}
|
|
@@ -125,10 +125,10 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
|
|
|
125
125
|
// resend without a fresh `handover` in the payload (the common case:
|
|
126
126
|
// the visitor just retyped their email) must still carry the earlier
|
|
127
127
|
// verified grant over to the new token instead of silently dropping it.
|
|
128
|
-
const priorBinding = await takeOverSignupHandoverForResend(ctx.redis, email);
|
|
128
|
+
const priorBinding = await takeOverSignupHandoverForResend(ctx.services.redis, email);
|
|
129
129
|
|
|
130
130
|
const locale = resolveHandlerMailLocale(ctx, opts.locale);
|
|
131
|
-
const redis = ctx.redis;
|
|
131
|
+
const redis = ctx.services.redis;
|
|
132
132
|
|
|
133
133
|
const issued = await issueSignupActivation({
|
|
134
134
|
redis,
|
|
@@ -153,7 +153,7 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
|
|
|
153
153
|
const { entityType, token: grantToken } = event.payload.handover;
|
|
154
154
|
const provider = findSignupHandoverProvider(ctx.registry);
|
|
155
155
|
if (!provider) {
|
|
156
|
-
ctx.log?.warn(
|
|
156
|
+
ctx.obs.log?.warn(
|
|
157
157
|
"signup-request: handover payload present but no signupHandover provider mounted",
|
|
158
158
|
);
|
|
159
159
|
} else {
|
|
@@ -167,10 +167,14 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
|
|
|
167
167
|
if (verified) {
|
|
168
168
|
binding = verified;
|
|
169
169
|
} else {
|
|
170
|
-
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
|
+
});
|
|
171
173
|
}
|
|
172
174
|
} catch {
|
|
173
|
-
ctx.log?.warn("signup-request: handover grant verification threw", {
|
|
175
|
+
ctx.obs.log?.warn("signup-request: handover grant verification threw", {
|
|
176
|
+
entityType,
|
|
177
|
+
});
|
|
174
178
|
}
|
|
175
179
|
}
|
|
176
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);
|
|
@@ -111,7 +111,7 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
|
|
|
111
111
|
grants: ["systemIdentity"],
|
|
112
112
|
reason:
|
|
113
113
|
"Anonymous requester has no session — looks up the target user by email via " +
|
|
114
|
-
"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.",
|
|
115
115
|
},
|
|
116
116
|
description: spec.description,
|
|
117
117
|
handler: async (event, ctx) => {
|
|
@@ -128,7 +128,7 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
|
|
|
128
128
|
const systemUser = createSystemUser(event.user.tenantId);
|
|
129
129
|
|
|
130
130
|
const user = parseAuthUserRow(
|
|
131
|
-
await ctx.queryAs(systemUser, UserQueries.findForAuth, {
|
|
131
|
+
await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
|
|
132
132
|
email: event.payload.email,
|
|
133
133
|
}),
|
|
134
134
|
);
|
|
@@ -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" },
|
|
@@ -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 };
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { SessionUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
14
14
|
import { defineFeature, type Registry } from "@cosmicdrift/kumiko-framework/engine";
|
|
15
|
+
import { systemScopedRunner } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
|
|
15
16
|
import {
|
|
16
17
|
validateSessionStoreMultiplicity,
|
|
17
18
|
validateTenantExistenceMultiplicity,
|
|
@@ -90,6 +91,14 @@ export function createAuthFoundationFeature() {
|
|
|
90
91
|
);
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
// Providers run before any tenant or session exists (token verify, host to tenant), so their reads are cross-tenant by nature.
|
|
95
|
+
function systemScopedDeps<T extends AuthProviderBuildDeps>(deps: T): T {
|
|
96
|
+
return {
|
|
97
|
+
...deps,
|
|
98
|
+
db: systemScopedRunner(deps.db, "auth lookups run before a tenant is established"),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
/**
|
|
94
103
|
* Tries every registered tokenVerifier provider's shape against `rawToken`
|
|
95
104
|
* in registration order and calls the first match's verifier. Returns null
|
|
@@ -104,7 +113,7 @@ export async function resolveTokenVerifier(
|
|
|
104
113
|
for (const usage of usages) {
|
|
105
114
|
if (!isAuthProviderPlugin(usage.options)) continue;
|
|
106
115
|
if (!tokenShapeMatches(usage.options.shape, rawToken)) continue;
|
|
107
|
-
const verify = await usage.options.build(deps);
|
|
116
|
+
const verify = await usage.options.build(systemScopedDeps(deps));
|
|
108
117
|
return verify(rawToken);
|
|
109
118
|
}
|
|
110
119
|
return null;
|
|
@@ -125,7 +134,7 @@ export async function resolveSessionStore(
|
|
|
125
134
|
"[auth-foundation] no sessionStore provider registered — did the boot validator run?",
|
|
126
135
|
);
|
|
127
136
|
}
|
|
128
|
-
return usage.options.build(deps);
|
|
137
|
+
return usage.options.build(systemScopedDeps(deps));
|
|
129
138
|
}
|
|
130
139
|
|
|
131
140
|
export type ResolvedTenantResolver = {
|
|
@@ -144,7 +153,7 @@ export async function resolveTenantResolver(
|
|
|
144
153
|
"[auth-foundation] tenantResolver provider registered without a valid TenantResolverProvider",
|
|
145
154
|
);
|
|
146
155
|
}
|
|
147
|
-
const resolve = await usage.options.build(deps);
|
|
156
|
+
const resolve = await usage.options.build(systemScopedDeps(deps));
|
|
148
157
|
return { resolve, trust: usage.options.trust };
|
|
149
158
|
}
|
|
150
159
|
|
|
@@ -159,5 +168,5 @@ export async function resolveTenantExistence(
|
|
|
159
168
|
"[auth-foundation] tenantExistence provider registered without a valid TenantExistenceProvider",
|
|
160
169
|
);
|
|
161
170
|
}
|
|
162
|
-
return usage.options.build(deps);
|
|
171
|
+
return usage.options.build(systemScopedDeps(deps));
|
|
163
172
|
}
|
|
@@ -45,20 +45,20 @@ export function createDisableHandler(opts: DisableOptions) {
|
|
|
45
45
|
if (!row) return mfaNotEnabled();
|
|
46
46
|
|
|
47
47
|
// Fail closed, matching verify.write.ts/enable-confirm-preauth.write.ts
|
|
48
|
-
// without ctx.redis, verifyMfaFactor's TOTP-replay burn is skipped, and
|
|
48
|
+
// without ctx.services.redis, verifyMfaFactor's TOTP-replay burn is skipped, and
|
|
49
49
|
// a code observed once (phishing proxy/shoulder-surfing) could be
|
|
50
50
|
// replayed within the ±1-step window to disable MFA outright.
|
|
51
|
-
// "no ctx.redis means no one gets through MFA login" doesn't cover
|
|
51
|
+
// "no ctx.services.redis means no one gets through MFA login" doesn't cover
|
|
52
52
|
// this route — it's session-authed, and JWTs issued before Redis was
|
|
53
53
|
// unwired stay valid until they expire.
|
|
54
|
-
if (!ctx.redis) {
|
|
54
|
+
if (!ctx.services.redis) {
|
|
55
55
|
return writeFailure(
|
|
56
56
|
new InternalError({
|
|
57
|
-
message: "disable (mfa) requires ctx.redis for TOTP replay protection",
|
|
57
|
+
message: "disable (mfa) requires ctx.services.redis for TOTP replay protection",
|
|
58
58
|
}),
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
|
-
const replay = { redis: ctx.redis, userId: event.user.id };
|
|
61
|
+
const replay = { redis: ctx.services.redis, userId: event.user.id };
|
|
62
62
|
const verify = await verifyMfaFactor(row, event.payload.code, replay);
|
|
63
63
|
if (!verify.ok) return invalidTotpCode();
|
|
64
64
|
|
|
@@ -73,8 +73,8 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
|
|
|
73
73
|
escapeHatch: {
|
|
74
74
|
grants: ["systemIdentity", "unsafeRaw"],
|
|
75
75
|
reason:
|
|
76
|
-
"Pre-auth MFA enrollment step has no session yet — re-checks status via ctx.queryAs(SYSTEM, " +
|
|
77
|
-
"user:findForAuth) and membership via ctx.resolveActiveMembership for the setup token's user. " +
|
|
76
|
+
"Pre-auth MFA enrollment step has no session yet — re-checks status via ctx.dispatch.queryAs(SYSTEM, " +
|
|
77
|
+
"user:findForAuth) and membership via ctx.auth.resolveActiveMembership for the setup token's user. " +
|
|
78
78
|
"Also reads the MFA enrollment of the tenant named in the signed login/setup token, not the " +
|
|
79
79
|
"guest dispatch tenant.",
|
|
80
80
|
},
|
|
@@ -96,15 +96,15 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
|
|
|
96
96
|
// endpoint has NO gate before it (access: { roles: ["anonymous"] }, no JWT,
|
|
97
97
|
// no prior challenge-token from a successful login) — its only secret
|
|
98
98
|
// is a 6-digit TOTP code. Silently skipping the brute-force cap and
|
|
99
|
-
// single-use burn when ctx.redis is absent would let the setup token
|
|
99
|
+
// single-use burn when ctx.services.redis is absent would let the setup token
|
|
100
100
|
// be guessed against unboundedly until its TTL expires. The route's
|
|
101
101
|
// IP rate limiter is not a substitute: it's IP-scoped and resets on
|
|
102
102
|
// every success.
|
|
103
|
-
if (!ctx.redis) {
|
|
103
|
+
if (!ctx.services.redis) {
|
|
104
104
|
return writeFailure(
|
|
105
105
|
new InternalError({
|
|
106
106
|
message:
|
|
107
|
-
"enable-confirm-preauth requires ctx.redis for the brute-force cap and single-use burn",
|
|
107
|
+
"enable-confirm-preauth requires ctx.services.redis for the brute-force cap and single-use burn",
|
|
108
108
|
}),
|
|
109
109
|
);
|
|
110
110
|
}
|
|
@@ -112,7 +112,7 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
|
|
|
112
112
|
// Same guessing surface as /auth/mfa/verify (a short TOTP code) and
|
|
113
113
|
// the same per-userId cap — deliberately shared counter, see
|
|
114
114
|
// mfa-verify-attempts.ts.
|
|
115
|
-
const state = await getMfaVerifyLockoutState(ctx.redis, userId);
|
|
115
|
+
const state = await getMfaVerifyLockoutState(ctx.services.redis, userId);
|
|
116
116
|
if (state?.lockedUntil !== null && state?.lockedUntil !== undefined) {
|
|
117
117
|
const now = Date.now();
|
|
118
118
|
if (state.lockedUntil > now) {
|
|
@@ -123,18 +123,28 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
|
|
|
123
123
|
|
|
124
124
|
const secret = base32Decode(verify.payload.totpSecretBase32);
|
|
125
125
|
if (verifyTotp(secret, event.payload.code) === false) {
|
|
126
|
-
await recordFailedMfaVerifyAttempt(ctx.redis, userId, maxAttempts, lockoutMinutes);
|
|
126
|
+
await recordFailedMfaVerifyAttempt(ctx.services.redis, userId, maxAttempts, lockoutMinutes);
|
|
127
127
|
return invalidTotpCode();
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// Burn the setup token on the first successful confirm — same
|
|
131
131
|
// single-use guarantee as enable-confirm.write.ts.
|
|
132
|
-
const burnResult = await burnToken(
|
|
132
|
+
const burnResult = await burnToken(
|
|
133
|
+
ctx.services.redis,
|
|
134
|
+
"mfa-setup",
|
|
135
|
+
userId,
|
|
136
|
+
verify.expiresAtMs,
|
|
137
|
+
);
|
|
133
138
|
if (burnResult === "already-used") return invalidSetupToken();
|
|
134
139
|
|
|
135
140
|
// "system" mode: no session exists yet, tenantId comes from the
|
|
136
141
|
// verified token, mirroring enable-start-preauth.write.ts.
|
|
137
|
-
const scopedDb = createTenantDb({
|
|
142
|
+
const scopedDb = createTenantDb({
|
|
143
|
+
db: ctx.db.unsafeRaw(),
|
|
144
|
+
tenantId,
|
|
145
|
+
mode: "system",
|
|
146
|
+
reason: "pre-auth MFA setup has no session, the tenant comes from the verified setup token",
|
|
147
|
+
});
|
|
138
148
|
const scopedUser: SessionUser = { id: userId, tenantId, roles: ["User"] };
|
|
139
149
|
const existing = await findUserMfaRow(scopedDb, scopedUser);
|
|
140
150
|
if (existing) return mfaAlreadyEnabled();
|
|
@@ -154,7 +164,7 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
|
|
|
154
164
|
// existing userId-unique-constraint backstop between the
|
|
155
165
|
// `existing` check above and executor.create() below.
|
|
156
166
|
const systemUser = createSystemUser(tenantId, ["SystemAdmin"]);
|
|
157
|
-
const userRow = (await ctx.queryAs(systemUser, UserQueries.findForAuth, {
|
|
167
|
+
const userRow = (await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
|
|
158
168
|
id: userId,
|
|
159
169
|
})) as {
|
|
160
170
|
roles?: string | null;
|
|
@@ -174,7 +184,7 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
|
|
|
174
184
|
|
|
175
185
|
const globalRoles = parseRoles(userRow?.roles ?? null);
|
|
176
186
|
|
|
177
|
-
const active = await ctx.resolveActiveMembership(userId, tenantId);
|
|
187
|
+
const active = await ctx.auth.resolveActiveMembership(userId, tenantId);
|
|
178
188
|
if (active.kind === "rejected") return invalidSetupToken();
|
|
179
189
|
|
|
180
190
|
const result = await executor.create(
|
|
@@ -190,8 +200,8 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
|
|
|
190
200
|
);
|
|
191
201
|
if (!result.isSuccess) return result;
|
|
192
202
|
|
|
193
|
-
// ctx.redis is guaranteed by the fail-closed check above.
|
|
194
|
-
await clearMfaVerifyAttempts(ctx.redis, userId);
|
|
203
|
+
// ctx.services.redis is guaranteed by the fail-closed check above.
|
|
204
|
+
await clearMfaVerifyAttempts(ctx.services.redis, userId);
|
|
195
205
|
|
|
196
206
|
// Re-derive the full session the way verify.write.ts does — the
|
|
197
207
|
// setupToken only proves "password + new TOTP secret", not roles;
|
|
@@ -214,7 +224,7 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
|
|
|
214
224
|
...sessionTimezoneField(userRow?.timezone),
|
|
215
225
|
...sessionLocaleField(userRow?.locale),
|
|
216
226
|
};
|
|
217
|
-
const claims = await ctx.
|
|
227
|
+
const claims = await ctx.auth.resolveClaims(baseSession);
|
|
218
228
|
const session: SessionUser =
|
|
219
229
|
Object.keys(claims).length > 0 ? { ...baseSession, claims } : baseSession;
|
|
220
230
|
|
|
@@ -63,10 +63,11 @@ export function createEnableConfirmHandler(opts: EnableConfirmOptions) {
|
|
|
63
63
|
// second seeing invalidSetupToken(). Checked before the TOTP verify
|
|
64
64
|
// for the same reason the other two handlers check it early: fail
|
|
65
65
|
// closed before doing any of the guarded work, not partway through.
|
|
66
|
-
if (!ctx.redis) {
|
|
66
|
+
if (!ctx.services.redis) {
|
|
67
67
|
return writeFailure(
|
|
68
68
|
new InternalError({
|
|
69
|
-
message:
|
|
69
|
+
message:
|
|
70
|
+
"enable-confirm requires ctx.services.redis for the single-use setup-token burn",
|
|
70
71
|
}),
|
|
71
72
|
);
|
|
72
73
|
}
|
|
@@ -86,7 +87,12 @@ export function createEnableConfirmHandler(opts: EnableConfirmOptions) {
|
|
|
86
87
|
// concurrent mfa_already_enabled() write) is meant to leave the burn
|
|
87
88
|
// standing. Add unburnToken here if create-failure retries turn out
|
|
88
89
|
// to matter.
|
|
89
|
-
const burnResult = await burnToken(
|
|
90
|
+
const burnResult = await burnToken(
|
|
91
|
+
ctx.services.redis,
|
|
92
|
+
"mfa-setup",
|
|
93
|
+
event.user.id,
|
|
94
|
+
verify.expiresAtMs,
|
|
95
|
+
);
|
|
90
96
|
if (burnResult === "already-used") return invalidSetupToken();
|
|
91
97
|
|
|
92
98
|
const existing = await findUserMfaRow(ctx.db, event.user);
|
|
@@ -62,7 +62,12 @@ export function createEnableStartPreauthHandler(opts: EnableStartPreauthOptions)
|
|
|
62
62
|
// "system" mode: the guest dispatch identity's own tenantId is
|
|
63
63
|
// meaningless here — the preauthSetupToken is the source of truth
|
|
64
64
|
// for which tenant's row to read, mirroring verify.write.ts.
|
|
65
|
-
const scopedDb = createTenantDb({
|
|
65
|
+
const scopedDb = createTenantDb({
|
|
66
|
+
db: ctx.db.unsafeRaw(),
|
|
67
|
+
tenantId,
|
|
68
|
+
mode: "system",
|
|
69
|
+
reason: "pre-auth MFA setup has no session, the tenant comes from the verified setup token",
|
|
70
|
+
});
|
|
66
71
|
const scopedUser: SessionUser = { id: userId, tenantId, roles: ["User"] };
|
|
67
72
|
const existing = await findUserMfaRow(scopedDb, scopedUser);
|
|
68
73
|
if (existing) return mfaAlreadyEnabled();
|
|
@@ -61,7 +61,9 @@ export function createEnableStartHandler(opts: EnableStartOptions) {
|
|
|
61
61
|
if (!accountLabel) {
|
|
62
62
|
// The declarative secretMint screen has no client component left to
|
|
63
63
|
// read the session email off — derive the otpauth label server-side.
|
|
64
|
-
accountLabel = readableAccountLabel(
|
|
64
|
+
accountLabel = readableAccountLabel(
|
|
65
|
+
await ctx.dispatch.queryAs(event.user, UserQueries.me, {}),
|
|
66
|
+
);
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
const secret = generateTotpSecret();
|
|
@@ -88,13 +88,13 @@ export async function mfaReencryptJob(
|
|
|
88
88
|
// hoisted function declarations below capture this — pin the narrowed
|
|
89
89
|
// type explicitly so TS keeps it inside the closures.
|
|
90
90
|
const cipher: EnvelopeCipher = maybeCipher;
|
|
91
|
-
if (!ctx.masterKeyProvider) {
|
|
91
|
+
if (!ctx.services.masterKeyProvider) {
|
|
92
92
|
throw new InternalError({
|
|
93
93
|
message:
|
|
94
|
-
"[auth-mfa:reencrypt] ctx.masterKeyProvider missing — wire it via extraContext.masterKeyProvider at boot.",
|
|
94
|
+
"[auth-mfa:reencrypt] ctx.services.masterKeyProvider missing — wire it via extraContext.masterKeyProvider at boot.",
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
|
-
const provider = ctx.masterKeyProvider;
|
|
97
|
+
const provider = ctx.services.masterKeyProvider;
|
|
98
98
|
// Undefined = per-subject crypto-shredding isn't configured for this app —
|
|
99
99
|
// both fields are then plain envelope strings, no PII layer to peel.
|
|
100
100
|
const piiKms = configuredPiiSubjectKms();
|
|
@@ -109,7 +109,12 @@ export async function mfaReencryptJob(
|
|
|
109
109
|
function tdbFor(tenantId: TenantId): TenantDb {
|
|
110
110
|
let existing = tdbCache.get(tenantId);
|
|
111
111
|
if (!existing) {
|
|
112
|
-
existing = createTenantDb({
|
|
112
|
+
existing = createTenantDb({
|
|
113
|
+
db,
|
|
114
|
+
tenantId,
|
|
115
|
+
mode: "system",
|
|
116
|
+
reason: "MFA key re-encryption job rewrites enrollments of every tenant",
|
|
117
|
+
});
|
|
113
118
|
tdbCache.set(tenantId, existing);
|
|
114
119
|
}
|
|
115
120
|
return existing;
|
|
@@ -158,7 +163,7 @@ export async function mfaReencryptJob(
|
|
|
158
163
|
const totpClass = classifyStoredEnvelope(envelopeValues.totpSecret, targetVersion);
|
|
159
164
|
const recoveryClass = classifyStoredEnvelope(envelopeValues.recoveryCodes, targetVersion);
|
|
160
165
|
if (totpClass === "unrecognized" || recoveryClass === "unrecognized") {
|
|
161
|
-
ctx.log?.warn?.(
|
|
166
|
+
ctx.obs.log?.warn?.(
|
|
162
167
|
`[auth-mfa:reencrypt] row ${row.id} has a field that is not a current-cipher envelope, not re-encryptable`,
|
|
163
168
|
);
|
|
164
169
|
return "failed";
|
|
@@ -193,7 +198,7 @@ export async function mfaReencryptJob(
|
|
|
193
198
|
// the row now holds a fresh write, already fine.
|
|
194
199
|
if (!result.isSuccess) {
|
|
195
200
|
if (result.error.code === "version_conflict") return "skipped";
|
|
196
|
-
ctx.log?.warn?.(`[auth-mfa:reencrypt] executor rejected row ${row.id}`, {
|
|
201
|
+
ctx.obs.log?.warn?.(`[auth-mfa:reencrypt] executor rejected row ${row.id}`, {
|
|
197
202
|
code: result.error.code,
|
|
198
203
|
});
|
|
199
204
|
return "failed";
|
|
@@ -206,9 +211,9 @@ export async function mfaReencryptJob(
|
|
|
206
211
|
migrateRow,
|
|
207
212
|
maxFailures,
|
|
208
213
|
deadlineAt: deadline,
|
|
209
|
-
signal: ctx.signal,
|
|
214
|
+
signal: ctx.lifecycle.signal,
|
|
210
215
|
onRowError: (row, err) => {
|
|
211
|
-
ctx.log?.warn?.(`[auth-mfa:reencrypt] failed to re-encrypt row ${row.id}`, { err });
|
|
216
|
+
ctx.obs.log?.warn?.(`[auth-mfa:reencrypt] failed to re-encrypt row ${row.id}`, { err });
|
|
212
217
|
},
|
|
213
218
|
});
|
|
214
219
|
|
|
@@ -219,5 +224,5 @@ export async function mfaReencryptJob(
|
|
|
219
224
|
batchesProcessed: outcome.batchesProcessed,
|
|
220
225
|
stoppedReason: outcome.stoppedReason,
|
|
221
226
|
};
|
|
222
|
-
ctx.log?.info?.(`[auth-mfa:reencrypt] complete: ${JSON.stringify(result)}`);
|
|
227
|
+
ctx.obs.log?.info?.(`[auth-mfa:reencrypt] complete: ${JSON.stringify(result)}`);
|
|
223
228
|
}
|
|
@@ -48,18 +48,19 @@ export function createRegenerateRecoveryHandler(opts: RegenerateRecoveryOptions)
|
|
|
48
48
|
// afterward.
|
|
49
49
|
//
|
|
50
50
|
// Fail closed, matching verify.write.ts/enable-confirm-preauth.write.ts:
|
|
51
|
-
// without ctx.redis, verifyMfaFactor's TOTP-replay burn is skipped,
|
|
51
|
+
// without ctx.services.redis, verifyMfaFactor's TOTP-replay burn is skipped,
|
|
52
52
|
// letting an observed code be replayed within the ±1-step window to
|
|
53
53
|
// regenerate (and thus invalidate) recovery codes — a session-authed
|
|
54
54
|
// route, so pre-unwiring JWTs stay a valid attack path.
|
|
55
|
-
if (!ctx.redis) {
|
|
55
|
+
if (!ctx.services.redis) {
|
|
56
56
|
return writeFailure(
|
|
57
57
|
new InternalError({
|
|
58
|
-
message:
|
|
58
|
+
message:
|
|
59
|
+
"regenerate-recovery (mfa) requires ctx.services.redis for TOTP replay protection",
|
|
59
60
|
}),
|
|
60
61
|
);
|
|
61
62
|
}
|
|
62
|
-
const replay = { redis: ctx.redis, userId: event.user.id };
|
|
63
|
+
const replay = { redis: ctx.services.redis, userId: event.user.id };
|
|
63
64
|
const verify = await verifyMfaFactor(row, event.payload.code, replay);
|
|
64
65
|
if (!verify.ok) return invalidTotpCode();
|
|
65
66
|
|