@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
|
@@ -226,12 +226,13 @@ async function parseTokenFailure(res: Response): Promise<AuthTokenFailure> {
|
|
|
226
226
|
// found. 429 → typed rate-limit failure. 5xx → unknown-error.
|
|
227
227
|
export async function requestPasswordReset(
|
|
228
228
|
email: string,
|
|
229
|
+
returnTo?: string,
|
|
229
230
|
): Promise<{ ok: true } | { ok: false; error: AuthTokenFailure }> {
|
|
230
231
|
const res = await fetch("/api/auth/request-password-reset", {
|
|
231
232
|
method: "POST",
|
|
232
233
|
credentials: "same-origin",
|
|
233
234
|
headers: { "Content-Type": "application/json", ...csrfHeader() },
|
|
234
|
-
body: JSON.stringify({ email }),
|
|
235
|
+
body: JSON.stringify({ email, ...(returnTo !== undefined && { returnTo }) }),
|
|
235
236
|
});
|
|
236
237
|
if (res.ok) return { ok: true };
|
|
237
238
|
return { ok: false, error: await parseTokenFailure(res) };
|
|
@@ -332,12 +333,17 @@ export async function requestSignup(
|
|
|
332
333
|
// travels any further than this request — never in the URL, the mail,
|
|
333
334
|
// or the confirm response.
|
|
334
335
|
handover?: { readonly entityType: string; readonly token: string },
|
|
336
|
+
returnTo?: string,
|
|
335
337
|
): Promise<{ ok: true } | { ok: false; error: AuthTokenFailure }> {
|
|
336
338
|
const res = await fetch("/api/auth/signup-request", {
|
|
337
339
|
method: "POST",
|
|
338
340
|
credentials: "same-origin",
|
|
339
341
|
headers: { "Content-Type": "application/json", ...csrfHeader() },
|
|
340
|
-
body: JSON.stringify({
|
|
342
|
+
body: JSON.stringify({
|
|
343
|
+
email,
|
|
344
|
+
...(handover !== undefined && { handover }),
|
|
345
|
+
...(returnTo !== undefined && { returnTo }),
|
|
346
|
+
}),
|
|
341
347
|
});
|
|
342
348
|
if (res.ok) return { ok: true };
|
|
343
349
|
return { ok: false, error: await parseTokenFailure(res) };
|
|
@@ -2,35 +2,37 @@
|
|
|
2
2
|
// string, so anything read back from it is untrusted: only same-origin
|
|
3
3
|
// root-relative paths pass (no "//host", no "/\host", no scheme).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { isSafeReturnPath, RETURN_TO_QUERY_PARAM, readReturnToFromSearch } from "./return-path.ts";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
for (let i = 0; i < candidate.length; i++) {
|
|
9
|
-
const code = candidate.charCodeAt(i);
|
|
10
|
-
if (code <= 0x20 || code === 0x7f) return true;
|
|
11
|
-
}
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
7
|
+
export const NEXT_QUERY_PARAM = "next";
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
// would otherwise normalize to "//evil.com" past the leading-slash check.
|
|
17
|
-
export function isSafeNextPath(candidate: unknown): candidate is string {
|
|
18
|
-
if (typeof candidate !== "string" || !candidate.startsWith("/")) return false;
|
|
19
|
-
if (candidate[1] === "/" || candidate.includes("\\")) return false;
|
|
20
|
-
if (hasControlOrWhitespace(candidate)) return false;
|
|
21
|
-
try {
|
|
22
|
-
const parsed = new URL(candidate, "https://next.invalid");
|
|
23
|
-
return parsed.origin === "https://next.invalid" && !parsed.pathname.startsWith("//");
|
|
24
|
-
} catch {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
9
|
+
export const isSafeNextPath = isSafeReturnPath;
|
|
28
10
|
|
|
29
11
|
export function readNextFromSearch(search: string): string | null {
|
|
30
12
|
const candidate = new URLSearchParams(search).get(NEXT_QUERY_PARAM);
|
|
31
13
|
return isSafeNextPath(candidate) ? candidate : null;
|
|
32
14
|
}
|
|
33
15
|
|
|
16
|
+
// The returnTo a mail link or sibling auth page carried in this page's own URL; null on native, where there is no window.
|
|
17
|
+
export function readReturnToFromWindow(): string | null {
|
|
18
|
+
return typeof window === "undefined" ? null : readReturnToFromSearch(window.location.search);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Adds returnTo to an href of a sibling auth page; a missing value leaves the href untouched.
|
|
22
|
+
export function appendReturnTo(href: string, returnTo: string | null): string {
|
|
23
|
+
if (returnTo === null) return href;
|
|
24
|
+
const hashAt = href.indexOf("#");
|
|
25
|
+
const base = hashAt === -1 ? href : href.slice(0, hashAt);
|
|
26
|
+
const hash = hashAt === -1 ? "" : href.slice(hashAt);
|
|
27
|
+
const separator = base.includes("?") ? "&" : "?";
|
|
28
|
+
return `${base}${separator}${RETURN_TO_QUERY_PARAM}=${encodeURIComponent(returnTo)}${hash}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The safe `next` of this page's URL; null on native, where there is no window.
|
|
32
|
+
export function readNextFromWindow(): string | null {
|
|
33
|
+
return typeof window === "undefined" ? null : readNextFromSearch(window.location.search);
|
|
34
|
+
}
|
|
35
|
+
|
|
34
36
|
// loginUrl/postLogoutUrl come from app code (trusted), but a typo like
|
|
35
37
|
// "javascript:" must fail at startup instead of becoming a navigation target.
|
|
36
38
|
export function assertNavigableUrl(url: string, optionName: string): void {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripBasePath, useBasePath } from "@cosmicdrift/kumiko-renderer";
|
|
1
2
|
import type { ReactNode } from "react";
|
|
2
3
|
import { DEFAULT_AUTH_PATHS, DEFAULT_FORGOT_PASSWORD_PATH } from "./auth-paths.ts";
|
|
3
4
|
import { ConfirmAccountUnlockScreen } from "./confirm-account-unlock-screen.tsx";
|
|
@@ -9,8 +10,9 @@ function hasToken(search: string): boolean {
|
|
|
9
10
|
return (new URLSearchParams(search).get("token") ?? "") !== "";
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
function pickDefaultAuthPage(pathname: string, search: string): ReactNode {
|
|
13
|
-
const
|
|
13
|
+
function pickDefaultAuthPage(pathname: string, search: string, basePath: string): ReactNode {
|
|
14
|
+
const trimmed = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
|
|
15
|
+
const path = stripBasePath(trimmed, basePath);
|
|
14
16
|
switch (path) {
|
|
15
17
|
case DEFAULT_FORGOT_PASSWORD_PATH:
|
|
16
18
|
return <ForgotPasswordScreen />;
|
|
@@ -27,5 +29,6 @@ function pickDefaultAuthPage(pathname: string, search: string): ReactNode {
|
|
|
27
29
|
// (outer) still win on the same path. The pages are public: no session needed.
|
|
28
30
|
export function DefaultAuthPagesGate({ children }: { readonly children: ReactNode }): ReactNode {
|
|
29
31
|
const { pathname, search } = window.location;
|
|
30
|
-
|
|
32
|
+
const basePath = useBasePath();
|
|
33
|
+
return pickDefaultAuthPage(pathname, search, basePath) ?? <>{children}</>;
|
|
31
34
|
}
|
|
@@ -12,6 +12,8 @@ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
|
12
12
|
import { type FormEvent, type ReactNode, useState } from "react";
|
|
13
13
|
import { requestPasswordReset } from "./auth-client.ts";
|
|
14
14
|
import { AuthCard } from "./auth-form-primitives.tsx";
|
|
15
|
+
import { readReturnToFromWindow } from "./auth-redirect.ts";
|
|
16
|
+
import { isSafeReturnPath } from "./return-path.ts";
|
|
15
17
|
|
|
16
18
|
export type ForgotPasswordScreenProps = {
|
|
17
19
|
readonly title?: string;
|
|
@@ -19,12 +21,20 @@ export type ForgotPasswordScreenProps = {
|
|
|
19
21
|
/** href for the "Back to login" link in the success and form states. App-
|
|
20
22
|
* specific — defaults to "/login". */
|
|
21
23
|
readonly loginHref?: string;
|
|
24
|
+
/** Page the reset mail should lead back to after the new password is set; defaults to the
|
|
25
|
+
* `returnTo` query parameter of this page. Unsafe values are dropped. */
|
|
26
|
+
readonly returnTo?: string;
|
|
22
27
|
};
|
|
23
28
|
|
|
29
|
+
function resolveReturnTo(prop: string | undefined): string | undefined {
|
|
30
|
+
return (isSafeReturnPath(prop) ? prop : readReturnToFromWindow()) ?? undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
export function ForgotPasswordScreen({
|
|
25
34
|
title,
|
|
26
35
|
subtitle,
|
|
27
36
|
loginHref = "/login",
|
|
37
|
+
returnTo,
|
|
28
38
|
}: ForgotPasswordScreenProps): ReactNode {
|
|
29
39
|
const t = useTranslation();
|
|
30
40
|
const { Form, Field, Input, Button, Banner, Link } = usePrimitives();
|
|
@@ -38,7 +48,7 @@ export function ForgotPasswordScreen({
|
|
|
38
48
|
setSubmitting(true);
|
|
39
49
|
setError(null);
|
|
40
50
|
try {
|
|
41
|
-
const res = await requestPasswordReset(email);
|
|
51
|
+
const res = await requestPasswordReset(email, resolveReturnTo(returnTo));
|
|
42
52
|
if (res.ok) {
|
|
43
53
|
setDone(true);
|
|
44
54
|
} else if (res.error.reason === "rate_limited") {
|
|
@@ -63,6 +63,11 @@ export type { RequestAccountUnlockScreenProps } from "./request-account-unlock-s
|
|
|
63
63
|
export { RequestAccountUnlockScreen } from "./request-account-unlock-screen.tsx";
|
|
64
64
|
export type { ResetPasswordScreenProps } from "./reset-password-screen.tsx";
|
|
65
65
|
export { ResetPasswordScreen } from "./reset-password-screen.tsx";
|
|
66
|
+
export {
|
|
67
|
+
isSafeReturnPath,
|
|
68
|
+
RETURN_TO_QUERY_PARAM,
|
|
69
|
+
readReturnToFromSearch,
|
|
70
|
+
} from "./return-path.ts";
|
|
66
71
|
export type {
|
|
67
72
|
SessionApi,
|
|
68
73
|
SessionBootstrapFailure,
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// reason code needs a bundle entry plus a line in that mapping.
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
prependBasePath,
|
|
6
|
+
useBasePath,
|
|
5
7
|
useOptionalDispatcher,
|
|
6
8
|
usePrimitives,
|
|
7
9
|
useQuery,
|
|
@@ -15,7 +17,11 @@ import {
|
|
|
15
17
|
} from "./auth-client.ts";
|
|
16
18
|
import { AuthCard } from "./auth-form-primitives.tsx";
|
|
17
19
|
import { DEFAULT_AUTH_PATHS, DEFAULT_FORGOT_PASSWORD_PATH } from "./auth-paths.ts";
|
|
18
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
appendReturnTo,
|
|
22
|
+
followNextOrLandingAfterLogin,
|
|
23
|
+
readNextFromWindow,
|
|
24
|
+
} from "./auth-redirect.ts";
|
|
19
25
|
import { AuthQueries } from "./constants.ts";
|
|
20
26
|
import { useSession } from "./session.tsx";
|
|
21
27
|
|
|
@@ -128,16 +134,21 @@ function LoginScreenBody({
|
|
|
128
134
|
// Hides the resend button once the email input changes; otherwise resend
|
|
129
135
|
// would silently go to the edited, possibly mistyped address.
|
|
130
136
|
const [failedLoginEmail, setFailedLoginEmail] = useState<string | null>(null);
|
|
137
|
+
const basePath = useBasePath();
|
|
138
|
+
// The page the user was sent away from rides along so the reset or signup mail leads back to it.
|
|
139
|
+
const returnTo = readNextFromWindow();
|
|
131
140
|
const effectiveForgotHref =
|
|
132
141
|
forgotPasswordHref === undefined
|
|
133
142
|
? loginOptions?.passwordReset === true
|
|
134
|
-
? DEFAULT_FORGOT_PASSWORD_PATH
|
|
143
|
+
? appendReturnTo(prependBasePath(DEFAULT_FORGOT_PASSWORD_PATH, basePath), returnTo)
|
|
135
144
|
: null
|
|
136
|
-
: forgotPasswordHref
|
|
145
|
+
: forgotPasswordHref === null
|
|
146
|
+
? null
|
|
147
|
+
: appendReturnTo(forgotPasswordHref, returnTo);
|
|
137
148
|
const effectiveUnlockHref =
|
|
138
149
|
unlockAccountHref === undefined
|
|
139
150
|
? loginOptions?.accountUnlock === true
|
|
140
|
-
? DEFAULT_AUTH_PATHS.unlockAccount
|
|
151
|
+
? prependBasePath(DEFAULT_AUTH_PATHS.unlockAccount, basePath)
|
|
141
152
|
: null
|
|
142
153
|
: unlockAccountHref;
|
|
143
154
|
|
|
@@ -281,7 +292,7 @@ function LoginScreenBody({
|
|
|
281
292
|
</Link>
|
|
282
293
|
)}
|
|
283
294
|
{signupHref !== undefined && (
|
|
284
|
-
<Link href={signupHref} variant="muted" className="self-center">
|
|
295
|
+
<Link href={appendReturnTo(signupHref, returnTo)} variant="muted" className="self-center">
|
|
285
296
|
{t("auth.signup.title")}
|
|
286
297
|
</Link>
|
|
287
298
|
)}
|
|
@@ -13,6 +13,7 @@ import { type FormEvent, type ReactNode, useState } from "react";
|
|
|
13
13
|
import { resetPassword } from "./auth-client.ts";
|
|
14
14
|
import { passwordPairIssue } from "./auth-form-logic.ts";
|
|
15
15
|
import { AuthCard, useUrlToken } from "./auth-form-primitives.tsx";
|
|
16
|
+
import { buildLoginRedirectUrl, readReturnToFromWindow } from "./auth-redirect.ts";
|
|
16
17
|
|
|
17
18
|
export type ResetPasswordScreenProps = {
|
|
18
19
|
readonly title?: string;
|
|
@@ -37,6 +38,7 @@ export function ResetPasswordScreen({
|
|
|
37
38
|
const [submitting, setSubmitting] = useState(false);
|
|
38
39
|
const [done, setDone] = useState(false);
|
|
39
40
|
const [error, setError] = useState<string | null>(null);
|
|
41
|
+
const [afterResetHref, setAfterResetHref] = useState(loginHref);
|
|
40
42
|
|
|
41
43
|
const doSubmit = async (): Promise<void> => {
|
|
42
44
|
setError(null);
|
|
@@ -53,6 +55,10 @@ export function ResetPasswordScreen({
|
|
|
53
55
|
const res = await resetPassword(token, newPassword);
|
|
54
56
|
setSubmitting(false);
|
|
55
57
|
if (res.ok) {
|
|
58
|
+
const returnTo = readReturnToFromWindow();
|
|
59
|
+
if (returnTo !== null) {
|
|
60
|
+
setAfterResetHref(buildLoginRedirectUrl(loginHref, returnTo, window.location.origin));
|
|
61
|
+
}
|
|
56
62
|
setDone(true);
|
|
57
63
|
return;
|
|
58
64
|
}
|
|
@@ -97,7 +103,7 @@ export function ResetPasswordScreen({
|
|
|
97
103
|
<p className="font-medium text-foreground">{t("auth.resetPassword.successTitle")}</p>
|
|
98
104
|
<p className="mt-1">{t("auth.resetPassword.successBody")}</p>
|
|
99
105
|
</Banner>
|
|
100
|
-
<Link href={
|
|
106
|
+
<Link href={afterResetHref} variant="button">
|
|
101
107
|
{t("auth.resetPassword.goToLogin")}
|
|
102
108
|
</Link>
|
|
103
109
|
</>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Shared by the server (mail link) and the web pages (landing page), so it is plain string logic:
|
|
2
|
+
// no DOM, no window, no `new URL` (React Native's URL getters throw).
|
|
3
|
+
|
|
4
|
+
export const RETURN_TO_QUERY_PARAM = "returnTo";
|
|
5
|
+
export const MAX_RETURN_PATH_LENGTH = 2048;
|
|
6
|
+
|
|
7
|
+
const DOT_SEGMENTS = new Set([".", "..", ".%2e", "%2e.", "%2e%2e", "%2e"]);
|
|
8
|
+
|
|
9
|
+
function hasForbiddenCharacter(candidate: string): boolean {
|
|
10
|
+
for (let i = 0; i < candidate.length; i++) {
|
|
11
|
+
const code = candidate.charCodeAt(i);
|
|
12
|
+
if (code <= 0x20 || code === 0x7f || (code >= 0x80 && code <= 0x9f)) return true;
|
|
13
|
+
if (code === 0x5c) return true;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// A dot segment is resolved by URL normalization, so "/.//evil" would become "//evil".
|
|
19
|
+
function hasDotSegment(candidate: string): boolean {
|
|
20
|
+
const pathEnd = candidate.search(/[?#]/);
|
|
21
|
+
const path = pathEnd === -1 ? candidate : candidate.slice(0, pathEnd);
|
|
22
|
+
return path.split("/").some((segment) => DOT_SEGMENTS.has(segment.toLowerCase()));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isSafeReturnPath(candidate: unknown): candidate is string {
|
|
26
|
+
if (typeof candidate !== "string") return false;
|
|
27
|
+
if (candidate.length < 1 || candidate.length > MAX_RETURN_PATH_LENGTH) return false;
|
|
28
|
+
if (!candidate.startsWith("/") || candidate[1] === "/") return false;
|
|
29
|
+
return !hasForbiddenCharacter(candidate) && !hasDotSegment(candidate);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function readReturnToFromSearch(search: string): string | null {
|
|
33
|
+
const candidate = new URLSearchParams(search).get(RETURN_TO_QUERY_PARAM);
|
|
34
|
+
return isSafeReturnPath(candidate) ? candidate : null;
|
|
35
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
useSessionEndedSignal,
|
|
15
15
|
} from "@cosmicdrift/kumiko-renderer";
|
|
16
16
|
import { readCsrfToken } from "@cosmicdrift/kumiko-renderer/live";
|
|
17
|
+
import { clearRememberedListState } from "@cosmicdrift/kumiko-renderer/web";
|
|
17
18
|
import {
|
|
18
19
|
createContext,
|
|
19
20
|
type ReactNode,
|
|
@@ -216,6 +217,8 @@ export function SessionProvider({
|
|
|
216
217
|
|
|
217
218
|
const logout = useCallback<SessionApi["logout"]>(async () => {
|
|
218
219
|
await logoutApi();
|
|
220
|
+
// The reload below would hide the identity change from the list-state boot.
|
|
221
|
+
clearRememberedListState();
|
|
219
222
|
if (postLogoutUrl !== undefined && typeof window !== "undefined") {
|
|
220
223
|
// State stays untouched: flipping to unauthenticated would let an AuthGate
|
|
221
224
|
// with loginUrl race this navigation to its own redirect.
|
|
@@ -251,6 +254,7 @@ export function SessionProvider({
|
|
|
251
254
|
const userEmail = profile?.email;
|
|
252
255
|
const userDisplayName = profile?.displayName;
|
|
253
256
|
const userTimeZone = profile?.effectiveTimeZone;
|
|
257
|
+
const activeTenantId = state.activeTenantId ?? undefined;
|
|
254
258
|
const currentUser = useMemo<CurrentUser | undefined>(
|
|
255
259
|
() =>
|
|
256
260
|
userId === undefined || userEmail === undefined || userDisplayName === undefined
|
|
@@ -260,8 +264,9 @@ export function SessionProvider({
|
|
|
260
264
|
email: userEmail,
|
|
261
265
|
displayName: userDisplayName,
|
|
262
266
|
...(userTimeZone !== undefined && { timeZone: userTimeZone }),
|
|
267
|
+
...(activeTenantId !== undefined && { tenantId: activeTenantId }),
|
|
263
268
|
},
|
|
264
|
-
[userId, userEmail, userDisplayName, userTimeZone],
|
|
269
|
+
[userId, userEmail, userDisplayName, userTimeZone, activeTenantId],
|
|
265
270
|
);
|
|
266
271
|
return (
|
|
267
272
|
<SessionContext.Provider value={api}>
|
|
@@ -21,7 +21,7 @@ import { type FormEvent, type ReactNode, useState } from "react";
|
|
|
21
21
|
import { confirmSignup, type SignupConfirmSuccess } from "./auth-client.ts";
|
|
22
22
|
import { passwordPairIssue, resolvePostAuthHref } from "./auth-form-logic.ts";
|
|
23
23
|
import { AuthCard, useUrlToken } from "./auth-form-primitives.tsx";
|
|
24
|
-
import { buildLoginRedirectUrl } from "./auth-redirect.ts";
|
|
24
|
+
import { buildLoginRedirectUrl, readReturnToFromWindow } from "./auth-redirect.ts";
|
|
25
25
|
|
|
26
26
|
export type SignupCompleteScreenProps = {
|
|
27
27
|
readonly title?: string;
|
|
@@ -74,15 +74,23 @@ export function SignupCompleteScreen({
|
|
|
74
74
|
const res = await confirmSignup(token, password);
|
|
75
75
|
setSubmitting(false);
|
|
76
76
|
if (res.ok) {
|
|
77
|
+
// The page the signup started from beats the server's landing path.
|
|
78
|
+
const returnTo = readReturnToFromWindow();
|
|
77
79
|
if (res.data.kind === "mfa-pending") {
|
|
78
80
|
setMfaPending(true);
|
|
81
|
+
const next = returnTo ?? res.data.landingPath;
|
|
79
82
|
setContinueHref(
|
|
80
|
-
|
|
83
|
+
next === undefined
|
|
81
84
|
? loginHref
|
|
82
|
-
: buildLoginRedirectUrl(loginHref,
|
|
85
|
+
: buildLoginRedirectUrl(loginHref, next, window.location.origin),
|
|
83
86
|
);
|
|
84
87
|
return;
|
|
85
88
|
}
|
|
89
|
+
// A claimed handover keeps its app-defined target.
|
|
90
|
+
if (returnTo !== null && res.data.handover === undefined) {
|
|
91
|
+
setContinueHref(returnTo);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
86
94
|
// Cookies are already set (auto-login). Show a confirmation with an
|
|
87
95
|
// explicit continue button instead of navigating away silently —
|
|
88
96
|
// the user otherwise gets no signal that activation worked.
|
|
@@ -16,6 +16,8 @@ import { type FormEvent, type ReactNode, useState } from "react";
|
|
|
16
16
|
import { requestSignup } from "./auth-client.ts";
|
|
17
17
|
import { retryAfterMinutes } from "./auth-form-logic.ts";
|
|
18
18
|
import { AuthCard } from "./auth-form-primitives.tsx";
|
|
19
|
+
import { readReturnToFromWindow } from "./auth-redirect.ts";
|
|
20
|
+
import { isSafeReturnPath } from "./return-path.ts";
|
|
19
21
|
|
|
20
22
|
export type SignupScreenProps = {
|
|
21
23
|
readonly title?: string;
|
|
@@ -23,12 +25,20 @@ export type SignupScreenProps = {
|
|
|
23
25
|
/** href for the "Already have an account?" link. App-specific; defaults to
|
|
24
26
|
* "/login". */
|
|
25
27
|
readonly loginHref?: string;
|
|
28
|
+
/** Page the activation mail should lead to after signup; defaults to the `returnTo` query
|
|
29
|
+
* parameter of this page. Unsafe values are dropped. */
|
|
30
|
+
readonly returnTo?: string;
|
|
26
31
|
};
|
|
27
32
|
|
|
33
|
+
function resolveReturnTo(prop: string | undefined): string | undefined {
|
|
34
|
+
return (isSafeReturnPath(prop) ? prop : readReturnToFromWindow()) ?? undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
export function SignupScreen({
|
|
29
38
|
title,
|
|
30
39
|
subtitle,
|
|
31
40
|
loginHref = "/login",
|
|
41
|
+
returnTo,
|
|
32
42
|
}: SignupScreenProps): ReactNode {
|
|
33
43
|
const t = useTranslation();
|
|
34
44
|
const { Form, Field, Input, Button, Banner, Link } = usePrimitives();
|
|
@@ -41,7 +51,7 @@ export function SignupScreen({
|
|
|
41
51
|
setSubmitting(true);
|
|
42
52
|
setError(null);
|
|
43
53
|
try {
|
|
44
|
-
const res = await requestSignup(email);
|
|
54
|
+
const res = await requestSignup(email, undefined, resolveReturnTo(returnTo));
|
|
45
55
|
if (res.ok) {
|
|
46
56
|
setDone(true);
|
|
47
57
|
} else if (res.error.reason === "rate_limited") {
|
|
@@ -73,7 +83,7 @@ export function SignupScreen({
|
|
|
73
83
|
setSubmitting(true);
|
|
74
84
|
setError(null);
|
|
75
85
|
try {
|
|
76
|
-
await requestSignup(email);
|
|
86
|
+
await requestSignup(email, undefined, resolveReturnTo(returnTo));
|
|
77
87
|
} finally {
|
|
78
88
|
setSubmitting(false);
|
|
79
89
|
}
|
|
@@ -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
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.389.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "requiredPolicy \"systemAdmins\"",
|
|
6
|
+
"detail": "The new auth-mfa:config:required value systemAdmins (between optional and admins) forces MFA enrollment only for users whose merged global and tenant roles contain SystemAdmin. TenantAdmins and other roles log in as before; enrolled users are always challenged regardless of policy. No locale labels are involved, the option list has none."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.388.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -2,19 +2,15 @@
|
|
|
2
2
|
// they haven't opted in themselves? Default "optional" means the feature
|
|
3
3
|
// is fully opt-in (existing tenants/users unaffected — no migration step).
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
// mfa-status-checker.ts
|
|
7
|
-
// for a blocked user to complete enrollment yet (that flow ships in PR3's
|
|
8
|
-
// UI). Flipping this away from "optional" before PR3 locks out every
|
|
9
|
-
// unenrolled matching user with no recovery path. Fine for a tenant that
|
|
10
|
-
// enrolls its admins out-of-band first; a footgun otherwise.
|
|
5
|
+
// Every non-optional policy blocks login for an unenrolled matching user until
|
|
6
|
+
// they finish the pre-auth enrollment step (mfa-status-checker.ts setupRequired).
|
|
11
7
|
import {
|
|
12
8
|
access,
|
|
13
9
|
type ConfigKeyHandle,
|
|
14
10
|
createTenantConfig,
|
|
15
11
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
16
12
|
|
|
17
|
-
export const MFA_REQUIRED_POLICIES = ["optional", "admins", "all"] as const;
|
|
13
|
+
export const MFA_REQUIRED_POLICIES = ["optional", "systemAdmins", "admins", "all"] as const;
|
|
18
14
|
export type MfaRequiredPolicy = (typeof MFA_REQUIRED_POLICIES)[number];
|
|
19
15
|
|
|
20
16
|
export const mfaRequiredConfigHandle: ConfigKeyHandle<"select"> = {
|
|
@@ -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
|
|