@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.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 +41 -7
- package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
- package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
- package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
- package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
- package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
- package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
- package/src/admin-shell/constants.ts +11 -0
- package/src/admin-shell/feature.ts +135 -0
- package/src/admin-shell/i18n.ts +36 -0
- package/src/admin-shell/index.ts +17 -0
- package/src/admin-shell/overview-allowlist.ts +37 -0
- package/src/admin-shell/web/client-plugin.tsx +26 -0
- package/src/admin-shell/web/i18n.ts +16 -0
- package/src/admin-shell/web/index.ts +2 -0
- package/src/admin-shell/web/overview-layout.tsx +69 -0
- package/src/admin-shell/web/overview-query.ts +15 -0
- package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
- package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
- package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
- package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
- package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
- package/src/agent-tools/index.ts +9 -0
- package/src/agent-tools/tool-catalog.ts +130 -0
- package/src/agent-tools/tool-dispatch.ts +82 -0
- package/src/agent-tools/types.ts +38 -0
- package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
- package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
- package/src/audit/__tests__/audit.integration.test.ts +50 -25
- package/src/audit/constants.ts +9 -2
- package/src/audit/feature.ts +33 -1
- package/src/audit/handlers/details.query.ts +47 -0
- package/src/audit/handlers/list.query.ts +3 -2
- package/src/audit/i18n.ts +31 -0
- package/src/audit/web/audit-log-detail-screen.tsx +131 -0
- package/src/audit/web/audit-log-screen.tsx +250 -0
- package/src/audit/web/client-plugin.tsx +22 -0
- package/src/audit/web/i18n.ts +12 -0
- package/src/audit/web/index.ts +5 -0
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
- package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
- package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
- package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
- package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
- package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
- package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
- package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
- package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
- package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
- package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
- package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
- package/src/auth-email-password/constants.ts +25 -0
- package/src/auth-email-password/email-templates.ts +32 -0
- package/src/auth-email-password/errors.ts +9 -0
- package/src/auth-email-password/feature.ts +59 -11
- package/src/auth-email-password/handlers/change-password.write.ts +14 -13
- package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
- package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
- package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
- package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
- package/src/auth-email-password/handlers/login.write.ts +86 -16
- package/src/auth-email-password/handlers/logout.write.ts +4 -2
- package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
- package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
- package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
- package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
- package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
- package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
- package/src/auth-email-password/i18n.ts +52 -0
- package/src/auth-email-password/index.ts +13 -2
- package/src/auth-email-password/lockout-store.ts +10 -5
- package/src/auth-email-password/password-policy.ts +2108 -0
- package/src/auth-email-password/seeding.ts +55 -27
- package/src/auth-email-password/self-registration-toggle.ts +25 -0
- package/src/auth-email-password/signed-token.ts +1 -0
- package/src/auth-email-password/unlock-token.ts +28 -0
- package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
- package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
- package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
- package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
- package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
- package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
- package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
- package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
- package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
- package/src/auth-email-password/web/auth-client.ts +96 -18
- package/src/auth-email-password/web/auth-form-logic.ts +30 -0
- package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
- package/src/auth-email-password/web/auth-gate.tsx +140 -19
- package/src/auth-email-password/web/client-plugin.ts +24 -2
- package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
- package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
- package/src/auth-email-password/web/index.ts +16 -4
- package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
- package/src/auth-email-password/web/login-screen.tsx +78 -22
- package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
- package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
- package/src/auth-email-password/web/session.tsx +11 -6
- package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
- package/src/auth-email-password/web/signup-screen.tsx +8 -10
- package/src/auth-email-password/web/use-shell-user.ts +5 -2
- package/src/auth-email-password/web/user-menu.tsx +3 -1
- package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
- package/src/auth-foundation/__tests__/feature.test.ts +46 -0
- package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
- package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
- package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
- package/src/auth-foundation/boot-checks.ts +150 -0
- package/src/auth-foundation/feature.ts +157 -0
- package/src/auth-foundation/index.ts +37 -0
- package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
- package/src/auth-foundation/types.ts +170 -0
- package/src/auth-mfa/__tests__/base32.test.ts +28 -0
- package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
- package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
- package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
- package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
- package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
- package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
- package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
- package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
- package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
- package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
- package/src/auth-mfa/__tests__/totp.test.ts +131 -0
- package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
- package/src/auth-mfa/base32.ts +51 -0
- package/src/auth-mfa/config.ts +26 -0
- package/src/auth-mfa/constants.ts +45 -0
- package/src/auth-mfa/db/queries.ts +45 -0
- package/src/auth-mfa/errors.ts +66 -0
- package/src/auth-mfa/feature.ts +170 -0
- package/src/auth-mfa/handlers/disable.write.ts +67 -0
- package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
- package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
- package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
- package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
- package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
- package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
- package/src/auth-mfa/handlers/status.query.ts +23 -0
- package/src/auth-mfa/handlers/verify.write.ts +179 -0
- package/src/auth-mfa/i18n.ts +12 -0
- package/src/auth-mfa/index.ts +19 -0
- package/src/auth-mfa/mfa-challenge-token.ts +90 -0
- package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
- package/src/auth-mfa/mfa-setup-token.ts +96 -0
- package/src/auth-mfa/mfa-status-checker.ts +84 -0
- package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
- package/src/auth-mfa/otpauth-uri.ts +21 -0
- package/src/auth-mfa/recovery-codes.ts +54 -0
- package/src/auth-mfa/schema/user-mfa.ts +60 -0
- package/src/auth-mfa/testing.ts +5 -0
- package/src/auth-mfa/totp.ts +67 -0
- package/src/auth-mfa/verify-factor.ts +58 -0
- package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
- package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
- package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
- package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
- package/src/auth-mfa/web/client-plugin.ts +37 -0
- package/src/auth-mfa/web/i18n.ts +136 -0
- package/src/auth-mfa/web/index.ts +27 -0
- package/src/auth-mfa/web/mfa-client.ts +184 -0
- package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
- package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
- package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
- package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
- package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
- package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
- package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
- package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
- package/src/auth-mfa-user-data/hooks.ts +53 -0
- package/src/auth-mfa-user-data/index.ts +21 -0
- package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
- package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
- package/src/billing-foundation/entities.ts +28 -4
- package/src/billing-foundation/events.ts +6 -2
- package/src/billing-foundation/feature.ts +9 -1
- package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
- package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
- package/src/billing-foundation/handlers/process-event.write.ts +39 -3
- package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
- package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
- package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
- package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
- package/src/cap-counter/constants.ts +2 -0
- package/src/cap-counter/enforce-cap.ts +15 -18
- package/src/cap-counter/entity.ts +3 -3
- package/src/cap-counter/feature.ts +28 -1
- package/src/cap-counter/i18n.ts +12 -0
- package/src/cap-counter/with-cap-enforcement.ts +2 -2
- package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
- package/src/channel-email/email-channel.ts +2 -1
- package/src/channel-email/index.ts +1 -0
- package/src/channel-email/pii-guard.ts +39 -0
- package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
- package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
- package/src/compliance-profiles/constants.ts +15 -0
- package/src/compliance-profiles/feature.ts +22 -1
- package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
- package/src/compliance-profiles/i18n.ts +6 -0
- package/src/compliance-profiles/index.ts +6 -0
- package/src/compliance-profiles/web/client-plugin.tsx +22 -0
- package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
- package/src/compliance-profiles/web/i18n.ts +25 -0
- package/src/compliance-profiles/web/index.ts +6 -0
- package/src/config/__tests__/app-overrides.test.ts +11 -0
- package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
- package/src/config/__tests__/cascade.integration.test.ts +35 -0
- package/src/config/__tests__/config.integration.test.ts +71 -10
- package/src/config/__tests__/deserialize-value.test.ts +5 -8
- package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
- package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
- package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
- package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
- package/src/config/__tests__/write-helpers.test.ts +23 -0
- package/src/config/db/queries/resolver.ts +3 -3
- package/src/config/feature.ts +15 -8
- package/src/config/handlers/cascade.query.ts +2 -1
- package/src/config/handlers/reencrypt.job.ts +209 -0
- package/src/config/handlers/set.write.ts +55 -3
- package/src/config/handlers/values.query.ts +2 -1
- package/src/config/i18n.ts +9 -0
- package/src/config/index.ts +5 -4
- package/src/config/resolver.ts +75 -61
- package/src/config/table.ts +1 -1
- package/src/config/write-helpers.ts +22 -0
- package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
- package/src/crypto-shredding/constants.ts +5 -0
- package/src/crypto-shredding/feature.ts +18 -0
- package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
- package/src/crypto-shredding/index.ts +11 -0
- package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
- package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
- package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
- package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
- package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
- package/src/custom-fields/constants.ts +1 -0
- package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
- package/src/custom-fields/entity.ts +1 -1
- package/src/custom-fields/events.ts +4 -3
- package/src/custom-fields/feature.ts +8 -0
- package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
- package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
- package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
- package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
- package/src/custom-fields/lib/field-access.ts +8 -1
- package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
- package/src/custom-fields/lib/value-schema.ts +1 -1
- package/src/custom-fields/run-retention.ts +9 -1
- package/src/custom-fields/schemas.ts +9 -9
- package/src/custom-fields/wire-for-entity.ts +110 -78
- package/src/custom-fields/wire-user-data-rights.ts +5 -54
- package/src/data-retention/__tests__/parse-override.test.ts +14 -13
- package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
- package/src/data-retention/feature.ts +3 -3
- package/src/data-retention/run-retention-cleanup.ts +102 -19
- package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
- package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
- package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
- package/src/delivery/constants.ts +13 -51
- package/src/delivery/feature.ts +29 -5
- package/src/delivery/handlers/log.query.ts +28 -7
- package/src/delivery/i18n.ts +6 -0
- package/src/delivery/index.ts +6 -1
- package/src/delivery/public-names.ts +36 -0
- package/src/delivery/tables.ts +3 -3
- package/src/delivery/web/client-plugin.tsx +20 -0
- package/src/delivery/web/delivery-log-screen.tsx +103 -0
- package/src/delivery/web/i18n.ts +25 -0
- package/src/delivery/web/index.ts +3 -0
- package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
- package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
- package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
- package/src/document-ingest-foundation/entity.ts +43 -0
- package/src/document-ingest-foundation/events.ts +25 -0
- package/src/document-ingest-foundation/feature.ts +126 -0
- package/src/document-ingest-foundation/index.ts +12 -0
- package/src/document-ingest-foundation/pages.ts +34 -0
- package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
- package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
- package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
- package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
- package/src/feature-toggles/compose-tier-resolver.ts +134 -0
- package/src/feature-toggles/constants.ts +14 -0
- package/src/feature-toggles/feature.ts +23 -3
- package/src/feature-toggles/global-feature-state-table.ts +20 -1
- package/src/feature-toggles/handlers/list.query.ts +1 -1
- package/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/src/feature-toggles/i18n.ts +6 -0
- package/src/feature-toggles/index.ts +8 -0
- package/src/feature-toggles/toggle-runtime.ts +14 -0
- package/src/feature-toggles/web/client-plugin.tsx +22 -0
- package/src/feature-toggles/web/i18n.ts +29 -0
- package/src/feature-toggles/web/index.ts +6 -0
- package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
- package/src/file-foundation/__tests__/feature.test.ts +5 -5
- package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
- package/src/file-foundation/feature.ts +10 -11
- package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
- package/src/files-provider-s3/s3-provider.ts +12 -1
- package/src/folders/__tests__/folders.integration.test.ts +3 -0
- package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
- package/src/folders/web/__tests__/tree.test.ts +8 -0
- package/src/folders/web/folder-manager.tsx +2 -3
- package/src/folders/web/i18n.ts +0 -2
- package/src/folders/web/tree.ts +13 -2
- package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
- package/src/folders-user-data/index.ts +4 -4
- package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
- package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
- package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
- package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
- package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
- package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
- package/src/inbound-mail-foundation/connect-routes.ts +230 -0
- package/src/inbound-mail-foundation/constants.ts +80 -0
- package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
- package/src/inbound-mail-foundation/entities.ts +172 -0
- package/src/inbound-mail-foundation/events.ts +147 -0
- package/src/inbound-mail-foundation/feature.ts +243 -0
- package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
- package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
- package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
- package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
- package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
- package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
- package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
- package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
- package/src/inbound-mail-foundation/index.ts +97 -0
- package/src/inbound-mail-foundation/oauth-state.ts +103 -0
- package/src/inbound-mail-foundation/projection.ts +152 -0
- package/src/inbound-mail-foundation/provider-factory.ts +53 -0
- package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
- package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
- package/src/inbound-mail-foundation/types.ts +250 -0
- package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
- package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
- package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
- package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
- package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
- package/src/inbound-provider-imap/credential-document.ts +51 -0
- package/src/inbound-provider-imap/feature.ts +283 -0
- package/src/inbound-provider-imap/imap-client.ts +158 -0
- package/src/inbound-provider-imap/index.ts +17 -0
- package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
- package/src/inbound-provider-inmemory/feature.ts +154 -0
- package/src/inbound-provider-inmemory/index.ts +12 -0
- package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
- package/src/jobs/__tests__/job-instant.test.ts +15 -0
- package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
- package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
- package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
- package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
- package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
- package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
- package/src/jobs/constants.ts +8 -1
- package/src/jobs/feature.ts +52 -8
- package/src/jobs/handlers/detail.query.ts +6 -0
- package/src/jobs/handlers/list.query.ts +8 -1
- package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
- package/src/jobs/handlers/reindex-entity.job.ts +49 -0
- package/src/jobs/i18n.ts +37 -0
- package/src/jobs/job-instant.ts +9 -0
- package/src/jobs/job-run-table.ts +4 -4
- package/src/jobs/web/client-plugin.tsx +22 -0
- package/src/jobs/web/i18n.ts +12 -0
- package/src/jobs/web/index.ts +4 -0
- package/src/jobs/web/job-run-detail-screen.tsx +180 -0
- package/src/jobs/web/job-runs-screen.tsx +146 -0
- package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
- package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
- package/src/ledger/recurring.ts +17 -8
- package/src/ledger/web/index.ts +3 -2
- package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
- package/src/legal-pages/feature.ts +24 -31
- package/src/legal-pages/index.ts +1 -0
- package/src/mail-foundation/__tests__/feature.test.ts +5 -6
- package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
- package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
- package/src/mail-foundation/feature.ts +26 -24
- package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
- package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
- package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
- package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
- package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
- package/src/managed-pages/feature.ts +45 -47
- package/src/managed-pages/handlers/branding.query.ts +5 -1
- package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
- package/src/managed-pages/i18n.ts +66 -0
- package/src/managed-pages/screens/page-screens.ts +5 -1
- package/src/managed-pages/web/client-plugin.tsx +22 -0
- package/src/managed-pages/web/i18n.ts +24 -0
- package/src/managed-pages/web/index.ts +3 -0
- package/src/page-render/__tests__/layout.test.ts +63 -0
- package/src/page-render/__tests__/markdown.test.ts +29 -0
- package/src/page-render/branding.ts +4 -3
- package/src/page-render/index.ts +1 -1
- package/src/page-render/layout.ts +18 -3
- package/src/page-render/markdown.ts +1 -9
- package/src/page-render/web.ts +13 -0
- package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
- package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
- package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
- package/src/personal-access-tokens/constants.ts +33 -0
- package/src/personal-access-tokens/feature.ts +105 -0
- package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
- package/src/personal-access-tokens/handlers/create.write.ts +47 -0
- package/src/personal-access-tokens/handlers/list.query.ts +50 -0
- package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
- package/src/personal-access-tokens/hash.ts +17 -0
- package/src/personal-access-tokens/i18n.ts +5 -0
- package/src/personal-access-tokens/index.ts +31 -0
- package/src/personal-access-tokens/resolver.ts +58 -0
- package/src/personal-access-tokens/roles.ts +29 -0
- package/src/personal-access-tokens/schema/api-token.ts +63 -0
- package/src/personal-access-tokens/scopes.ts +43 -0
- package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
- package/src/personal-access-tokens/web/i18n.ts +69 -0
- package/src/personal-access-tokens/web/index.ts +7 -0
- package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
- package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
- package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
- package/src/renderer-simple/resolve-variables.ts +3 -1
- package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
- package/src/secrets/handlers/rotate.job.ts +77 -98
- package/src/secrets/secrets-context.ts +7 -38
- package/src/secrets/table.ts +7 -8
- package/src/seo/__tests__/llms-txt.test.ts +38 -0
- package/src/seo/__tests__/robots-txt.test.ts +18 -0
- package/src/seo/__tests__/schema-builders.test.ts +57 -0
- package/src/seo/__tests__/seo.integration.test.ts +350 -0
- package/src/seo/__tests__/sitemap.test.ts +38 -0
- package/src/seo/constants.ts +60 -0
- package/src/seo/feature.ts +316 -0
- package/src/seo/handlers/seo-config.query.ts +35 -0
- package/src/seo/index.ts +19 -0
- package/src/seo/llms-txt.ts +33 -0
- package/src/seo/robots-txt.ts +13 -0
- package/src/seo/schema-builders.ts +57 -0
- package/src/seo/sitemap.ts +33 -0
- package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
- package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
- package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
- package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
- package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
- package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
- package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
- package/src/sessions/__tests__/test-helpers.ts +16 -3
- package/src/sessions/constants.ts +5 -0
- package/src/sessions/db/queries/cleanup.ts +2 -2
- package/src/sessions/feature.ts +175 -26
- package/src/sessions/handlers/detail.query.ts +36 -0
- package/src/sessions/handlers/list.query.ts +14 -9
- package/src/sessions/handlers/mine.query.ts +13 -8
- package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
- package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
- package/src/sessions/handlers/revoke.write.ts +13 -0
- package/src/sessions/i18n.ts +23 -0
- package/src/sessions/index.ts +9 -2
- package/src/sessions/schema/user-session.ts +8 -2
- package/src/sessions/session-callbacks.ts +99 -14
- package/src/sessions/session-checker-fail-open.test.ts +77 -0
- package/src/sessions/session-revoked-event.ts +29 -0
- package/src/sessions/testing.ts +14 -1
- package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
- package/src/shared/chunked-entity-migration.ts +89 -0
- package/src/shared/decrypt-stored-pii.ts +26 -0
- package/src/shared/encrypt-for-direct-write.ts +23 -0
- package/src/shared/entities-of.ts +17 -0
- package/src/shared/grace-period.test.ts +26 -0
- package/src/shared/grace-period.ts +20 -0
- package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
- package/src/shared/index.ts +16 -0
- package/src/shared/map-with-concurrency.test.ts +104 -0
- package/src/shared/map-with-concurrency.ts +38 -0
- package/src/shared/password-hashing.test.ts +35 -0
- package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
- package/src/shared/system-query.ts +5 -0
- package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
- package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
- package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
- package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
- package/src/subscription-mollie/verify-webhook.ts +1 -0
- package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
- package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
- package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
- package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
- package/src/subscription-stripe/feature.ts +2 -0
- package/src/subscription-stripe/verify-webhook.ts +3 -2
- package/src/tags/__tests__/schemas.test.ts +34 -0
- package/src/tags/__tests__/tags.integration.test.ts +12 -1
- package/src/tags/entity.ts +6 -1
- package/src/tags/feature.ts +2 -0
- package/src/tags/i18n.ts +5 -0
- package/src/tags/schemas.ts +9 -2
- package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
- package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
- package/src/tags/web/tag-manager.tsx +1 -1
- package/src/tags/web/tag-section.tsx +6 -3
- package/src/template-resolver/testing.ts +2 -1
- package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
- package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
- package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
- package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
- package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
- package/src/tenant/constants.ts +5 -0
- package/src/tenant/feature.ts +19 -0
- package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
- package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
- package/src/tenant/handlers/invitations.query.ts +21 -4
- package/src/tenant/handlers/members.query.ts +41 -6
- package/src/tenant/i18n.ts +28 -0
- package/src/tenant/index.ts +7 -2
- package/src/tenant/invitation-table.ts +3 -2
- package/src/tenant/schema/tenant.ts +27 -2
- package/src/tenant/screens.ts +3 -2
- package/src/tenant/seeding.ts +136 -3
- package/src/tenant/web/client-plugin.tsx +20 -0
- package/src/tenant/web/i18n.ts +43 -0
- package/src/tenant/web/index.ts +4 -0
- package/src/tenant/web/members-screen.tsx +270 -0
- package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
- package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
- package/src/tenant-lifecycle/boot-checks.ts +45 -0
- package/src/tenant-lifecycle/constants.ts +56 -0
- package/src/tenant-lifecycle/events.ts +46 -0
- package/src/tenant-lifecycle/feature.ts +112 -0
- package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
- package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
- package/src/tenant-lifecycle/index.ts +7 -0
- package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
- package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
- package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
- package/src/tenant-lifecycle/stages.ts +206 -0
- package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
- package/src/tenant-settings/config.ts +44 -0
- package/src/tenant-settings/constants.ts +16 -0
- package/src/tenant-settings/feature.ts +49 -0
- package/src/tenant-settings/index.ts +8 -0
- package/src/tenant-settings/tenant-defaults.ts +67 -0
- package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
- package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
- package/src/text-content/web/client-plugin.tsx +126 -104
- package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
- package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
- package/src/tier-engine/feature.ts +13 -6
- package/src/tier-engine/i18n.ts +8 -0
- package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
- package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
- package/src/user/__tests__/user.integration.test.ts +26 -0
- package/src/user/constants.ts +1 -0
- package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
- package/src/user/feature.ts +3 -0
- package/src/user/handlers/find-for-auth.query.ts +8 -6
- package/src/user/i18n.ts +20 -0
- package/src/user/index.ts +4 -0
- package/src/user/schema/user.ts +6 -2
- package/src/user/screens.ts +3 -15
- package/src/user/seeding.ts +8 -0
- package/src/user-data-rights/COMPLIANCE.md +21 -1
- package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
- package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
- package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
- package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
- package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
- package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
- package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
- package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
- package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
- package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
- package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
- package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
- package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
- package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
- package/src/user-data-rights/boot-checks.ts +70 -0
- package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
- package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
- package/src/user-data-rights/email-templates.ts +6 -6
- package/src/user-data-rights/feature.ts +162 -43
- package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
- package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
- package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
- package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
- package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
- package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
- package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
- package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
- package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
- package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
- package/src/user-data-rights/i18n.ts +35 -32
- package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
- package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
- package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
- package/src/user-data-rights/run-export-jobs.ts +55 -19
- package/src/user-data-rights/run-forget-cleanup.ts +112 -7
- package/src/user-data-rights/run-user-export.ts +70 -4
- package/src/user-data-rights/schema/download-token.ts +5 -3
- package/src/user-data-rights/schema/export-job.ts +2 -0
- package/src/user-data-rights/screens.ts +2 -1
- package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
- package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
- package/src/user-data-rights/web/client-plugin.tsx +6 -1
- package/src/user-data-rights/web/i18n.ts +30 -10
- package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
- package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
- package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
- package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
- package/src/user-data-rights-defaults/feature.ts +78 -1
- package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
- package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
- package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
- package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
- package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
- package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
- package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
- package/src/user-data-rights-defaults/index.ts +26 -0
- package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
- package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
- package/src/user-profile/feature.ts +4 -1
- package/src/user-profile/handlers/change-email.write.ts +15 -10
- package/src/user-profile/web/profile-screen.tsx +15 -3
- package/src/auth-email-password/stream-tenant.ts +0 -31
- package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
- package/src/legal-pages/security-headers.ts +0 -1
- package/src/sessions/schema/index.ts +0 -5
- package/src/tenant/schema/index.ts +0 -5
- package/src/user/schema/index.ts +0 -5
- /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
|
@@ -34,8 +34,24 @@ export type LoginResponse = {
|
|
|
34
34
|
};
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
// Reason codes the login-handler is known to emit today (login-screen.tsx's
|
|
38
|
+
// reasonToKey maps every one of these to an i18n key). The server can also
|
|
39
|
+
// send a reason this client doesn't know about yet (rolling deploys, a new
|
|
40
|
+
// enforcement policy) — `string & {}` keeps that open while still giving
|
|
41
|
+
// autocomplete/typo-checking on the known set, instead of collapsing both
|
|
42
|
+
// to plain `string` (coding-standards: no magic strings without a union).
|
|
43
|
+
export type KnownLoginFailureReason =
|
|
44
|
+
| "invalid_credentials"
|
|
45
|
+
| "no_membership"
|
|
46
|
+
| "account_locked"
|
|
47
|
+
| "email_not_verified"
|
|
48
|
+
| "rate_limited"
|
|
49
|
+
| "invalid_body"
|
|
50
|
+
| "mfa_not_supported"
|
|
51
|
+
| "mfa_setup_required";
|
|
52
|
+
|
|
37
53
|
export type LoginFailure = {
|
|
38
|
-
readonly reason: string;
|
|
54
|
+
readonly reason: KnownLoginFailureReason | (string & {});
|
|
39
55
|
readonly message?: string;
|
|
40
56
|
readonly retryAfterSeconds?: number;
|
|
41
57
|
};
|
|
@@ -45,13 +61,24 @@ export function csrfHeader(): Record<string, string> {
|
|
|
45
61
|
return token !== undefined ? { [CSRF_HEADER_NAME]: token } : {};
|
|
46
62
|
}
|
|
47
63
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
// LoginResult mirrors the two-step login contract auth-routes.ts owns: a
|
|
65
|
+
// straight success, an MFA challenge (auth-mfa mints the token), an
|
|
66
|
+
// MFA-setup-required block (enforcement policy blocks an unenrolled user),
|
|
67
|
+
// or a plain failure. One discriminated union, not `{ok}` booleans, because
|
|
68
|
+
// callers branch on 4 arms.
|
|
69
|
+
export type LoginResult =
|
|
70
|
+
| { readonly kind: "success"; readonly data: LoginResponse }
|
|
71
|
+
| { readonly kind: "mfa-challenge"; readonly challengeToken: string }
|
|
72
|
+
| { readonly kind: "mfa-setup-required"; readonly preauthSetupToken: string }
|
|
73
|
+
| { readonly kind: "failure"; readonly error: LoginFailure };
|
|
74
|
+
|
|
75
|
+
// POST /api/auth/login. Success → token + user; MFA-enrolled user → a
|
|
76
|
+
// challenge token the caller completes via auth-mfa's verify screen;
|
|
77
|
+
// unenrolled user blocked by enforcement policy → mfa-setup-required;
|
|
78
|
+
// otherwise a structured failure with reason (invalid_credentials,
|
|
79
|
+
// account_locked, no_membership, rate_limited). The server sets cookies
|
|
80
|
+
// automatically on any 200.
|
|
81
|
+
export async function login(req: LoginRequest): Promise<LoginResult> {
|
|
55
82
|
const res = await fetch("/api/auth/login", {
|
|
56
83
|
method: "POST",
|
|
57
84
|
credentials: "same-origin",
|
|
@@ -59,13 +86,17 @@ export async function login(
|
|
|
59
86
|
body: JSON.stringify(req),
|
|
60
87
|
});
|
|
61
88
|
if (res.status === 429) {
|
|
62
|
-
return {
|
|
89
|
+
return { kind: "failure", error: { reason: "rate_limited" } };
|
|
63
90
|
}
|
|
64
91
|
// @cast-boundary engine-payload — HTTP-API contract, server-side schema-validated
|
|
65
92
|
const body = (await res.json().catch(() => ({}))) as {
|
|
66
93
|
isSuccess?: boolean;
|
|
67
94
|
token?: string;
|
|
68
95
|
user?: LoginResponse["user"];
|
|
96
|
+
mfaRequired?: boolean;
|
|
97
|
+
challengeToken?: string;
|
|
98
|
+
mfaSetupRequired?: boolean;
|
|
99
|
+
preauthSetupToken?: string;
|
|
69
100
|
error?:
|
|
70
101
|
| {
|
|
71
102
|
code?: string;
|
|
@@ -74,19 +105,33 @@ export async function login(
|
|
|
74
105
|
}
|
|
75
106
|
| string;
|
|
76
107
|
};
|
|
77
|
-
if (body.isSuccess === true
|
|
78
|
-
|
|
108
|
+
if (body.isSuccess === true) {
|
|
109
|
+
if (body.mfaRequired === true && typeof body.challengeToken === "string") {
|
|
110
|
+
return { kind: "mfa-challenge", challengeToken: body.challengeToken };
|
|
111
|
+
}
|
|
112
|
+
if (body.mfaSetupRequired === true) {
|
|
113
|
+
// Older servers / mounts may set the flag without minting a preauth token —
|
|
114
|
+
// surface an explicit failure (login-screen maps mfa_setup_required) instead
|
|
115
|
+
// of falling through to generic login_failed.
|
|
116
|
+
if (typeof body.preauthSetupToken === "string") {
|
|
117
|
+
return { kind: "mfa-setup-required", preauthSetupToken: body.preauthSetupToken };
|
|
118
|
+
}
|
|
119
|
+
return { kind: "failure", error: { reason: "mfa_setup_required" } };
|
|
120
|
+
}
|
|
121
|
+
if (body.token !== undefined && body.user !== undefined) {
|
|
122
|
+
return { kind: "success", data: { token: body.token, user: body.user } };
|
|
123
|
+
}
|
|
79
124
|
}
|
|
80
125
|
// Der Server schickt error entweder als string ("invalid_body") oder als
|
|
81
126
|
// strukturiertes Objekt. Wir ziehen uns den sprechendsten Reason raus.
|
|
82
127
|
const err = body.error;
|
|
83
128
|
if (typeof err === "string") {
|
|
84
|
-
return {
|
|
129
|
+
return { kind: "failure", error: { reason: err } };
|
|
85
130
|
}
|
|
86
131
|
const reason = err?.details?.reason ?? err?.code ?? "login_failed";
|
|
87
132
|
const retry = err?.details?.retryAfterSeconds;
|
|
88
133
|
return {
|
|
89
|
-
|
|
134
|
+
kind: "failure",
|
|
90
135
|
error: {
|
|
91
136
|
reason,
|
|
92
137
|
...(err?.message !== undefined && { message: err.message }),
|
|
@@ -144,10 +189,10 @@ async function parseTokenFailure(res: Response): Promise<AuthTokenFailure> {
|
|
|
144
189
|
return { reason };
|
|
145
190
|
}
|
|
146
191
|
|
|
147
|
-
// POST /api/auth/request-password-reset. 200 silent-success:
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
192
|
+
// POST /api/auth/request-password-reset. 200 silent-success: even if the
|
|
193
|
+
// email doesn't exist, the caller sees `{ ok: true }` — no account
|
|
194
|
+
// enumeration. Server only triggers the mail internally when a user is
|
|
195
|
+
// found. 429 → typed rate-limit failure. 5xx → unknown-error.
|
|
151
196
|
export async function requestPasswordReset(
|
|
152
197
|
email: string,
|
|
153
198
|
): Promise<{ ok: true } | { ok: false; error: AuthTokenFailure }> {
|
|
@@ -181,7 +226,7 @@ export async function resetPassword(
|
|
|
181
226
|
}
|
|
182
227
|
|
|
183
228
|
// POST /api/auth/request-email-verification. Same silent-success
|
|
184
|
-
//
|
|
229
|
+
// semantics as request-password-reset. 429 → rate-limit failure.
|
|
185
230
|
export async function requestEmailVerification(
|
|
186
231
|
email: string,
|
|
187
232
|
): Promise<{ ok: true } | { ok: false; error: AuthTokenFailure }> {
|
|
@@ -211,6 +256,39 @@ export async function verifyEmail(
|
|
|
211
256
|
return { ok: false, error: await parseTokenFailure(res) };
|
|
212
257
|
}
|
|
213
258
|
|
|
259
|
+
// POST /api/auth/request-account-unlock. Same silent-success semantics as
|
|
260
|
+
// request-password-reset. 429 → rate-limit failure.
|
|
261
|
+
export async function requestAccountUnlock(
|
|
262
|
+
email: string,
|
|
263
|
+
): Promise<{ ok: true } | { ok: false; error: AuthTokenFailure }> {
|
|
264
|
+
const res = await fetch("/api/auth/request-account-unlock", {
|
|
265
|
+
method: "POST",
|
|
266
|
+
credentials: "same-origin",
|
|
267
|
+
headers: { "Content-Type": "application/json", ...csrfHeader() },
|
|
268
|
+
body: JSON.stringify({ email }),
|
|
269
|
+
});
|
|
270
|
+
if (res.ok) return { ok: true };
|
|
271
|
+
return { ok: false, error: await parseTokenFailure(res) };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// POST /api/auth/confirm-account-unlock. Auto-submitted by
|
|
275
|
+
// ConfirmAccountUnlockScreen after parsing `?token=...`. Server collapses
|
|
276
|
+
// every verify failure into `invalid_unlock_token` (anti-enumeration,
|
|
277
|
+
// parallel to reset/verify). Only clears the Redis lockout state, no
|
|
278
|
+
// entity write.
|
|
279
|
+
export async function confirmAccountUnlock(
|
|
280
|
+
token: string,
|
|
281
|
+
): Promise<{ ok: true } | { ok: false; error: AuthTokenFailure }> {
|
|
282
|
+
const res = await fetch("/api/auth/confirm-account-unlock", {
|
|
283
|
+
method: "POST",
|
|
284
|
+
credentials: "same-origin",
|
|
285
|
+
headers: { "Content-Type": "application/json", ...csrfHeader() },
|
|
286
|
+
body: JSON.stringify({ token }),
|
|
287
|
+
});
|
|
288
|
+
if (res.ok) return { ok: true };
|
|
289
|
+
return { ok: false, error: await parseTokenFailure(res) };
|
|
290
|
+
}
|
|
291
|
+
|
|
214
292
|
// POST /api/auth/signup-request. Always-200 (anti-enumeration; wir
|
|
215
293
|
// sagen nicht ob die Email schon registriert ist). Server schickt
|
|
216
294
|
// Activation-Mail an die Adresse — beim Klick auf den Link landet der
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @runtime client
|
|
2
|
+
// Pure form helpers shared by auth screens. Extracted so unit tests can
|
|
3
|
+
// pin password / rate-limit / redirect rules without mounting React.
|
|
4
|
+
|
|
5
|
+
export type PasswordPairIssue = "too_short" | "mismatch";
|
|
6
|
+
|
|
7
|
+
/** Client-side password + confirm check before calling signup/reset APIs. */
|
|
8
|
+
export function passwordPairIssue(
|
|
9
|
+
password: string,
|
|
10
|
+
confirmPassword: string,
|
|
11
|
+
minLength = 8,
|
|
12
|
+
): PasswordPairIssue | null {
|
|
13
|
+
if (password.length < minLength) return "too_short";
|
|
14
|
+
if (password !== confirmPassword) return "mismatch";
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Maps server `retryAfterSeconds` to whole minutes for i18n interpolation. */
|
|
19
|
+
export function retryAfterMinutes(retryAfterSeconds?: number): number | undefined {
|
|
20
|
+
if (retryAfterSeconds === undefined) return undefined;
|
|
21
|
+
return Math.ceil(retryAfterSeconds / 60);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Resolves post-login redirect: string template or function of tenantKey. */
|
|
25
|
+
export function resolveLoggedInHref(
|
|
26
|
+
href: string | ((args: { readonly tenantKey: string }) => string),
|
|
27
|
+
tenantKey: string,
|
|
28
|
+
): string {
|
|
29
|
+
return typeof href === "function" ? href({ tenantKey }) : href;
|
|
30
|
+
}
|
|
@@ -9,15 +9,14 @@
|
|
|
9
9
|
// (full-screen, zentriert, max-w-sm). Web-only;
|
|
10
10
|
// eine Native-Variante landet bei Bedarf
|
|
11
11
|
// daneben (z.B. SafeArea + ScrollView).
|
|
12
|
-
// authButtonClass — Tailwind-Class für anchor-styled-as-button
|
|
13
|
-
// (z.B. "Zum Login"-Link nach Reset-Success).
|
|
14
|
-
// Nur dort, wo ein <a>-Tag rendert.
|
|
15
|
-
// authMutedLinkClass — Subtle-Link-Style.
|
|
16
12
|
// parseUrlToken — URL-Param-Helper (window.location.search).
|
|
13
|
+
//
|
|
14
|
+
// Link-Styles laufen über das Link-Primitive (variant="button"/"muted") —
|
|
15
|
+
// die früheren authButtonClass/authMutedLinkClass sind dorthin gewandert.
|
|
17
16
|
|
|
18
17
|
import { usePrimitives } from "@cosmicdrift/kumiko-renderer";
|
|
19
|
-
import { BareFormProvider
|
|
20
|
-
import { createContext, type ReactNode, useContext } from "react";
|
|
18
|
+
import { BareFormProvider } from "@cosmicdrift/kumiko-renderer-web";
|
|
19
|
+
import { createContext, type ReactNode, useContext, useEffect, useState } from "react";
|
|
21
20
|
|
|
22
21
|
// Wrappt die zentrierte Auth-Card in ihre Umgebung. Default = Fullscreen-
|
|
23
22
|
// zentriert (Standalone-Auth-Page). Eine Apex-/Marketing-Chrome reicht über
|
|
@@ -80,20 +79,6 @@ export function AuthCard({ title, subtitle, children }: AuthCardProps): ReactNod
|
|
|
80
79
|
return shell(card);
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
// Primary-button-Style für anchor-Tags die wie ein Button aussehen
|
|
84
|
-
// (z.B. "Zum Login"-Link nach Reset-Success — kein <Button> weil <a>).
|
|
85
|
-
export const authButtonClass = cn(
|
|
86
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors",
|
|
87
|
-
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
88
|
-
"disabled:pointer-events-none disabled:opacity-50",
|
|
89
|
-
"bg-primary text-primary-foreground shadow hover:bg-primary/90 h-9 px-4 py-2",
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
// Subtle-Link-Style (für "Zurück zum Login"-Anchors). Fixed margin/
|
|
93
|
-
// alignment-classes lassen wir den Caller setzen — nur Farbe + hover.
|
|
94
|
-
export const authMutedLinkClass =
|
|
95
|
-
"text-sm text-muted-foreground hover:text-foreground underline-offset-4 hover:underline";
|
|
96
|
-
|
|
97
82
|
// Liest `?<paramName>=<value>` aus der aktuellen URL — typisches
|
|
98
83
|
// Pattern für Token-bearing Pages (reset, verify). Returnt "" wenn der
|
|
99
84
|
// Browser nicht da ist (SSR-safety) oder der Parameter fehlt.
|
|
@@ -106,3 +91,20 @@ export function parseUrlToken(paramName = "token"): string {
|
|
|
106
91
|
if (typeof window === "undefined") return "";
|
|
107
92
|
return new URLSearchParams(window.location.search).get(paramName) ?? "";
|
|
108
93
|
}
|
|
94
|
+
|
|
95
|
+
// Reads the magic-link token from `?<paramName>=` once on mount, then strips
|
|
96
|
+
// that param from the URL via history.replaceState so the single-use token
|
|
97
|
+
// doesn't linger in browser history / Referer (#774). An explicit `override`
|
|
98
|
+
// (server-injected token) short-circuits both the URL read and the scrub.
|
|
99
|
+
export function useUrlToken(override?: string, paramName = "token"): string {
|
|
100
|
+
const [token] = useState(() => override ?? parseUrlToken(paramName));
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (override !== undefined) return;
|
|
103
|
+
if (typeof window === "undefined") return;
|
|
104
|
+
const url = new URL(window.location.href);
|
|
105
|
+
if (!url.searchParams.has(paramName)) return;
|
|
106
|
+
url.searchParams.delete(paramName);
|
|
107
|
+
window.history.replaceState(window.history.state, "", url.toString());
|
|
108
|
+
}, [override, paramName]);
|
|
109
|
+
return token;
|
|
110
|
+
}
|
|
@@ -9,38 +9,159 @@
|
|
|
9
9
|
// (nur `{ children }`-Prop). Der Sample kann so einen eigenen Login-
|
|
10
10
|
// Screen rein konfigurieren, ohne den Gate selbst ersetzen zu müssen.
|
|
11
11
|
|
|
12
|
-
import type
|
|
12
|
+
import { type ComponentType, type ReactNode, useEffect, useState } from "react";
|
|
13
13
|
import { LoginScreen, type LoginScreenProps } from "./login-screen";
|
|
14
14
|
import { SessionProvider, useSession } from "./session";
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
// Generic — NOT auth-mfa's MfaVerifyScreenProps directly, so this feature
|
|
17
|
+
// stays unaware of auth-mfa's concrete shape (same coupling direction as
|
|
18
|
+
// login.write.ts's mfaStatusChecker callback on the server side). Apps
|
|
19
|
+
// wire auth-mfa's MfaVerifyScreen in here via EmailPasswordClientOptions.
|
|
20
|
+
export type MfaVerifyComponentProps = {
|
|
21
|
+
readonly challengeToken: string;
|
|
22
|
+
readonly onSuccess?: () => void;
|
|
23
|
+
readonly onCancel?: () => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Same coupling reasoning as MfaVerifyComponentProps above — generic, not
|
|
27
|
+
// auth-mfa's MfaSetupPreauthScreenProps directly. Apps wire auth-mfa's
|
|
28
|
+
// MfaSetupPreauthScreen in here via EmailPasswordClientOptions.
|
|
29
|
+
export type MfaSetupComponentProps = {
|
|
30
|
+
readonly preauthSetupToken: string;
|
|
31
|
+
readonly accountLabel: string;
|
|
32
|
+
readonly onSuccess?: () => void;
|
|
33
|
+
readonly onCancel?: () => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type LoginRouteOptions = {
|
|
37
|
+
readonly loginScreen?: ComponentType<LoginScreenProps>;
|
|
38
|
+
readonly loginScreenProps?: LoginScreenProps;
|
|
39
|
+
readonly mfaVerifyScreen?: ComponentType<MfaVerifyComponentProps>;
|
|
40
|
+
readonly mfaSetupScreen?: ComponentType<MfaSetupComponentProps>;
|
|
41
|
+
/** Called once the session becomes authenticated. makeAuthGate ignores
|
|
42
|
+
* this (it renders `children` on its own authenticated branch instead);
|
|
43
|
+
* standalone routes — no parent gate, e.g. an anonymous apex/marketing
|
|
44
|
+
* surface — use it to redirect. */
|
|
45
|
+
readonly onAuthenticated?: () => void;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// The one sanctioned way to render a login flow that correctly completes
|
|
49
|
+
// an MFA challenge — makeAuthGate below is just this wrapped for the
|
|
50
|
+
// `{ children }` gate shape. Also exported directly for apps that render
|
|
51
|
+
// their own standalone login route outside any gate (createPublicSurface
|
|
52
|
+
// apex pages stack providers, not gates — see auth-mount.tsx recipes).
|
|
53
|
+
// Handing an app raw LoginScreen + telling it to hand-roll the challenge-
|
|
54
|
+
// token swap itself is exactly how kumiko-framework#266's login-time MFA
|
|
55
|
+
// step went missing in a real apex surface; this prevents the *most common*
|
|
56
|
+
// version of that mistake. MFA still only actually works once a consumer
|
|
57
|
+
// wires `mfaVerifyScreen` — omitting it is a silent no-MFA fallback, not
|
|
58
|
+
// something this route can catch on its own (auth-mfa is a separate,
|
|
59
|
+
// optional package).
|
|
60
|
+
export function createLoginRoute(
|
|
61
|
+
opts: LoginRouteOptions = {},
|
|
62
|
+
): ComponentType<Record<string, never>> {
|
|
63
|
+
const LoginComponent = opts.loginScreen ?? LoginScreen;
|
|
64
|
+
const MfaVerifyComponent = opts.mfaVerifyScreen;
|
|
65
|
+
const MfaSetupComponent = opts.mfaSetupScreen;
|
|
66
|
+
|
|
67
|
+
function LoginRoute(): ReactNode {
|
|
68
|
+
const { status, refresh } = useSession();
|
|
69
|
+
const { onAuthenticated } = opts;
|
|
70
|
+
// Pending challenge-token from LoginScreen's onMfaChallenge. Lives here
|
|
71
|
+
// (not in SessionState) because it's a UI-only transition — the server
|
|
72
|
+
// never considers this session authenticated until verify succeeds.
|
|
73
|
+
const [challengeToken, setChallengeToken] = useState<string | null>(null);
|
|
74
|
+
const [setupRequest, setSetupRequest] = useState<{
|
|
75
|
+
readonly preauthSetupToken: string;
|
|
76
|
+
readonly accountLabel: string;
|
|
77
|
+
} | null>(null);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (status === "authenticated") onAuthenticated?.();
|
|
81
|
+
}, [status]);
|
|
82
|
+
|
|
22
83
|
if (status === "loading") {
|
|
23
84
|
return (
|
|
24
85
|
<div className="min-h-screen flex items-center justify-center text-muted-foreground text-sm" />
|
|
25
86
|
);
|
|
26
87
|
}
|
|
27
|
-
|
|
28
|
-
|
|
88
|
+
// A standalone mount (no parent gate, no onAuthenticated wired — e.g. an
|
|
89
|
+
// apex/marketing surface that just places <LoginRoute /> directly) has no
|
|
90
|
+
// way to navigate away once authenticated. Blanking the page there is a
|
|
91
|
+
// regression against the old raw LoginScreen (which had no session check
|
|
92
|
+
// at all); fall through to the form instead. A route WITH onAuthenticated
|
|
93
|
+
// still returns null and trusts the effect above to redirect.
|
|
94
|
+
if (status === "authenticated" && onAuthenticated) return null;
|
|
95
|
+
if (challengeToken !== null && MfaVerifyComponent) {
|
|
96
|
+
return (
|
|
97
|
+
<MfaVerifyComponent
|
|
98
|
+
challengeToken={challengeToken}
|
|
99
|
+
onSuccess={() => setChallengeToken(null)}
|
|
100
|
+
onCancel={() => setChallengeToken(null)}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
// Pending preauthSetupToken from LoginScreen's onMfaSetupRequired. Same
|
|
105
|
+
// reasoning as challengeToken — a UI-only transition, not session state.
|
|
106
|
+
if (setupRequest !== null && MfaSetupComponent) {
|
|
107
|
+
return (
|
|
108
|
+
<MfaSetupComponent
|
|
109
|
+
preauthSetupToken={setupRequest.preauthSetupToken}
|
|
110
|
+
accountLabel={setupRequest.accountLabel}
|
|
111
|
+
onSuccess={() => {
|
|
112
|
+
// MfaSetupPreauthScreen has no session to refresh itself with
|
|
113
|
+
// (it runs pre-auth) — the gate owns the session, so it refreshes.
|
|
114
|
+
// Keep setupRequest until refresh settles: clearing first would
|
|
115
|
+
// dump the user back on the login form with no retry if refresh
|
|
116
|
+
// rejects (cookie+JWT already minted server-side).
|
|
117
|
+
void refresh().then(
|
|
118
|
+
() => setSetupRequest(null),
|
|
119
|
+
() => {
|
|
120
|
+
// leave setupRequest set so the user can cancel/retry
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
}}
|
|
124
|
+
onCancel={() => setSetupRequest(null)}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
29
127
|
}
|
|
30
|
-
return
|
|
128
|
+
return (
|
|
129
|
+
<LoginComponent
|
|
130
|
+
{...opts.loginScreenProps}
|
|
131
|
+
onMfaChallenge={
|
|
132
|
+
MfaVerifyComponent ? setChallengeToken : opts.loginScreenProps?.onMfaChallenge
|
|
133
|
+
}
|
|
134
|
+
onMfaSetupRequired={
|
|
135
|
+
MfaSetupComponent
|
|
136
|
+
? (preauthSetupToken, accountLabel) =>
|
|
137
|
+
setSetupRequest({ preauthSetupToken, accountLabel })
|
|
138
|
+
: opts.loginScreenProps?.onMfaSetupRequired
|
|
139
|
+
}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return LoginRoute;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function makeAuthGate(opts: LoginRouteOptions = {}): ComponentType<{
|
|
147
|
+
children: ReactNode;
|
|
148
|
+
}> {
|
|
149
|
+
const LoginRoute = createLoginRoute(opts);
|
|
150
|
+
function AuthGate({ children }: { readonly children: ReactNode }): ReactNode {
|
|
151
|
+
const { status } = useSession();
|
|
152
|
+
if (status === "authenticated") return <>{children}</>;
|
|
153
|
+
return <LoginRoute />;
|
|
31
154
|
}
|
|
32
155
|
return AuthGate;
|
|
33
156
|
}
|
|
34
157
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
): ComponentType<{ children: ReactNode }> {
|
|
43
|
-
const AuthGate = makeAuthGate(LoginComponent, loginProps);
|
|
158
|
+
// SessionProvider + AuthGate as one gate, so a public gate in front (e.g.
|
|
159
|
+
// /calculator) doesn't mount the session bootstrap — createKumikoApp stacks
|
|
160
|
+
// providers outside all gates, so SessionProvider can't be a provider anymore.
|
|
161
|
+
export function makeSessionAuthGate(opts: LoginRouteOptions = {}): ComponentType<{
|
|
162
|
+
children: ReactNode;
|
|
163
|
+
}> {
|
|
164
|
+
const AuthGate = makeAuthGate(opts);
|
|
44
165
|
function SessionAuthGate({ children }: { readonly children: ReactNode }): ReactNode {
|
|
45
166
|
return (
|
|
46
167
|
<SessionProvider>
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
|
|
9
9
|
import type { ComponentType, ReactNode } from "react";
|
|
10
10
|
import { defaultTranslations, mergeTranslations } from "../i18n";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type MfaSetupComponentProps,
|
|
13
|
+
type MfaVerifyComponentProps,
|
|
14
|
+
makeSessionAuthGate,
|
|
15
|
+
} from "./auth-gate";
|
|
12
16
|
import type { LoginScreenProps } from "./login-screen";
|
|
13
17
|
|
|
14
18
|
export type EmailPasswordClientOptions = {
|
|
@@ -17,6 +21,17 @@ export type EmailPasswordClientOptions = {
|
|
|
17
21
|
* eine eigene Komponente mit derselben Signatur reichen. */
|
|
18
22
|
readonly loginScreen?: ComponentType<LoginScreenProps>;
|
|
19
23
|
readonly loginScreenProps?: LoginScreenProps;
|
|
24
|
+
/** auth-mfa's MfaVerifyScreen, wired in from the app so this feature
|
|
25
|
+
* stays unaware of auth-mfa's concrete shape. When LoginScreen's
|
|
26
|
+
* onMfaChallenge fires, the gate swaps to this component instead of
|
|
27
|
+
* requiring the app to own that state itself. Apps not mounting
|
|
28
|
+
* auth-mfa simply don't pass this — LoginScreen's built-in fallback
|
|
29
|
+
* error covers that case. */
|
|
30
|
+
readonly mfaVerifyScreen?: ComponentType<MfaVerifyComponentProps>;
|
|
31
|
+
/** auth-mfa's MfaSetupPreauthScreen, wired in from the app the same way
|
|
32
|
+
* as mfaVerifyScreen above. Apps not mounting auth-mfa simply don't
|
|
33
|
+
* pass this. */
|
|
34
|
+
readonly mfaSetupScreen?: ComponentType<MfaSetupComponentProps>;
|
|
20
35
|
/** Key-Overrides pro Locale. Wird mit den Default-Bundles (de/en)
|
|
21
36
|
* aus `translations.ts` gemerged — jeder hier gesetzte Key gewinnt.
|
|
22
37
|
* Für Branding ("Sign in" → "Login to Acme") oder weitere Sprachen
|
|
@@ -41,7 +56,14 @@ export function emailPasswordClient(
|
|
|
41
56
|
return {
|
|
42
57
|
name: "auth-email-password",
|
|
43
58
|
providers: [],
|
|
44
|
-
gates: [
|
|
59
|
+
gates: [
|
|
60
|
+
makeSessionAuthGate({
|
|
61
|
+
loginScreen: options.loginScreen,
|
|
62
|
+
loginScreenProps: options.loginScreenProps,
|
|
63
|
+
mfaVerifyScreen: options.mfaVerifyScreen,
|
|
64
|
+
mfaSetupScreen: options.mfaSetupScreen,
|
|
65
|
+
}),
|
|
66
|
+
],
|
|
45
67
|
translations,
|
|
46
68
|
};
|
|
47
69
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// @runtime client
|
|
2
|
+
// ConfirmAccountUnlockScreen — reads `?token=...` from the URL, auto-posts
|
|
3
|
+
// it to /api/auth/confirm-account-unlock on mount, shows success/error.
|
|
4
|
+
// Idempotent server-side (clearLockoutState is a no-op when already
|
|
5
|
+
// unlocked), so no re-submit risk. Mirrors VerifyEmailScreen 1:1.
|
|
6
|
+
//
|
|
7
|
+
// useEffect with empty deps + a once-guard so React Strict Mode's double
|
|
8
|
+
// useEffect dispatch in dev fires the confirm call only once.
|
|
9
|
+
|
|
10
|
+
import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
11
|
+
import { type ReactNode, useEffect, useRef, useState } from "react";
|
|
12
|
+
import { confirmAccountUnlock } from "./auth-client";
|
|
13
|
+
import { AuthCard, useUrlToken } from "./auth-form-primitives";
|
|
14
|
+
|
|
15
|
+
export type ConfirmAccountUnlockScreenProps = {
|
|
16
|
+
readonly title?: string;
|
|
17
|
+
/** Override for the token from the URL. Default: parsed from `?token=...`. */
|
|
18
|
+
readonly token?: string;
|
|
19
|
+
/** href for the "go to sign in" link. Default "/login". */
|
|
20
|
+
readonly loginHref?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type Status = "verifying" | "success" | "error" | "missing-token";
|
|
24
|
+
|
|
25
|
+
export function ConfirmAccountUnlockScreen({
|
|
26
|
+
title,
|
|
27
|
+
token: tokenProp,
|
|
28
|
+
loginHref = "/login",
|
|
29
|
+
}: ConfirmAccountUnlockScreenProps): ReactNode {
|
|
30
|
+
const t = useTranslation();
|
|
31
|
+
const { Link } = usePrimitives();
|
|
32
|
+
const token = useUrlToken(tokenProp);
|
|
33
|
+
const [status, setStatus] = useState<Status>(token === "" ? "missing-token" : "verifying");
|
|
34
|
+
const startedRef = useRef(false);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (status !== "verifying") return;
|
|
38
|
+
if (startedRef.current) return;
|
|
39
|
+
startedRef.current = true;
|
|
40
|
+
void (async () => {
|
|
41
|
+
const res = await confirmAccountUnlock(token);
|
|
42
|
+
setStatus(res.ok ? "success" : "error");
|
|
43
|
+
})();
|
|
44
|
+
}, [status, token]);
|
|
45
|
+
|
|
46
|
+
if (status === "missing-token") {
|
|
47
|
+
return (
|
|
48
|
+
<AuthCard title={title ?? t("auth.unlockAccount.errorTitle")}>
|
|
49
|
+
<div className="p-6 pt-0 flex flex-col gap-4">
|
|
50
|
+
<p className="text-sm text-muted-foreground">{t("auth.unlockAccount.missingToken")}</p>
|
|
51
|
+
<Link href={loginHref} variant="muted">
|
|
52
|
+
{t("auth.unlockAccount.goToLogin")}
|
|
53
|
+
</Link>
|
|
54
|
+
</div>
|
|
55
|
+
</AuthCard>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (status === "verifying") {
|
|
60
|
+
return (
|
|
61
|
+
<AuthCard>
|
|
62
|
+
<div className="p-6">
|
|
63
|
+
<p className="text-sm text-muted-foreground" role="status">
|
|
64
|
+
{t("auth.unlockAccount.verifying")}
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
</AuthCard>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (status === "success") {
|
|
72
|
+
return (
|
|
73
|
+
<AuthCard title={title ?? t("auth.unlockAccount.successTitle")}>
|
|
74
|
+
<div className="p-6 pt-0 flex flex-col gap-4">
|
|
75
|
+
<p className="text-sm text-muted-foreground">{t("auth.unlockAccount.successBody")}</p>
|
|
76
|
+
<Link href={loginHref} variant="button">
|
|
77
|
+
{t("auth.unlockAccount.goToLogin")}
|
|
78
|
+
</Link>
|
|
79
|
+
</div>
|
|
80
|
+
</AuthCard>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// status === "error"
|
|
85
|
+
return (
|
|
86
|
+
<AuthCard title={title ?? t("auth.unlockAccount.errorTitle")}>
|
|
87
|
+
<div className="p-6 pt-0 flex flex-col gap-4">
|
|
88
|
+
<p className="text-sm text-muted-foreground">{t("auth.unlockAccount.errorBody")}</p>
|
|
89
|
+
<Link href={loginHref} variant="muted">
|
|
90
|
+
{t("auth.unlockAccount.goToLogin")}
|
|
91
|
+
</Link>
|
|
92
|
+
</div>
|
|
93
|
+
</AuthCard>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
13
13
|
import { type FormEvent, type ReactNode, useState } from "react";
|
|
14
14
|
import { requestPasswordReset } from "./auth-client";
|
|
15
|
-
import { AuthCard
|
|
15
|
+
import { AuthCard } from "./auth-form-primitives";
|
|
16
16
|
|
|
17
17
|
export type ForgotPasswordScreenProps = {
|
|
18
18
|
readonly title?: string;
|
|
@@ -28,7 +28,7 @@ export function ForgotPasswordScreen({
|
|
|
28
28
|
loginHref = "/login",
|
|
29
29
|
}: ForgotPasswordScreenProps): ReactNode {
|
|
30
30
|
const t = useTranslation();
|
|
31
|
-
const { Form, Field, Input, Button, Banner } = usePrimitives();
|
|
31
|
+
const { Form, Field, Input, Button, Banner, Link } = usePrimitives();
|
|
32
32
|
const [email, setEmail] = useState("");
|
|
33
33
|
const [submitting, setSubmitting] = useState(false);
|
|
34
34
|
const [done, setDone] = useState(false);
|
|
@@ -77,9 +77,9 @@ export function ForgotPasswordScreen({
|
|
|
77
77
|
<p className="font-medium text-foreground">{t("auth.forgotPassword.successTitle")}</p>
|
|
78
78
|
<p className="mt-1">{t("auth.forgotPassword.successBody")}</p>
|
|
79
79
|
</Banner>
|
|
80
|
-
<
|
|
80
|
+
<Link href={loginHref} variant="muted">
|
|
81
81
|
{t("auth.forgotPassword.backToLogin")}
|
|
82
|
-
</
|
|
82
|
+
</Link>
|
|
83
83
|
</div>
|
|
84
84
|
) : (
|
|
85
85
|
<div className="p-6 pt-0 flex flex-col gap-4">
|
|
@@ -101,9 +101,9 @@ export function ForgotPasswordScreen({
|
|
|
101
101
|
{submitting ? t("auth.forgotPassword.submitting") : t("auth.forgotPassword.submit")}
|
|
102
102
|
</Button>
|
|
103
103
|
</Form>
|
|
104
|
-
<
|
|
104
|
+
<Link href={loginHref} variant="muted" className="self-center">
|
|
105
105
|
{t("auth.forgotPassword.backToLogin")}
|
|
106
|
-
</
|
|
106
|
+
</Link>
|
|
107
107
|
</div>
|
|
108
108
|
)}
|
|
109
109
|
</AuthCard>
|