@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
|
@@ -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
|
|
|
@@ -50,8 +50,8 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
|
|
|
50
50
|
escapeHatch: {
|
|
51
51
|
grants: ["systemIdentity", "unsafeRaw"],
|
|
52
52
|
reason:
|
|
53
|
-
"Pre-auth MFA step has no session yet — re-derives it via ctx.queryAs(SYSTEM, " +
|
|
54
|
-
"user:findForAuth) and ctx.resolveActiveMembership for the user the challenge token names. " +
|
|
53
|
+
"Pre-auth MFA step has no session yet — re-derives it via ctx.dispatch.queryAs(SYSTEM, " +
|
|
54
|
+
"user:findForAuth) and ctx.auth.resolveActiveMembership for the user the challenge token names. " +
|
|
55
55
|
"Also reads the MFA enrollment of the tenant named in the signed login/setup token, not the " +
|
|
56
56
|
"guest dispatch tenant.",
|
|
57
57
|
},
|
|
@@ -71,14 +71,15 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
|
|
|
71
71
|
// this route sits behind a challenge token from a successful login,
|
|
72
72
|
// but its own secret is still just a short TOTP/recovery code —
|
|
73
73
|
// silently skipping the brute-force cap and single-use burn when
|
|
74
|
-
// ctx.redis is absent would let a stolen challenge token be guessed
|
|
74
|
+
// ctx.services.redis is absent would let a stolen challenge token be guessed
|
|
75
75
|
// against unboundedly, and let a recovery code be replayed via a
|
|
76
76
|
// reissued challenge (mfa-verify-attempts.ts's counter is deliberately
|
|
77
77
|
// NOT reset by reissuance, precisely to survive this).
|
|
78
|
-
if (!ctx.redis) {
|
|
78
|
+
if (!ctx.services.redis) {
|
|
79
79
|
return writeFailure(
|
|
80
80
|
new InternalError({
|
|
81
|
-
message:
|
|
81
|
+
message:
|
|
82
|
+
"verify (mfa) requires ctx.services.redis for the brute-force cap and single-use burn",
|
|
82
83
|
}),
|
|
83
84
|
);
|
|
84
85
|
}
|
|
@@ -86,7 +87,7 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
|
|
|
86
87
|
// Per-account brute-force cap — survives challenge-token reissuance
|
|
87
88
|
// on purpose (see mfa-verify-attempts.ts header). Checked BEFORE the
|
|
88
89
|
// TOTP/recovery verify so a locked account can't be guessed against.
|
|
89
|
-
const state = await getMfaVerifyLockoutState(ctx.redis, userId);
|
|
90
|
+
const state = await getMfaVerifyLockoutState(ctx.services.redis, userId);
|
|
90
91
|
if (state?.lockedUntil !== null && state?.lockedUntil !== undefined) {
|
|
91
92
|
const now = Date.now();
|
|
92
93
|
if (state.lockedUntil > now) {
|
|
@@ -98,7 +99,12 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
|
|
|
98
99
|
// "system" mode: this handler runs with a guest identity whose own
|
|
99
100
|
// tenantId is meaningless here — the challenge token is the source
|
|
100
101
|
// of truth for which tenant's row to read.
|
|
101
|
-
const scopedDb = createTenantDb({
|
|
102
|
+
const scopedDb = createTenantDb({
|
|
103
|
+
db: ctx.db.unsafeRaw(),
|
|
104
|
+
tenantId,
|
|
105
|
+
mode: "system",
|
|
106
|
+
reason: "MFA challenge has no session, the tenant comes from the verified challenge token",
|
|
107
|
+
});
|
|
102
108
|
const scopedUser: SessionUser = { id: userId, tenantId, roles: ["User"] };
|
|
103
109
|
const row = await findUserMfaRow(scopedDb, scopedUser);
|
|
104
110
|
// MFA got disabled between login and verify (race, or a stale
|
|
@@ -106,10 +112,10 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
|
|
|
106
112
|
// bad code, no detail leak about account state.
|
|
107
113
|
if (!row) return invalidChallengeToken();
|
|
108
114
|
|
|
109
|
-
const replay = { redis: ctx.redis, userId };
|
|
115
|
+
const replay = { redis: ctx.services.redis, userId };
|
|
110
116
|
const verify = await verifyMfaFactor(row, event.payload.code, replay);
|
|
111
117
|
if (!verify.ok) {
|
|
112
|
-
await recordFailedMfaVerifyAttempt(ctx.redis, userId, maxAttempts, lockoutMinutes);
|
|
118
|
+
await recordFailedMfaVerifyAttempt(ctx.services.redis, userId, maxAttempts, lockoutMinutes);
|
|
113
119
|
return invalidTotpCode();
|
|
114
120
|
}
|
|
115
121
|
|
|
@@ -121,7 +127,12 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
|
|
|
121
127
|
// is reached, so this burn only needs to guard a DIFFERENT code
|
|
122
128
|
// (recovery, or a still-valid-but-not-yet-burned TOTP step) reused
|
|
123
129
|
// against the same challenge token.
|
|
124
|
-
const burnResult = await burnToken(
|
|
130
|
+
const burnResult = await burnToken(
|
|
131
|
+
ctx.services.redis,
|
|
132
|
+
"mfa-challenge",
|
|
133
|
+
userId,
|
|
134
|
+
verified.expiresAtMs,
|
|
135
|
+
);
|
|
125
136
|
if (burnResult === "already-used") return invalidChallengeToken();
|
|
126
137
|
|
|
127
138
|
// Recovery-code use consumes the code — persist the reduced hash-list
|
|
@@ -140,13 +151,13 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
|
|
|
140
151
|
if (!updateResult.isSuccess) return updateResult;
|
|
141
152
|
}
|
|
142
153
|
|
|
143
|
-
await clearMfaVerifyAttempts(ctx.redis, userId);
|
|
154
|
+
await clearMfaVerifyAttempts(ctx.services.redis, userId);
|
|
144
155
|
|
|
145
156
|
// Re-derive the full session the way login.write.ts would have, for
|
|
146
157
|
// the SAME tenant the challenge token already committed to (no
|
|
147
158
|
// "pick a tenant" step here — that already happened at login time).
|
|
148
159
|
const systemUser = createSystemUser(tenantId, ["SystemAdmin"]);
|
|
149
|
-
const userRow = (await ctx.queryAs(systemUser, UserQueries.findForAuth, {
|
|
160
|
+
const userRow = (await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
|
|
150
161
|
id: userId,
|
|
151
162
|
})) as {
|
|
152
163
|
roles?: string | null;
|
|
@@ -174,7 +185,7 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
|
|
|
174
185
|
|
|
175
186
|
// Membership revoked or principal blocked between login and verify —
|
|
176
187
|
// mirror login.write.ts's refusal instead of falling back to globalRoles only.
|
|
177
|
-
const active = await ctx.resolveActiveMembership(userId, tenantId);
|
|
188
|
+
const active = await ctx.auth.resolveActiveMembership(userId, tenantId);
|
|
178
189
|
if (active.kind === "rejected") return invalidChallengeToken();
|
|
179
190
|
// buildSessionRoles calls stripForbiddenMembershipRoles to strip reserved
|
|
180
191
|
// roles from the membership portion (globalRoles keeps SystemAdmin) —
|
|
@@ -188,7 +199,7 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
|
|
|
188
199
|
...sessionTimezoneField(userRow?.timezone),
|
|
189
200
|
...sessionLocaleField(userRow?.locale),
|
|
190
201
|
};
|
|
191
|
-
const claims = await ctx.
|
|
202
|
+
const claims = await ctx.auth.resolveClaims(baseSession);
|
|
192
203
|
const session: SessionUser =
|
|
193
204
|
Object.keys(claims).length > 0 ? { ...baseSession, claims } : baseSession;
|
|
194
205
|
|
|
@@ -29,13 +29,19 @@ export function createMfaCodeVerifier(): MfaCodeVerifier {
|
|
|
29
29
|
reason:
|
|
30
30
|
"reads the MFA enrollment of the user being re-authenticated on the caller's handler context; the calling handler declares its own escapeHatch",
|
|
31
31
|
});
|
|
32
|
-
const scopedDb = createTenantDb({
|
|
32
|
+
const scopedDb = createTenantDb({
|
|
33
|
+
db: ctx.db.unsafeRaw(),
|
|
34
|
+
tenantId,
|
|
35
|
+
mode: "system",
|
|
36
|
+
reason:
|
|
37
|
+
"re-authentication reads the MFA enrollment of the user's own tenant, not the handler's dispatch tenant",
|
|
38
|
+
});
|
|
33
39
|
const row = await findUserMfaRow(scopedDb, { id: userId, tenantId, roles: [] });
|
|
34
40
|
if (!row) return { enrolled: false, ok: false };
|
|
35
|
-
// Fail closed without ctx.redis, matching disable.write.ts/verify.write.ts —
|
|
41
|
+
// Fail closed without ctx.services.redis, matching disable.write.ts/verify.write.ts —
|
|
36
42
|
// skipping the TOTP-replay burn would let an observed code be replayed.
|
|
37
|
-
if (!code || !ctx.redis) return { enrolled: true, ok: false };
|
|
38
|
-
const verify = await verifyMfaFactor(row, code, { redis: ctx.redis, userId });
|
|
43
|
+
if (!code || !ctx.services.redis) return { enrolled: true, ok: false };
|
|
44
|
+
const verify = await verifyMfaFactor(row, code, { redis: ctx.services.redis, userId });
|
|
39
45
|
return { enrolled: true, ok: verify.ok && verify.method === "totp" };
|
|
40
46
|
};
|
|
41
47
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
|
|
1
2
|
import { createTenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
2
3
|
import {
|
|
3
4
|
access,
|
|
@@ -31,8 +32,8 @@ export type MfaStatusChecker = (
|
|
|
31
32
|
ctx: HandlerContext,
|
|
32
33
|
userId: string,
|
|
33
34
|
tenantId: TenantId,
|
|
34
|
-
// Merged global+tenant roles — needed to evaluate the "admins"
|
|
35
|
-
//
|
|
35
|
+
// Merged global+tenant roles — needed to evaluate the "admins" and "systemAdmins"
|
|
36
|
+
// policies. Must be the MERGED set (see login.write.ts caller): a
|
|
36
37
|
// SystemAdmin whose admin-ness lives only in global roles would be
|
|
37
38
|
// missed if the caller passed tenant-membership roles alone.
|
|
38
39
|
roles: readonly string[],
|
|
@@ -47,7 +48,13 @@ export function createMfaStatusChecker(opts: {
|
|
|
47
48
|
reason:
|
|
48
49
|
"reads the MFA enrollment of the tenant named in the signed login/setup token, not the guest dispatch tenant, on the caller's handler context; the calling handler declares its own escapeHatch",
|
|
49
50
|
});
|
|
50
|
-
const scopedDb = createTenantDb({
|
|
51
|
+
const scopedDb = createTenantDb({
|
|
52
|
+
db: ctx.db.unsafeRaw(),
|
|
53
|
+
tenantId,
|
|
54
|
+
mode: "system",
|
|
55
|
+
reason:
|
|
56
|
+
"login/setup token names the tenant to check, the guest dispatch tenant is meaningless",
|
|
57
|
+
});
|
|
51
58
|
const scopedUser: SessionUser = { id: userId, tenantId, roles: ["User"] };
|
|
52
59
|
const row = await findUserMfaRow(scopedDb, scopedUser);
|
|
53
60
|
|
|
@@ -64,12 +71,12 @@ export function createMfaStatusChecker(opts: {
|
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
// ctx.config is bound to the CALLING user's tenant (the anonymous
|
|
67
|
-
// identity's tenant for login) — wrong tenant here. ctx.configResolver takes tenantId
|
|
74
|
+
// identity's tenant for login) — wrong tenant here. ctx.services.configResolver takes tenantId
|
|
68
75
|
// explicitly, which is what a pre-session check against the LOGGING-IN
|
|
69
76
|
// user's tenant needs.
|
|
70
77
|
// @cast-boundary engine-payload — "select" config values are validated
|
|
71
78
|
// against the declared `options` at write-time by the config feature.
|
|
72
|
-
const policy = ((await ctx.configResolver?.get(
|
|
79
|
+
const policy = ((await ctx.services.configResolver?.get(
|
|
73
80
|
mfaRequiredConfigHandle.name,
|
|
74
81
|
mfaRequiredConfigKey(opts.requiredPolicy),
|
|
75
82
|
tenantId,
|
|
@@ -84,9 +91,11 @@ export function createMfaStatusChecker(opts: {
|
|
|
84
91
|
opts.challengeTokenSecret,
|
|
85
92
|
).token;
|
|
86
93
|
if (policy === "all") return { setupRequired: true, preauthSetupToken: buildSetupToken() };
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
const isEnforced =
|
|
95
|
+
policy === "systemAdmins"
|
|
96
|
+
? roles.includes(ROLES.SystemAdmin)
|
|
97
|
+
: roles.some((role) => access.admin.includes(role));
|
|
98
|
+
return isEnforced
|
|
90
99
|
? { setupRequired: true, preauthSetupToken: buildSetupToken() }
|
|
91
100
|
: { required: false };
|
|
92
101
|
};
|
|
@@ -21,7 +21,7 @@ export type MfaFactorVerifyResult =
|
|
|
21
21
|
// must not verify again within its ±1-step window — otherwise a code an
|
|
22
22
|
// attacker observed once, via phishing proxy or shoulder-surfing, stays
|
|
23
23
|
// usable for ~90s of parallel logins). Omitted where a caller has no
|
|
24
|
-
// ctx.redis available; the code-verify itself still works, just without
|
|
24
|
+
// ctx.services.redis available; the code-verify itself still works, just without
|
|
25
25
|
// replay protection for that call.
|
|
26
26
|
export async function verifyMfaFactor(
|
|
27
27
|
row: UserMfaRow,
|
|
@@ -68,8 +68,8 @@ export function createBillingInfoQueryConfig<TTier extends string>(
|
|
|
68
68
|
const billingLive = ctx.config
|
|
69
69
|
? (await ctx.config(STRIPE_BILLING_LIVE_CONFIG_QN)) === true
|
|
70
70
|
: false;
|
|
71
|
-
const apiKeySet = ctx.secrets
|
|
72
|
-
? await ctx.secrets.has(SYSTEM_TENANT_ID, STRIPE_API_KEY_CONFIG_QN)
|
|
71
|
+
const apiKeySet = ctx.services.secrets
|
|
72
|
+
? await ctx.services.secrets.has(SYSTEM_TENANT_ID, STRIPE_API_KEY_CONFIG_QN)
|
|
73
73
|
: false;
|
|
74
74
|
|
|
75
75
|
const sub = await ctx.db.fetchOne<{
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.390.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Revoking one termination no longer resumes a subscription another termination still cancels",
|
|
6
|
+
"detail": "A revocation only resumes the subscription at the provider when no other recorded termination still cancels it. Recording and revoking a termination now run one at a time per tenant, so provider calls can no longer interleave with the event stream."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.388.0",
|
|
4
10
|
"type": "fix",
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts
CHANGED
|
@@ -172,7 +172,7 @@ export async function recordConsent(
|
|
|
172
172
|
};
|
|
173
173
|
const tenantId = ctx.user.tenantId;
|
|
174
174
|
const isSubscription = prepared.mode === "subscription";
|
|
175
|
-
await ctx.
|
|
175
|
+
await ctx.events.unsafeAppend({
|
|
176
176
|
aggregateId: isSubscription ? subscriptionAggregateId(tenantId) : paymentAggregateId(tenantId),
|
|
177
177
|
aggregateType: isSubscription ? SUBSCRIPTION_AGGREGATE_TYPE : PAYMENT_AGGREGATE_TYPE,
|
|
178
178
|
type: CHECKOUT_CONSENT_RECORDED_EVENT_QN,
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts
CHANGED
|
@@ -102,7 +102,7 @@ export function createIssueContractConfirmationHandler(
|
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const stream = await ctx.fetchForWriting({
|
|
105
|
+
const stream = await ctx.events.fetchForWriting({
|
|
106
106
|
aggregateId: payload.sourceAggregateId,
|
|
107
107
|
aggregateType: isSubscription ? SUBSCRIPTION_AGGREGATE_TYPE : PAYMENT_AGGREGATE_TYPE,
|
|
108
108
|
});
|
|
@@ -119,7 +119,7 @@ export function createIssueContractConfirmationHandler(
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
if (!consentEvent || !consent) {
|
|
122
|
-
ctx.log?.warn(
|
|
122
|
+
ctx.obs.log?.warn(
|
|
123
123
|
`[billing-foundation:issue-contract-confirmation] no recorded consent ${payload.consentId} on the stream; no mail sent`,
|
|
124
124
|
);
|
|
125
125
|
return {
|
|
@@ -150,7 +150,7 @@ export function createIssueContractConfirmationHandler(
|
|
|
150
150
|
// Everything that can fail runs before the append, so a failed run
|
|
151
151
|
// leaves no marker behind and the job retry starts clean.
|
|
152
152
|
const recipient = recipientRowSchema.safeParse(
|
|
153
|
-
await ctx.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
|
|
153
|
+
await ctx.dispatch.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
|
|
154
154
|
id: consent.actorUserId,
|
|
155
155
|
}),
|
|
156
156
|
);
|
|
@@ -162,7 +162,7 @@ export function createIssueContractConfirmationHandler(
|
|
|
162
162
|
)
|
|
163
163
|
: null;
|
|
164
164
|
if (!recipient.success || recipientEmail === null) {
|
|
165
|
-
ctx.log?.warn(
|
|
165
|
+
ctx.obs.log?.warn(
|
|
166
166
|
`[billing-foundation:issue-contract-confirmation] recipient of consent ${payload.consentId} not found; no mail sent`,
|
|
167
167
|
);
|
|
168
168
|
return {
|
|
@@ -182,7 +182,7 @@ export function createIssueContractConfirmationHandler(
|
|
|
182
182
|
locale,
|
|
183
183
|
});
|
|
184
184
|
if (terms.version !== consent.termsTemplateVersion) {
|
|
185
|
-
ctx.log?.warn(
|
|
185
|
+
ctx.obs.log?.warn(
|
|
186
186
|
`[billing-foundation:issue-contract-confirmation] terms text block changed since consent ${payload.consentId} (version ${consent.termsTemplateVersion} -> ${terms.version}); the mail carries the current text`,
|
|
187
187
|
);
|
|
188
188
|
}
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts
CHANGED
|
@@ -76,13 +76,16 @@ export function registerConsumerProtection(
|
|
|
76
76
|
// user), never from payload data.
|
|
77
77
|
const tenantId = ctx.systemUser.tenantId;
|
|
78
78
|
const currentPeriodEndIso = payload["currentPeriodEndIso"];
|
|
79
|
-
const result = await ctx.write(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
:
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
const result = await ctx.dispatch.write(
|
|
80
|
+
SubscriptionFoundationHandlers.issueContractConfirmation,
|
|
81
|
+
{
|
|
82
|
+
consentId,
|
|
83
|
+
sourceAggregateId: isSubscriptionEvent
|
|
84
|
+
? subscriptionAggregateId(tenantId)
|
|
85
|
+
: paymentAggregateId(tenantId),
|
|
86
|
+
...(typeof currentPeriodEndIso === "string" && { currentPeriodEndIso }),
|
|
87
|
+
},
|
|
88
|
+
);
|
|
86
89
|
if (!result.isSuccess) {
|
|
87
90
|
throw new Error(
|
|
88
91
|
`billing-foundation:${trigger.jobName}: issue-contract-confirmation failed: ${JSON.stringify(result.error)}`,
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts
CHANGED
|
@@ -80,18 +80,21 @@ export function registerContractTermination(
|
|
|
80
80
|
const declared = contractTerminationDeclaredPayloadSchema.safeParse(payload);
|
|
81
81
|
// skip: trigger event carries no usable declaration
|
|
82
82
|
if (!declared.success) return;
|
|
83
|
-
const result = await ctx.write(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
83
|
+
const result = await ctx.dispatch.write(
|
|
84
|
+
SubscriptionFoundationHandlers.recordContractTermination,
|
|
85
|
+
{
|
|
86
|
+
requestId: declared.data.requestId,
|
|
87
|
+
declarationType: declared.data.declarationType,
|
|
88
|
+
terminationKind: declared.data.terminationKind,
|
|
89
|
+
channel: "public",
|
|
90
|
+
when:
|
|
91
|
+
declared.data.declarationType === "withdrawal"
|
|
92
|
+
? "none"
|
|
93
|
+
: SubscriptionCancelTimings.periodEnd,
|
|
94
|
+
receivedAtIso: declared.data.receivedAtIso,
|
|
95
|
+
locale: declared.data.locale,
|
|
96
|
+
},
|
|
97
|
+
);
|
|
95
98
|
if (!result.isSuccess) {
|
|
96
99
|
throw new Error(
|
|
97
100
|
`billing-foundation:cancel-on-public-termination-declared: record-contract-termination failed: ${JSON.stringify(result.error)}`,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONTRACT_TERMINATION_REQUESTED_EVENT_QN,
|
|
3
|
+
contractTerminationRequestedPayloadSchema,
|
|
4
|
+
} from "../events.ts";
|
|
5
|
+
import { hasRevocation } from "./termination-record.ts";
|
|
6
|
+
|
|
7
|
+
// A revoked declaration must not resume a subscription another declaration still cancels at the provider.
|
|
8
|
+
export function hasOtherOpenProviderCancel(
|
|
9
|
+
events: readonly { type: string; payload: unknown }[],
|
|
10
|
+
requestId: string,
|
|
11
|
+
): boolean {
|
|
12
|
+
return events.some((e) => {
|
|
13
|
+
if (e.type !== CONTRACT_TERMINATION_REQUESTED_EVENT_QN) return false;
|
|
14
|
+
const recorded = contractTerminationRequestedPayloadSchema.safeParse(e.payload).data;
|
|
15
|
+
return (
|
|
16
|
+
recorded !== undefined &&
|
|
17
|
+
recorded.requestId !== requestId &&
|
|
18
|
+
recorded.providerCancel !== "none" &&
|
|
19
|
+
!hasRevocation(events, recorded.requestId)
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -24,22 +24,22 @@ export async function mayMailReceipt(
|
|
|
24
24
|
email: string,
|
|
25
25
|
): Promise<ReceiptMailVerdict> {
|
|
26
26
|
// skip: no rate limiter wired, nothing to count against
|
|
27
|
-
if (!ctx.rateLimit) return "allowed";
|
|
27
|
+
if (!ctx.services.rateLimit) return "allowed";
|
|
28
28
|
try {
|
|
29
29
|
const bucketKey = receiptBucketKey(ctx, email);
|
|
30
30
|
if (bucketKey === undefined) {
|
|
31
|
-
ctx.log?.warn(
|
|
31
|
+
ctx.obs.log?.warn(
|
|
32
32
|
"[billing-foundation:termination] no rate-limit digest available, receipt mail withheld",
|
|
33
33
|
);
|
|
34
34
|
return "unavailable";
|
|
35
35
|
}
|
|
36
|
-
const decision = await ctx.rateLimit.check(bucketKey, {
|
|
36
|
+
const decision = await ctx.services.rateLimit.check(bucketKey, {
|
|
37
37
|
limit: RECEIPT_MAILS_PER_RECIPIENT,
|
|
38
38
|
windowSeconds: RECEIPT_WINDOW_SECONDS,
|
|
39
39
|
});
|
|
40
40
|
return decision.allowed ? "allowed" : "limited";
|
|
41
41
|
} catch (error) {
|
|
42
|
-
ctx.log?.warn(
|
|
42
|
+
ctx.obs.log?.warn(
|
|
43
43
|
`[billing-foundation:termination] receipt rate limiter failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
44
44
|
);
|
|
45
45
|
return "unavailable";
|