@entropy-softworks/ui 2026.7.31
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/CHANGELOG.md +96 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/lib/components/auth/LockOverlay.d.ts +22 -0
- package/lib/components/auth/LockOverlay.d.ts.map +1 -0
- package/lib/components/auth/LockOverlay.jsx +82 -0
- package/lib/components/auth/LockOverlay.jsx.map +1 -0
- package/lib/components/auth/PasswordStrengthMeter.d.ts +12 -0
- package/lib/components/auth/PasswordStrengthMeter.d.ts.map +1 -0
- package/lib/components/auth/PasswordStrengthMeter.jsx +81 -0
- package/lib/components/auth/PasswordStrengthMeter.jsx.map +1 -0
- package/lib/components/auth/ProtectedRoute.d.ts +24 -0
- package/lib/components/auth/ProtectedRoute.d.ts.map +1 -0
- package/lib/components/auth/ProtectedRoute.jsx +56 -0
- package/lib/components/auth/ProtectedRoute.jsx.map +1 -0
- package/lib/components/auth/PwnedPasswordWarning.d.ts +13 -0
- package/lib/components/auth/PwnedPasswordWarning.d.ts.map +1 -0
- package/lib/components/auth/PwnedPasswordWarning.jsx +29 -0
- package/lib/components/auth/PwnedPasswordWarning.jsx.map +1 -0
- package/lib/components/auth/index.d.ts +5 -0
- package/lib/components/auth/index.d.ts.map +1 -0
- package/lib/components/auth/index.js +5 -0
- package/lib/components/auth/index.js.map +1 -0
- package/lib/components/bottom-nav.d.ts +25 -0
- package/lib/components/bottom-nav.d.ts.map +1 -0
- package/lib/components/bottom-nav.jsx +51 -0
- package/lib/components/bottom-nav.jsx.map +1 -0
- package/lib/components/button.d.ts +16 -0
- package/lib/components/button.d.ts.map +1 -0
- package/lib/components/button.jsx +73 -0
- package/lib/components/button.jsx.map +1 -0
- package/lib/components/card.d.ts +22 -0
- package/lib/components/card.d.ts.map +1 -0
- package/lib/components/card.jsx +36 -0
- package/lib/components/card.jsx.map +1 -0
- package/lib/components/checkbox.d.ts +12 -0
- package/lib/components/checkbox.d.ts.map +1 -0
- package/lib/components/checkbox.jsx +16 -0
- package/lib/components/checkbox.jsx.map +1 -0
- package/lib/components/connection-settings.d.ts +41 -0
- package/lib/components/connection-settings.d.ts.map +1 -0
- package/lib/components/connection-settings.jsx +354 -0
- package/lib/components/connection-settings.jsx.map +1 -0
- package/lib/components/context-menu.d.ts +52 -0
- package/lib/components/context-menu.d.ts.map +1 -0
- package/lib/components/context-menu.jsx +24 -0
- package/lib/components/context-menu.jsx.map +1 -0
- package/lib/components/data-table.d.ts +59 -0
- package/lib/components/data-table.d.ts.map +1 -0
- package/lib/components/data-table.jsx +101 -0
- package/lib/components/data-table.jsx.map +1 -0
- package/lib/components/dialog.d.ts +58 -0
- package/lib/components/dialog.d.ts.map +1 -0
- package/lib/components/dialog.jsx +197 -0
- package/lib/components/dialog.jsx.map +1 -0
- package/lib/components/dropdown-menu.d.ts +55 -0
- package/lib/components/dropdown-menu.d.ts.map +1 -0
- package/lib/components/dropdown-menu.jsx +38 -0
- package/lib/components/dropdown-menu.jsx.map +1 -0
- package/lib/components/entropy-boot-splash.d.ts +24 -0
- package/lib/components/entropy-boot-splash.d.ts.map +1 -0
- package/lib/components/entropy-boot-splash.jsx +83 -0
- package/lib/components/entropy-boot-splash.jsx.map +1 -0
- package/lib/components/entropy-mark-animated.d.ts +20 -0
- package/lib/components/entropy-mark-animated.d.ts.map +1 -0
- package/lib/components/entropy-mark-animated.jsx +148 -0
- package/lib/components/entropy-mark-animated.jsx.map +1 -0
- package/lib/components/entropy-mark-geometry.d.ts +8 -0
- package/lib/components/entropy-mark-geometry.d.ts.map +1 -0
- package/lib/components/entropy-mark-geometry.js +10 -0
- package/lib/components/entropy-mark-geometry.js.map +1 -0
- package/lib/components/entropy-mark-sequential.d.ts +12 -0
- package/lib/components/entropy-mark-sequential.d.ts.map +1 -0
- package/lib/components/entropy-mark-sequential.jsx +53 -0
- package/lib/components/entropy-mark-sequential.jsx.map +1 -0
- package/lib/components/entropy-mark.d.ts +29 -0
- package/lib/components/entropy-mark.d.ts.map +1 -0
- package/lib/components/entropy-mark.jsx +66 -0
- package/lib/components/entropy-mark.jsx.map +1 -0
- package/lib/components/error-boundary.d.ts +31 -0
- package/lib/components/error-boundary.d.ts.map +1 -0
- package/lib/components/error-boundary.jsx +40 -0
- package/lib/components/error-boundary.jsx.map +1 -0
- package/lib/components/index.d.ts +41 -0
- package/lib/components/index.d.ts.map +1 -0
- package/lib/components/index.js +43 -0
- package/lib/components/index.js.map +1 -0
- package/lib/components/input.d.ts +26 -0
- package/lib/components/input.d.ts.map +1 -0
- package/lib/components/input.jsx +95 -0
- package/lib/components/input.jsx.map +1 -0
- package/lib/components/item-icon.d.ts +32 -0
- package/lib/components/item-icon.d.ts.map +1 -0
- package/lib/components/item-icon.jsx +54 -0
- package/lib/components/item-icon.jsx.map +1 -0
- package/lib/components/label.d.ts +13 -0
- package/lib/components/label.d.ts.map +1 -0
- package/lib/components/label.jsx +9 -0
- package/lib/components/label.jsx.map +1 -0
- package/lib/components/legal-dialog.d.ts +18 -0
- package/lib/components/legal-dialog.d.ts.map +1 -0
- package/lib/components/legal-dialog.jsx +31 -0
- package/lib/components/legal-dialog.jsx.map +1 -0
- package/lib/components/license-gate.d.ts +17 -0
- package/lib/components/license-gate.d.ts.map +1 -0
- package/lib/components/license-gate.jsx +21 -0
- package/lib/components/license-gate.jsx.map +1 -0
- package/lib/components/logo.d.ts +15 -0
- package/lib/components/logo.d.ts.map +1 -0
- package/lib/components/logo.jsx +20 -0
- package/lib/components/logo.jsx.map +1 -0
- package/lib/components/onboarding-wizard.d.ts +89 -0
- package/lib/components/onboarding-wizard.d.ts.map +1 -0
- package/lib/components/onboarding-wizard.jsx +185 -0
- package/lib/components/onboarding-wizard.jsx.map +1 -0
- package/lib/components/pagination.d.ts +43 -0
- package/lib/components/pagination.d.ts.map +1 -0
- package/lib/components/pagination.jsx +72 -0
- package/lib/components/pagination.jsx.map +1 -0
- package/lib/components/password-generator.d.ts +15 -0
- package/lib/components/password-generator.d.ts.map +1 -0
- package/lib/components/password-generator.jsx +273 -0
- package/lib/components/password-generator.jsx.map +1 -0
- package/lib/components/pin-pad.d.ts +20 -0
- package/lib/components/pin-pad.d.ts.map +1 -0
- package/lib/components/pin-pad.jsx +93 -0
- package/lib/components/pin-pad.jsx.map +1 -0
- package/lib/components/popover.d.ts +27 -0
- package/lib/components/popover.d.ts.map +1 -0
- package/lib/components/popover.jsx +18 -0
- package/lib/components/popover.jsx.map +1 -0
- package/lib/components/profile/ProfileDialog.d.ts +25 -0
- package/lib/components/profile/ProfileDialog.d.ts.map +1 -0
- package/lib/components/profile/ProfileDialog.jsx +71 -0
- package/lib/components/profile/ProfileDialog.jsx.map +1 -0
- package/lib/components/profile/ProfileTab.d.ts +12 -0
- package/lib/components/profile/ProfileTab.d.ts.map +1 -0
- package/lib/components/profile/ProfileTab.jsx +112 -0
- package/lib/components/profile/ProfileTab.jsx.map +1 -0
- package/lib/components/profile/SecurityTab.d.ts +32 -0
- package/lib/components/profile/SecurityTab.d.ts.map +1 -0
- package/lib/components/profile/SecurityTab.jsx +145 -0
- package/lib/components/profile/SecurityTab.jsx.map +1 -0
- package/lib/components/profile/index.d.ts +4 -0
- package/lib/components/profile/index.d.ts.map +1 -0
- package/lib/components/profile/index.js +4 -0
- package/lib/components/profile/index.js.map +1 -0
- package/lib/components/segmented-toggle.d.ts +28 -0
- package/lib/components/segmented-toggle.d.ts.map +1 -0
- package/lib/components/segmented-toggle.jsx +64 -0
- package/lib/components/segmented-toggle.jsx.map +1 -0
- package/lib/components/separator.d.ts +13 -0
- package/lib/components/separator.d.ts.map +1 -0
- package/lib/components/separator.jsx +16 -0
- package/lib/components/separator.jsx.map +1 -0
- package/lib/components/server-settings.d.ts +27 -0
- package/lib/components/server-settings.d.ts.map +1 -0
- package/lib/components/server-settings.jsx +307 -0
- package/lib/components/server-settings.jsx.map +1 -0
- package/lib/components/settings/AccountSection.d.ts +26 -0
- package/lib/components/settings/AccountSection.d.ts.map +1 -0
- package/lib/components/settings/AccountSection.jsx +32 -0
- package/lib/components/settings/AccountSection.jsx.map +1 -0
- package/lib/components/settings/DangerZoneSection.d.ts +13 -0
- package/lib/components/settings/DangerZoneSection.d.ts.map +1 -0
- package/lib/components/settings/DangerZoneSection.jsx +16 -0
- package/lib/components/settings/DangerZoneSection.jsx.map +1 -0
- package/lib/components/settings/GeneralSection.d.ts +11 -0
- package/lib/components/settings/GeneralSection.d.ts.map +1 -0
- package/lib/components/settings/GeneralSection.jsx +12 -0
- package/lib/components/settings/GeneralSection.jsx.map +1 -0
- package/lib/components/settings/SupportSection.d.ts +11 -0
- package/lib/components/settings/SupportSection.d.ts.map +1 -0
- package/lib/components/settings/SupportSection.jsx +22 -0
- package/lib/components/settings/SupportSection.jsx.map +1 -0
- package/lib/components/settings/index.d.ts +5 -0
- package/lib/components/settings/index.d.ts.map +1 -0
- package/lib/components/settings/index.js +5 -0
- package/lib/components/settings/index.js.map +1 -0
- package/lib/components/settings-row.d.ts +28 -0
- package/lib/components/settings-row.d.ts.map +1 -0
- package/lib/components/settings-row.jsx +68 -0
- package/lib/components/settings-row.jsx.map +1 -0
- package/lib/components/skeleton.d.ts +8 -0
- package/lib/components/skeleton.d.ts.map +1 -0
- package/lib/components/skeleton.jsx +24 -0
- package/lib/components/skeleton.jsx.map +1 -0
- package/lib/components/social-connections.d.ts +15 -0
- package/lib/components/social-connections.d.ts.map +1 -0
- package/lib/components/social-connections.jsx +31 -0
- package/lib/components/social-connections.jsx.map +1 -0
- package/lib/components/stat-card.d.ts +29 -0
- package/lib/components/stat-card.d.ts.map +1 -0
- package/lib/components/stat-card.jsx +36 -0
- package/lib/components/stat-card.jsx.map +1 -0
- package/lib/components/tag-chip.d.ts +18 -0
- package/lib/components/tag-chip.d.ts.map +1 -0
- package/lib/components/tag-chip.jsx +54 -0
- package/lib/components/tag-chip.jsx.map +1 -0
- package/lib/components/tag-multi-select.d.ts +27 -0
- package/lib/components/tag-multi-select.d.ts.map +1 -0
- package/lib/components/tag-multi-select.jsx +65 -0
- package/lib/components/tag-multi-select.jsx.map +1 -0
- package/lib/components/text.d.ts +22 -0
- package/lib/components/text.d.ts.map +1 -0
- package/lib/components/text.jsx +43 -0
- package/lib/components/text.jsx.map +1 -0
- package/lib/components/toast/ToastPortal.d.ts +11 -0
- package/lib/components/toast/ToastPortal.d.ts.map +1 -0
- package/lib/components/toast/ToastPortal.jsx +15 -0
- package/lib/components/toast/ToastPortal.jsx.map +1 -0
- package/lib/components/toast/ToastPortal.web.d.ts +14 -0
- package/lib/components/toast/ToastPortal.web.d.ts.map +1 -0
- package/lib/components/toast/ToastPortal.web.jsx +39 -0
- package/lib/components/toast/ToastPortal.web.jsx.map +1 -0
- package/lib/components/toast/index.d.ts +3 -0
- package/lib/components/toast/index.d.ts.map +1 -0
- package/lib/components/toast/index.js +3 -0
- package/lib/components/toast/index.js.map +1 -0
- package/lib/components/toast/toast-config.d.ts +14 -0
- package/lib/components/toast/toast-config.d.ts.map +1 -0
- package/lib/components/toast/toast-config.jsx +24 -0
- package/lib/components/toast/toast-config.jsx.map +1 -0
- package/lib/components/tooltip.d.ts +30 -0
- package/lib/components/tooltip.d.ts.map +1 -0
- package/lib/components/tooltip.jsx +14 -0
- package/lib/components/tooltip.jsx.map +1 -0
- package/lib/components/totp-display.d.ts +23 -0
- package/lib/components/totp-display.d.ts.map +1 -0
- package/lib/components/totp-display.jsx +132 -0
- package/lib/components/totp-display.jsx.map +1 -0
- package/lib/components/user-avatar.d.ts +18 -0
- package/lib/components/user-avatar.d.ts.map +1 -0
- package/lib/components/user-avatar.jsx +25 -0
- package/lib/components/user-avatar.jsx.map +1 -0
- package/lib/context/AppConfigContext.d.ts +56 -0
- package/lib/context/AppConfigContext.d.ts.map +1 -0
- package/lib/context/AppConfigContext.jsx +14 -0
- package/lib/context/AppConfigContext.jsx.map +1 -0
- package/lib/context/NavigationContext.d.ts +42 -0
- package/lib/context/NavigationContext.d.ts.map +1 -0
- package/lib/context/NavigationContext.jsx +53 -0
- package/lib/context/NavigationContext.jsx.map +1 -0
- package/lib/context/NotificationContext.d.ts +32 -0
- package/lib/context/NotificationContext.d.ts.map +1 -0
- package/lib/context/NotificationContext.jsx +49 -0
- package/lib/context/NotificationContext.jsx.map +1 -0
- package/lib/context/ThemeContext.d.ts +12 -0
- package/lib/context/ThemeContext.d.ts.map +1 -0
- package/lib/context/ThemeContext.jsx +66 -0
- package/lib/context/ThemeContext.jsx.map +1 -0
- package/lib/context/index.d.ts +5 -0
- package/lib/context/index.d.ts.map +1 -0
- package/lib/context/index.js +5 -0
- package/lib/context/index.js.map +1 -0
- package/lib/flags.d.ts +9 -0
- package/lib/flags.d.ts.map +1 -0
- package/lib/flags.js +37 -0
- package/lib/flags.js.map +1 -0
- package/lib/forms/form-error.d.ts +12 -0
- package/lib/forms/form-error.d.ts.map +1 -0
- package/lib/forms/form-error.jsx +14 -0
- package/lib/forms/form-error.jsx.map +1 -0
- package/lib/forms/index.d.ts +3 -0
- package/lib/forms/index.d.ts.map +1 -0
- package/lib/forms/index.js +3 -0
- package/lib/forms/index.js.map +1 -0
- package/lib/forms/shake-field.d.ts +18 -0
- package/lib/forms/shake-field.d.ts.map +1 -0
- package/lib/forms/shake-field.jsx +30 -0
- package/lib/forms/shake-field.jsx.map +1 -0
- package/lib/hooks/index.d.ts +6 -0
- package/lib/hooks/index.d.ts.map +1 -0
- package/lib/hooks/index.js +6 -0
- package/lib/hooks/index.js.map +1 -0
- package/lib/hooks/useAuth.d.ts +109 -0
- package/lib/hooks/useAuth.d.ts.map +1 -0
- package/lib/hooks/useAuth.jsx +404 -0
- package/lib/hooks/useAuth.jsx.map +1 -0
- package/lib/hooks/useAutoLock.d.ts +22 -0
- package/lib/hooks/useAutoLock.d.ts.map +1 -0
- package/lib/hooks/useAutoLock.js +85 -0
- package/lib/hooks/useAutoLock.js.map +1 -0
- package/lib/hooks/useDebouncedValue.d.ts +19 -0
- package/lib/hooks/useDebouncedValue.d.ts.map +1 -0
- package/lib/hooks/useDebouncedValue.js +29 -0
- package/lib/hooks/useDebouncedValue.js.map +1 -0
- package/lib/hooks/useShakeAnimation.d.ts +17 -0
- package/lib/hooks/useShakeAnimation.d.ts.map +1 -0
- package/lib/hooks/useShakeAnimation.js +22 -0
- package/lib/hooks/useShakeAnimation.js.map +1 -0
- package/lib/hooks/useUploadBlobUrl.d.ts +13 -0
- package/lib/hooks/useUploadBlobUrl.d.ts.map +1 -0
- package/lib/hooks/useUploadBlobUrl.js +60 -0
- package/lib/hooks/useUploadBlobUrl.js.map +1 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +47 -0
- package/lib/index.js.map +1 -0
- package/lib/screens/auth/AccountPickerScreen.d.ts +24 -0
- package/lib/screens/auth/AccountPickerScreen.d.ts.map +1 -0
- package/lib/screens/auth/AccountPickerScreen.jsx +47 -0
- package/lib/screens/auth/AccountPickerScreen.jsx.map +1 -0
- package/lib/screens/auth/AuthScreen.d.ts +59 -0
- package/lib/screens/auth/AuthScreen.d.ts.map +1 -0
- package/lib/screens/auth/AuthScreen.jsx +594 -0
- package/lib/screens/auth/AuthScreen.jsx.map +1 -0
- package/lib/screens/auth/ConsentScreen.d.ts +27 -0
- package/lib/screens/auth/ConsentScreen.d.ts.map +1 -0
- package/lib/screens/auth/ConsentScreen.jsx +55 -0
- package/lib/screens/auth/ConsentScreen.jsx.map +1 -0
- package/lib/screens/auth/ErrorScreen.d.ts +20 -0
- package/lib/screens/auth/ErrorScreen.d.ts.map +1 -0
- package/lib/screens/auth/ErrorScreen.jsx +43 -0
- package/lib/screens/auth/ErrorScreen.jsx.map +1 -0
- package/lib/screens/auth/ExpiredLinkScreen.d.ts +19 -0
- package/lib/screens/auth/ExpiredLinkScreen.d.ts.map +1 -0
- package/lib/screens/auth/ExpiredLinkScreen.jsx +38 -0
- package/lib/screens/auth/ExpiredLinkScreen.jsx.map +1 -0
- package/lib/screens/auth/ForgotPasswordScreen.d.ts +4 -0
- package/lib/screens/auth/ForgotPasswordScreen.d.ts.map +1 -0
- package/lib/screens/auth/ForgotPasswordScreen.jsx +123 -0
- package/lib/screens/auth/ForgotPasswordScreen.jsx.map +1 -0
- package/lib/screens/auth/ImportDataScreen.d.ts +17 -0
- package/lib/screens/auth/ImportDataScreen.d.ts.map +1 -0
- package/lib/screens/auth/ImportDataScreen.jsx +73 -0
- package/lib/screens/auth/ImportDataScreen.jsx.map +1 -0
- package/lib/screens/auth/LoginScreen.d.ts +38 -0
- package/lib/screens/auth/LoginScreen.d.ts.map +1 -0
- package/lib/screens/auth/LoginScreen.jsx +326 -0
- package/lib/screens/auth/LoginScreen.jsx.map +1 -0
- package/lib/screens/auth/MfaChallengeScreen.d.ts +4 -0
- package/lib/screens/auth/MfaChallengeScreen.d.ts.map +1 -0
- package/lib/screens/auth/MfaChallengeScreen.jsx +139 -0
- package/lib/screens/auth/MfaChallengeScreen.jsx.map +1 -0
- package/lib/screens/auth/MfaEnrollScreen.d.ts +12 -0
- package/lib/screens/auth/MfaEnrollScreen.d.ts.map +1 -0
- package/lib/screens/auth/MfaEnrollScreen.jsx +267 -0
- package/lib/screens/auth/MfaEnrollScreen.jsx.map +1 -0
- package/lib/screens/auth/PasskeyEnrollScreen.d.ts +18 -0
- package/lib/screens/auth/PasskeyEnrollScreen.d.ts.map +1 -0
- package/lib/screens/auth/PasskeyEnrollScreen.jsx +51 -0
- package/lib/screens/auth/PasskeyEnrollScreen.jsx.map +1 -0
- package/lib/screens/auth/PrivacyPolicyScreen.d.ts +19 -0
- package/lib/screens/auth/PrivacyPolicyScreen.d.ts.map +1 -0
- package/lib/screens/auth/PrivacyPolicyScreen.jsx +143 -0
- package/lib/screens/auth/PrivacyPolicyScreen.jsx.map +1 -0
- package/lib/screens/auth/RegisterScreen.d.ts +14 -0
- package/lib/screens/auth/RegisterScreen.d.ts.map +1 -0
- package/lib/screens/auth/RegisterScreen.jsx +296 -0
- package/lib/screens/auth/RegisterScreen.jsx.map +1 -0
- package/lib/screens/auth/ResetPasswordScreen.d.ts +12 -0
- package/lib/screens/auth/ResetPasswordScreen.d.ts.map +1 -0
- package/lib/screens/auth/ResetPasswordScreen.jsx +276 -0
- package/lib/screens/auth/ResetPasswordScreen.jsx.map +1 -0
- package/lib/screens/auth/ServerConfigScreen.d.ts +18 -0
- package/lib/screens/auth/ServerConfigScreen.d.ts.map +1 -0
- package/lib/screens/auth/ServerConfigScreen.jsx +259 -0
- package/lib/screens/auth/ServerConfigScreen.jsx.map +1 -0
- package/lib/screens/auth/TermsOfServiceScreen.d.ts +30 -0
- package/lib/screens/auth/TermsOfServiceScreen.d.ts.map +1 -0
- package/lib/screens/auth/TermsOfServiceScreen.jsx +130 -0
- package/lib/screens/auth/TermsOfServiceScreen.jsx.map +1 -0
- package/lib/screens/auth/VerifyEmailScreen.d.ts +4 -0
- package/lib/screens/auth/VerifyEmailScreen.d.ts.map +1 -0
- package/lib/screens/auth/VerifyEmailScreen.jsx +249 -0
- package/lib/screens/auth/VerifyEmailScreen.jsx.map +1 -0
- package/lib/screens/auth/WelcomeScreen.d.ts +17 -0
- package/lib/screens/auth/WelcomeScreen.d.ts.map +1 -0
- package/lib/screens/auth/WelcomeScreen.jsx +58 -0
- package/lib/screens/auth/WelcomeScreen.jsx.map +1 -0
- package/lib/screens/auth/index.d.ts +19 -0
- package/lib/screens/auth/index.d.ts.map +1 -0
- package/lib/screens/auth/index.js +19 -0
- package/lib/screens/auth/index.js.map +1 -0
- package/lib/screens/index.d.ts +2 -0
- package/lib/screens/index.d.ts.map +1 -0
- package/lib/screens/index.js +2 -0
- package/lib/screens/index.js.map +1 -0
- package/lib/services/api-config.d.ts +87 -0
- package/lib/services/api-config.d.ts.map +1 -0
- package/lib/services/api-config.js +202 -0
- package/lib/services/api-config.js.map +1 -0
- package/lib/services/auth.service.d.ts +140 -0
- package/lib/services/auth.service.d.ts.map +1 -0
- package/lib/services/auth.service.js +376 -0
- package/lib/services/auth.service.js.map +1 -0
- package/lib/services/favicon.d.ts +46 -0
- package/lib/services/favicon.d.ts.map +1 -0
- package/lib/services/favicon.js +84 -0
- package/lib/services/favicon.js.map +1 -0
- package/lib/services/hibp-cache.d.ts +23 -0
- package/lib/services/hibp-cache.d.ts.map +1 -0
- package/lib/services/hibp-cache.js +56 -0
- package/lib/services/hibp-cache.js.map +1 -0
- package/lib/services/hibp.d.ts +28 -0
- package/lib/services/hibp.d.ts.map +1 -0
- package/lib/services/hibp.js +92 -0
- package/lib/services/hibp.js.map +1 -0
- package/lib/services/index.d.ts +8 -0
- package/lib/services/index.d.ts.map +1 -0
- package/lib/services/index.js +7 -0
- package/lib/services/index.js.map +1 -0
- package/lib/services/passkey.d.ts +10 -0
- package/lib/services/passkey.d.ts.map +1 -0
- package/lib/services/passkey.js +192 -0
- package/lib/services/passkey.js.map +1 -0
- package/lib/utils/api-fetch.d.ts +11 -0
- package/lib/utils/api-fetch.d.ts.map +1 -0
- package/lib/utils/api-fetch.js +52 -0
- package/lib/utils/api-fetch.js.map +1 -0
- package/lib/utils/clipboard.d.ts +31 -0
- package/lib/utils/clipboard.d.ts.map +1 -0
- package/lib/utils/clipboard.js +66 -0
- package/lib/utils/clipboard.js.map +1 -0
- package/lib/utils/cn.d.ts +9 -0
- package/lib/utils/cn.d.ts.map +1 -0
- package/lib/utils/cn.js +12 -0
- package/lib/utils/cn.js.map +1 -0
- package/lib/utils/debounce.d.ts +13 -0
- package/lib/utils/debounce.d.ts.map +1 -0
- package/lib/utils/debounce.js +28 -0
- package/lib/utils/debounce.js.map +1 -0
- package/lib/utils/error.d.ts +9 -0
- package/lib/utils/error.d.ts.map +1 -0
- package/lib/utils/error.js +11 -0
- package/lib/utils/error.js.map +1 -0
- package/lib/utils/fetch.d.ts +17 -0
- package/lib/utils/fetch.d.ts.map +1 -0
- package/lib/utils/fetch.js +29 -0
- package/lib/utils/fetch.js.map +1 -0
- package/lib/utils/http-error.d.ts +41 -0
- package/lib/utils/http-error.d.ts.map +1 -0
- package/lib/utils/http-error.js +63 -0
- package/lib/utils/http-error.js.map +1 -0
- package/lib/utils/index.d.ts +11 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/index.js +11 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/palette.d.ts +46 -0
- package/lib/utils/palette.d.ts.map +1 -0
- package/lib/utils/palette.js +75 -0
- package/lib/utils/palette.js.map +1 -0
- package/lib/utils/password.d.ts +20 -0
- package/lib/utils/password.d.ts.map +1 -0
- package/lib/utils/password.js +51 -0
- package/lib/utils/password.js.map +1 -0
- package/lib/utils/settings-storage.d.ts +3 -0
- package/lib/utils/settings-storage.d.ts.map +1 -0
- package/lib/utils/settings-storage.js +62 -0
- package/lib/utils/settings-storage.js.map +1 -0
- package/lib/utils/validation.d.ts +29 -0
- package/lib/utils/validation.d.ts.map +1 -0
- package/lib/utils/validation.js +58 -0
- package/lib/utils/validation.js.map +1 -0
- package/package.json +204 -0
- package/src/components/__tests__/data-table.test.ts +94 -0
- package/src/components/__tests__/pagination.test.ts +73 -0
- package/src/components/__tests__/stat-card.test.ts +21 -0
- package/src/components/auth/LockOverlay.tsx +122 -0
- package/src/components/auth/PasswordStrengthMeter.tsx +96 -0
- package/src/components/auth/ProtectedRoute.tsx +91 -0
- package/src/components/auth/PwnedPasswordWarning.tsx +54 -0
- package/src/components/auth/index.ts +10 -0
- package/src/components/bottom-nav.tsx +99 -0
- package/src/components/button.tsx +144 -0
- package/src/components/card.tsx +82 -0
- package/src/components/checkbox.tsx +48 -0
- package/src/components/connection-settings.tsx +583 -0
- package/src/components/context-menu.tsx +95 -0
- package/src/components/data-table.tsx +223 -0
- package/src/components/dialog.tsx +376 -0
- package/src/components/dropdown-menu.tsx +120 -0
- package/src/components/entropy-boot-splash.tsx +114 -0
- package/src/components/entropy-mark-animated.tsx +260 -0
- package/src/components/entropy-mark-geometry.ts +11 -0
- package/src/components/entropy-mark-sequential.tsx +132 -0
- package/src/components/entropy-mark.tsx +135 -0
- package/src/components/error-boundary.tsx +70 -0
- package/src/components/index.ts +156 -0
- package/src/components/input.tsx +246 -0
- package/src/components/item-icon.tsx +111 -0
- package/src/components/label.tsx +25 -0
- package/src/components/legal-dialog.tsx +47 -0
- package/src/components/license-gate.tsx +28 -0
- package/src/components/logo.tsx +34 -0
- package/src/components/onboarding-wizard.tsx +353 -0
- package/src/components/pagination.tsx +154 -0
- package/src/components/password-generator.tsx +393 -0
- package/src/components/pin-pad.tsx +156 -0
- package/src/components/popover.tsx +38 -0
- package/src/components/profile/ProfileDialog.tsx +129 -0
- package/src/components/profile/ProfileTab.tsx +151 -0
- package/src/components/profile/SecurityTab.tsx +229 -0
- package/src/components/profile/index.ts +3 -0
- package/src/components/segmented-toggle.tsx +119 -0
- package/src/components/separator.tsx +41 -0
- package/src/components/server-settings.tsx +442 -0
- package/src/components/settings/AccountSection.tsx +76 -0
- package/src/components/settings/DangerZoneSection.tsx +37 -0
- package/src/components/settings/GeneralSection.tsx +39 -0
- package/src/components/settings/SupportSection.tsx +53 -0
- package/src/components/settings/index.ts +4 -0
- package/src/components/settings-row.tsx +126 -0
- package/src/components/skeleton.tsx +48 -0
- package/src/components/social-connections.tsx +55 -0
- package/src/components/stat-card.tsx +64 -0
- package/src/components/tag-chip.tsx +78 -0
- package/src/components/tag-multi-select.tsx +136 -0
- package/src/components/text.tsx +77 -0
- package/src/components/toast/ToastPortal.tsx +16 -0
- package/src/components/toast/ToastPortal.web.tsx +47 -0
- package/src/components/toast/index.ts +2 -0
- package/src/components/toast/toast-config.tsx +31 -0
- package/src/components/tooltip.tsx +30 -0
- package/src/components/totp-display.tsx +207 -0
- package/src/components/user-avatar.tsx +46 -0
- package/src/context/AppConfigContext.tsx +76 -0
- package/src/context/NavigationContext.tsx +75 -0
- package/src/context/NotificationContext.tsx +69 -0
- package/src/context/ThemeContext.tsx +83 -0
- package/src/context/__tests__/NavigationContext.test.tsx +53 -0
- package/src/context/index.ts +24 -0
- package/src/flags.ts +44 -0
- package/src/forms/form-error.tsx +20 -0
- package/src/forms/index.ts +2 -0
- package/src/forms/shake-field.tsx +52 -0
- package/src/hooks/__tests__/useUploadBlobUrl.test.tsx +77 -0
- package/src/hooks/index.ts +18 -0
- package/src/hooks/useAuth.tsx +567 -0
- package/src/hooks/useAutoLock.ts +110 -0
- package/src/hooks/useDebouncedValue.ts +32 -0
- package/src/hooks/useShakeAnimation.ts +42 -0
- package/src/hooks/useUploadBlobUrl.ts +75 -0
- package/src/index.ts +48 -0
- package/src/screens/auth/AccountPickerScreen.tsx +103 -0
- package/src/screens/auth/AuthScreen.tsx +941 -0
- package/src/screens/auth/ConsentScreen.tsx +118 -0
- package/src/screens/auth/ErrorScreen.tsx +96 -0
- package/src/screens/auth/ExpiredLinkScreen.tsx +87 -0
- package/src/screens/auth/ForgotPasswordScreen.tsx +182 -0
- package/src/screens/auth/ImportDataScreen.tsx +112 -0
- package/src/screens/auth/LoginScreen.tsx +484 -0
- package/src/screens/auth/MfaChallengeScreen.tsx +216 -0
- package/src/screens/auth/MfaEnrollScreen.tsx +388 -0
- package/src/screens/auth/PasskeyEnrollScreen.tsx +96 -0
- package/src/screens/auth/PrivacyPolicyScreen.tsx +249 -0
- package/src/screens/auth/RegisterScreen.tsx +430 -0
- package/src/screens/auth/ResetPasswordScreen.tsx +377 -0
- package/src/screens/auth/ServerConfigScreen.tsx +388 -0
- package/src/screens/auth/TermsOfServiceScreen.tsx +236 -0
- package/src/screens/auth/VerifyEmailScreen.tsx +310 -0
- package/src/screens/auth/WelcomeScreen.tsx +78 -0
- package/src/screens/auth/index.ts +36 -0
- package/src/screens/index.ts +1 -0
- package/src/services/__tests__/api-config.test.ts +67 -0
- package/src/services/__tests__/favicon.test.ts +88 -0
- package/src/services/__tests__/hibp-cache.test.ts +37 -0
- package/src/services/__tests__/hibp.test.ts +39 -0
- package/src/services/api-config.ts +260 -0
- package/src/services/auth.service.ts +498 -0
- package/src/services/favicon.ts +102 -0
- package/src/services/hibp-cache.ts +71 -0
- package/src/services/hibp.ts +120 -0
- package/src/services/index.ts +52 -0
- package/src/services/passkey.ts +213 -0
- package/src/types/external.d.ts +24 -0
- package/src/utils/__tests__/clipboard.test.ts +59 -0
- package/src/utils/__tests__/cn.test.ts +24 -0
- package/src/utils/__tests__/debounce.test.ts +42 -0
- package/src/utils/__tests__/error.test.ts +18 -0
- package/src/utils/__tests__/http-error.test.ts +33 -0
- package/src/utils/__tests__/palette.test.ts +22 -0
- package/src/utils/__tests__/password.test.ts +55 -0
- package/src/utils/__tests__/settings-storage.test.ts +15 -0
- package/src/utils/__tests__/validation.test.ts +52 -0
- package/src/utils/api-fetch.ts +61 -0
- package/src/utils/clipboard.ts +69 -0
- package/src/utils/cn.ts +12 -0
- package/src/utils/debounce.ts +33 -0
- package/src/utils/error.ts +10 -0
- package/src/utils/fetch.ts +33 -0
- package/src/utils/http-error.ts +81 -0
- package/src/utils/index.ts +23 -0
- package/src/utils/palette.ts +105 -0
- package/src/utils/password.ts +64 -0
- package/src/utils/settings-storage.ts +68 -0
- package/src/utils/validation.ts +71 -0
- package/styles/globals.css +262 -0
- package/tailwind-preset.js +163 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { type Href } from "expo-router";
|
|
11
|
+
import { useNavigation } from "../context/NavigationContext";
|
|
12
|
+
import { useAppConfig } from "../context/AppConfigContext";
|
|
13
|
+
import AuthService, { type AuthResponse, type User } from "../services/auth.service";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Outcome of a passkey login attempt. Used by apps with a client-side
|
|
17
|
+
* unlock step (e.g. vault's symmetric-key derivation) so the login
|
|
18
|
+
* screen can route to either the home tab or a password-prompt dialog.
|
|
19
|
+
*
|
|
20
|
+
* `unlocked: true` — the authenticator honored PRF and the local
|
|
21
|
+
* unlock work is done. Route to `routes.home`.
|
|
22
|
+
* `unlocked: false` — server accepted the assertion and the session
|
|
23
|
+
* is established, but the consumer still needs
|
|
24
|
+
* the master password. The login screen renders
|
|
25
|
+
* its unlock dialog and calls `unlockWithPassword`.
|
|
26
|
+
*/
|
|
27
|
+
export interface PasskeyLoginResult {
|
|
28
|
+
unlocked: boolean;
|
|
29
|
+
user: User;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface AuthContextType {
|
|
33
|
+
user: User | null;
|
|
34
|
+
isLoading: boolean;
|
|
35
|
+
isAuthenticated: boolean;
|
|
36
|
+
login: (email: string, password: string) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Drive a passkey ceremony end-to-end. Available when the consumer
|
|
39
|
+
* provided `loginWithPasskey` to `AuthProvider`; throws otherwise.
|
|
40
|
+
* `signal` aborts an in-flight WebAuthn ceremony on web — wire it
|
|
41
|
+
* to a `useEffect` cleanup so unmounting the screen tears the OS
|
|
42
|
+
* prompt down instead of leaving an orphaned resolution.
|
|
43
|
+
*/
|
|
44
|
+
loginWithPasskey: (email?: string, signal?: AbortSignal) => Promise<PasskeyLoginResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Complete a non-PRF passkey unlock by deriving the local key from
|
|
47
|
+
* the master password. Available when the consumer provided
|
|
48
|
+
* `unlockWithPassword` to `AuthProvider`.
|
|
49
|
+
*/
|
|
50
|
+
unlockWithPassword: (password: string) => Promise<void>;
|
|
51
|
+
register: (
|
|
52
|
+
email: string,
|
|
53
|
+
username: string | undefined,
|
|
54
|
+
password: string,
|
|
55
|
+
displayName?: string
|
|
56
|
+
) => Promise<void>;
|
|
57
|
+
logout: () => Promise<void>;
|
|
58
|
+
refreshUser: () => Promise<void>;
|
|
59
|
+
deleteAccount: (password: string) => Promise<void>;
|
|
60
|
+
checkAuth: () => Promise<void>;
|
|
61
|
+
error: string | null;
|
|
62
|
+
clearError: () => void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
66
|
+
|
|
67
|
+
export interface AuthProviderProps {
|
|
68
|
+
children: React.ReactNode;
|
|
69
|
+
/**
|
|
70
|
+
* Optional hook called after a successful login. Vault wires its
|
|
71
|
+
* `KeyManager.unlock(password)` here; bootstrap leaves it unset.
|
|
72
|
+
* Throwing rolls the login back via `AuthService.logout()` and bubbles
|
|
73
|
+
* the error to the caller.
|
|
74
|
+
*/
|
|
75
|
+
onAfterLogin?: (password: string, response: AuthResponse) => Promise<void> | void;
|
|
76
|
+
/** Optional post-register hook (mirror of `onAfterLogin`). */
|
|
77
|
+
onAfterRegister?: (password: string, response: AuthResponse) => Promise<void> | void;
|
|
78
|
+
/**
|
|
79
|
+
* Optional pre-logout hook. Vault wires `KeyManager.lock()` and
|
|
80
|
+
* websocket teardown here; failures are swallowed to keep logout
|
|
81
|
+
* idempotent.
|
|
82
|
+
*/
|
|
83
|
+
onBeforeLogout?: () => Promise<void> | void;
|
|
84
|
+
/**
|
|
85
|
+
* Optional pre-checkAuth hook. Vault uses this to fetch the server's
|
|
86
|
+
* HIBP policy alongside the initial session probe.
|
|
87
|
+
*/
|
|
88
|
+
onBeforeCheckAuth?: () => Promise<void> | void;
|
|
89
|
+
/**
|
|
90
|
+
* Optional passkey-ceremony driver. When provided, `useAuth()` exposes
|
|
91
|
+
* `loginWithPasskey`; the consumer owns the WebAuthn round-trip and the
|
|
92
|
+
* post-assertion vault unlock. Returning `{ unlocked: true }` finishes
|
|
93
|
+
* the flow; returning `{ unlocked: false }` keeps the session but tells
|
|
94
|
+
* the screen to collect the master password and call `unlockWithPassword`.
|
|
95
|
+
*/
|
|
96
|
+
loginWithPasskey?: (email?: string, signal?: AbortSignal) => Promise<PasskeyLoginResult>;
|
|
97
|
+
/**
|
|
98
|
+
* Optional master-password unlock. Used by the passkey flow when the
|
|
99
|
+
* authenticator did not honor PRF.
|
|
100
|
+
*/
|
|
101
|
+
unlockWithPassword?: (password: string) => Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Optional telemetry hook. Invoked with every error AuthProvider
|
|
104
|
+
* would otherwise swallow into a `__DEV__`-gated `console.warn` —
|
|
105
|
+
* post-login hook throw, post-register hook throw, refreshUser
|
|
106
|
+
* failure, logout API failure, etc. Wire this to Sentry / Rollbar /
|
|
107
|
+
* your structured-log shipper to get production visibility into
|
|
108
|
+
* paths the UI hides from the user.
|
|
109
|
+
*
|
|
110
|
+
* The `context` string identifies the call site (`"login-hook"`,
|
|
111
|
+
* `"register-hook"`, `"refresh-user"`, `"logout"`, …) so the
|
|
112
|
+
* consumer can route or filter without parsing the error message.
|
|
113
|
+
*/
|
|
114
|
+
onError?: (error: unknown, context: string) => void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function AuthProvider({
|
|
118
|
+
children,
|
|
119
|
+
onAfterLogin,
|
|
120
|
+
onAfterRegister,
|
|
121
|
+
onBeforeLogout,
|
|
122
|
+
onBeforeCheckAuth,
|
|
123
|
+
loginWithPasskey: providedLoginWithPasskey,
|
|
124
|
+
unlockWithPassword: providedUnlockWithPassword,
|
|
125
|
+
onError,
|
|
126
|
+
}: AuthProviderProps) {
|
|
127
|
+
// Stable wrapper so `console.warn`-then-onError stays a single
|
|
128
|
+
// pattern across every silent catch in this provider.
|
|
129
|
+
const reportError = useCallback(
|
|
130
|
+
(error: unknown, context: string) => {
|
|
131
|
+
if (__DEV__) {
|
|
132
|
+
console.warn(`[auth:${context}]`, error);
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
onError?.(error, context);
|
|
136
|
+
} catch {
|
|
137
|
+
// Telemetry must never crash the auth flow.
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
[onError]
|
|
141
|
+
);
|
|
142
|
+
const [user, setUser] = useState<User | null>(null);
|
|
143
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
144
|
+
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
145
|
+
const [error, setError] = useState<string | null>(null);
|
|
146
|
+
const router = useNavigation();
|
|
147
|
+
const { routes } = useAppConfig();
|
|
148
|
+
const authCheckRef = useRef(false);
|
|
149
|
+
const sessionGenRef = useRef(0);
|
|
150
|
+
|
|
151
|
+
const clearError = useCallback(() => {
|
|
152
|
+
setError(null);
|
|
153
|
+
}, []);
|
|
154
|
+
|
|
155
|
+
const routeAfterAuth = useCallback(
|
|
156
|
+
(response: AuthResponse, email: string) => {
|
|
157
|
+
const step = response.user.onboarding_step;
|
|
158
|
+
if (step === "email_verification") {
|
|
159
|
+
router.replace({ pathname: routes.verifyEmail, params: { email } } as Href);
|
|
160
|
+
} else if (step === "mfa_enrollment") {
|
|
161
|
+
router.replace(routes.mfaEnroll);
|
|
162
|
+
} else if (step === "import") {
|
|
163
|
+
router.replace(routes.importData);
|
|
164
|
+
} else if (step === "complete" || step == null) {
|
|
165
|
+
router.replace(routes.home);
|
|
166
|
+
} else {
|
|
167
|
+
// Unknown step (server added a new onboarding step the
|
|
168
|
+
// client doesn't recognize). Route home — the consumer's
|
|
169
|
+
// route guards will catch the still-incomplete state and
|
|
170
|
+
// either prompt an app update or re-route to welcome.
|
|
171
|
+
// Don't strand the user in the auth flow.
|
|
172
|
+
router.replace(routes.home);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
[router, routes]
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const checkAuth = useCallback(async () => {
|
|
179
|
+
if (authCheckRef.current) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
authCheckRef.current = true;
|
|
183
|
+
const gen = ++sessionGenRef.current;
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
setIsLoading(true);
|
|
187
|
+
const isAuth = await AuthService.isAuthenticated();
|
|
188
|
+
if (gen !== sessionGenRef.current) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (isAuth) {
|
|
193
|
+
const userData = await AuthService.getCurrentUser();
|
|
194
|
+
if (gen !== sessionGenRef.current) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (userData) {
|
|
198
|
+
// Unblock navigation immediately with cached data.
|
|
199
|
+
setUser(userData);
|
|
200
|
+
setIsAuthenticated(true);
|
|
201
|
+
setIsLoading(false);
|
|
202
|
+
authCheckRef.current = false;
|
|
203
|
+
|
|
204
|
+
// Refresh from the server in the background.
|
|
205
|
+
AuthService.refreshUserData()
|
|
206
|
+
.then((fresh) => {
|
|
207
|
+
if (fresh && gen === sessionGenRef.current) {
|
|
208
|
+
setUser(fresh);
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
.catch((e) => reportError(e, "background-refresh"));
|
|
212
|
+
return;
|
|
213
|
+
} else {
|
|
214
|
+
await AuthService.logout();
|
|
215
|
+
if (gen !== sessionGenRef.current) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
setUser(null);
|
|
219
|
+
setIsAuthenticated(false);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
setUser(null);
|
|
223
|
+
setIsAuthenticated(false);
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {
|
|
226
|
+
// Distinguish a transport failure (server unreachable on cold
|
|
227
|
+
// start) from a real auth failure. Falling through to
|
|
228
|
+
// "logged out" on transient network errors paints the welcome
|
|
229
|
+
// screen unjustly to a user whose cookie is still valid, then
|
|
230
|
+
// flips back the moment a request lands. Keep any persisted
|
|
231
|
+
// user record while we wait for the next probe.
|
|
232
|
+
if (gen !== sessionGenRef.current) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const persisted = await AuthService.getCurrentUser().catch(() => null);
|
|
236
|
+
if (gen !== sessionGenRef.current) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (persisted) {
|
|
240
|
+
setUser(persisted);
|
|
241
|
+
setIsAuthenticated(true);
|
|
242
|
+
reportError(e instanceof Error ? e : new Error(String(e)), "check-auth-transient");
|
|
243
|
+
} else {
|
|
244
|
+
setUser(null);
|
|
245
|
+
setIsAuthenticated(false);
|
|
246
|
+
}
|
|
247
|
+
} finally {
|
|
248
|
+
if (gen === sessionGenRef.current) {
|
|
249
|
+
setIsLoading(false);
|
|
250
|
+
}
|
|
251
|
+
authCheckRef.current = false;
|
|
252
|
+
}
|
|
253
|
+
}, [reportError]);
|
|
254
|
+
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
AuthService.loadServerUrlOverride()
|
|
257
|
+
.then(() => (onBeforeCheckAuth ? onBeforeCheckAuth() : undefined))
|
|
258
|
+
.catch((e) => reportError(e, "pre-check-auth"))
|
|
259
|
+
.then(() => checkAuth());
|
|
260
|
+
}, [checkAuth, onBeforeCheckAuth, reportError]);
|
|
261
|
+
|
|
262
|
+
const login = useCallback(
|
|
263
|
+
async (email: string, password: string) => {
|
|
264
|
+
try {
|
|
265
|
+
setIsLoading(true);
|
|
266
|
+
setError(null);
|
|
267
|
+
|
|
268
|
+
const response = await AuthService.login(email, password);
|
|
269
|
+
|
|
270
|
+
if (onAfterLogin) {
|
|
271
|
+
try {
|
|
272
|
+
await onAfterLogin(password, response);
|
|
273
|
+
} catch (hookErr) {
|
|
274
|
+
await AuthService.logout();
|
|
275
|
+
const message = hookErr instanceof Error ? hookErr.message : "Post-login hook failed";
|
|
276
|
+
// Deliberately do NOT attach `cause: hookErr` — the inner
|
|
277
|
+
// error from the consumer's post-login hook may carry
|
|
278
|
+
// key-derivation context (raw masterPassword references,
|
|
279
|
+
// server payloads) that would be exposed if downstream
|
|
280
|
+
// logging surfaces `err.cause` (Sentry, devtools, …).
|
|
281
|
+
|
|
282
|
+
throw new Error(message);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
setUser(response.user);
|
|
287
|
+
setIsAuthenticated(true);
|
|
288
|
+
routeAfterAuth(response, email);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
const message = err instanceof Error ? err.message : "Login failed";
|
|
291
|
+
setError(message);
|
|
292
|
+
throw err;
|
|
293
|
+
} finally {
|
|
294
|
+
setIsLoading(false);
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
[onAfterLogin, routeAfterAuth]
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const register = useCallback(
|
|
301
|
+
async (email: string, username: string | undefined, password: string, displayName?: string) => {
|
|
302
|
+
try {
|
|
303
|
+
setIsLoading(true);
|
|
304
|
+
setError(null);
|
|
305
|
+
|
|
306
|
+
const response = await AuthService.register(email, username, password, displayName);
|
|
307
|
+
|
|
308
|
+
if (onAfterRegister) {
|
|
309
|
+
try {
|
|
310
|
+
await onAfterRegister(password, response);
|
|
311
|
+
} catch (hookErr) {
|
|
312
|
+
await AuthService.logout();
|
|
313
|
+
const message =
|
|
314
|
+
hookErr instanceof Error ? hookErr.message : "Post-register hook failed";
|
|
315
|
+
// No `cause` chain — see login() above for rationale.
|
|
316
|
+
|
|
317
|
+
throw new Error(message);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
setUser(response.user);
|
|
322
|
+
setIsAuthenticated(true);
|
|
323
|
+
routeAfterAuth(response, email);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
const message = err instanceof Error ? err.message : "Registration failed";
|
|
326
|
+
setError(message);
|
|
327
|
+
throw err;
|
|
328
|
+
} finally {
|
|
329
|
+
setIsLoading(false);
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
[onAfterRegister, routeAfterAuth]
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
const logout = useCallback(async () => {
|
|
336
|
+
sessionGenRef.current++;
|
|
337
|
+
setIsLoading(true);
|
|
338
|
+
// Flip React auth state FIRST so any in-flight queries that
|
|
339
|
+
// re-render against `user` / `isAuthenticated` see "logged out"
|
|
340
|
+
// immediately. Previously this happened after `AuthService.logout()`
|
|
341
|
+
// resolved, opening a window where the pre-logout hook had already
|
|
342
|
+
// torn down KeyManager / queryClient / WS but components still
|
|
343
|
+
// rendered as authenticated — any React-Query refetch in that
|
|
344
|
+
// window would hit a half-disassembled client.
|
|
345
|
+
setUser(null);
|
|
346
|
+
setIsAuthenticated(false);
|
|
347
|
+
if (onBeforeLogout) {
|
|
348
|
+
try {
|
|
349
|
+
await onBeforeLogout();
|
|
350
|
+
} catch (e) {
|
|
351
|
+
// pre-logout hook errors are non-fatal — proceed to clear
|
|
352
|
+
// state. Telemetry sees it via the onError surface.
|
|
353
|
+
reportError(e, "pre-logout-hook");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
router.replace(routes.login);
|
|
357
|
+
// Server-side revoke runs in the background. Failure is also
|
|
358
|
+
// surfaced through the onError hook so consumers can detect
|
|
359
|
+
// half-broken logouts (server session still alive after the
|
|
360
|
+
// user thinks they're gone).
|
|
361
|
+
try {
|
|
362
|
+
await AuthService.logout();
|
|
363
|
+
} catch (e) {
|
|
364
|
+
reportError(e, "logout-revoke");
|
|
365
|
+
}
|
|
366
|
+
setIsLoading(false);
|
|
367
|
+
}, [onBeforeLogout, router, routes, reportError]);
|
|
368
|
+
|
|
369
|
+
const refreshUser = useCallback(async () => {
|
|
370
|
+
try {
|
|
371
|
+
const userData = await AuthService.refreshUserData();
|
|
372
|
+
if (userData) {
|
|
373
|
+
setUser(userData);
|
|
374
|
+
setIsAuthenticated(true);
|
|
375
|
+
} else {
|
|
376
|
+
setUser(null);
|
|
377
|
+
setIsAuthenticated(false);
|
|
378
|
+
router.replace(routes.login);
|
|
379
|
+
}
|
|
380
|
+
} catch {
|
|
381
|
+
// Refresh failed — try again on next interval.
|
|
382
|
+
}
|
|
383
|
+
}, [router, routes]);
|
|
384
|
+
|
|
385
|
+
const deleteAccount = useCallback(
|
|
386
|
+
async (password: string) => {
|
|
387
|
+
sessionGenRef.current++;
|
|
388
|
+
try {
|
|
389
|
+
setIsLoading(true);
|
|
390
|
+
await AuthService.deleteAccount(password);
|
|
391
|
+
if (onBeforeLogout) {
|
|
392
|
+
try {
|
|
393
|
+
await onBeforeLogout();
|
|
394
|
+
} catch {
|
|
395
|
+
// ignore
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
setUser(null);
|
|
399
|
+
setIsAuthenticated(false);
|
|
400
|
+
router.replace(routes.welcome);
|
|
401
|
+
} catch (err) {
|
|
402
|
+
const message = err instanceof Error ? err.message : "Failed to delete account";
|
|
403
|
+
setError(message);
|
|
404
|
+
throw err;
|
|
405
|
+
} finally {
|
|
406
|
+
setIsLoading(false);
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
[onBeforeLogout, router, routes]
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
const loginWithPasskey = useCallback(
|
|
413
|
+
async (email?: string, signal?: AbortSignal): Promise<PasskeyLoginResult> => {
|
|
414
|
+
if (!providedLoginWithPasskey) {
|
|
415
|
+
throw new Error(
|
|
416
|
+
"entropy-ui: loginWithPasskey() called but AuthProvider wasn't given a loginWithPasskey handler."
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
setIsLoading(true);
|
|
421
|
+
setError(null);
|
|
422
|
+
const result = await providedLoginWithPasskey(email, signal);
|
|
423
|
+
setUser(result.user);
|
|
424
|
+
setIsAuthenticated(true);
|
|
425
|
+
return result;
|
|
426
|
+
} catch (err) {
|
|
427
|
+
const message = err instanceof Error ? err.message : "Passkey sign-in failed";
|
|
428
|
+
setError(message);
|
|
429
|
+
throw err;
|
|
430
|
+
} finally {
|
|
431
|
+
setIsLoading(false);
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
[providedLoginWithPasskey]
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const unlockWithPassword = useCallback(
|
|
438
|
+
async (password: string): Promise<void> => {
|
|
439
|
+
if (!providedUnlockWithPassword) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
"entropy-ui: unlockWithPassword() called but AuthProvider wasn't given an unlockWithPassword handler."
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
await providedUnlockWithPassword(password);
|
|
445
|
+
},
|
|
446
|
+
[providedUnlockWithPassword]
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
const value = useMemo<AuthContextType>(
|
|
450
|
+
() => ({
|
|
451
|
+
user,
|
|
452
|
+
isLoading,
|
|
453
|
+
isAuthenticated,
|
|
454
|
+
login,
|
|
455
|
+
loginWithPasskey,
|
|
456
|
+
unlockWithPassword,
|
|
457
|
+
register,
|
|
458
|
+
logout,
|
|
459
|
+
refreshUser,
|
|
460
|
+
deleteAccount,
|
|
461
|
+
checkAuth,
|
|
462
|
+
error,
|
|
463
|
+
clearError,
|
|
464
|
+
}),
|
|
465
|
+
[
|
|
466
|
+
user,
|
|
467
|
+
isLoading,
|
|
468
|
+
isAuthenticated,
|
|
469
|
+
login,
|
|
470
|
+
loginWithPasskey,
|
|
471
|
+
unlockWithPassword,
|
|
472
|
+
register,
|
|
473
|
+
logout,
|
|
474
|
+
refreshUser,
|
|
475
|
+
deleteAccount,
|
|
476
|
+
checkAuth,
|
|
477
|
+
error,
|
|
478
|
+
clearError,
|
|
479
|
+
]
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function useAuth(): AuthContextType {
|
|
486
|
+
const context = useContext(AuthContext);
|
|
487
|
+
if (context === undefined) {
|
|
488
|
+
throw new Error("useAuth must be used within an AuthProvider");
|
|
489
|
+
}
|
|
490
|
+
return context;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export type { AuthContextType };
|
|
494
|
+
|
|
495
|
+
export function useRole(requiredRole: "Admin" | "User"): boolean {
|
|
496
|
+
const { user } = useAuth();
|
|
497
|
+
if (!user) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
if (requiredRole === "User") {
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
return user.role === "Admin";
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function useRequireAuth(redirectTo?: Href) {
|
|
507
|
+
const { isAuthenticated, isLoading } = useAuth();
|
|
508
|
+
const router = useNavigation();
|
|
509
|
+
const { routes } = useAppConfig();
|
|
510
|
+
const target = redirectTo ?? routes.login;
|
|
511
|
+
|
|
512
|
+
useEffect(() => {
|
|
513
|
+
if (!isLoading && !isAuthenticated) {
|
|
514
|
+
router.replace(target);
|
|
515
|
+
}
|
|
516
|
+
}, [isAuthenticated, isLoading, router, target]);
|
|
517
|
+
|
|
518
|
+
return { isAuthenticated, isLoading };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export function useSession() {
|
|
522
|
+
const { user, refreshUser, isAuthenticated } = useAuth();
|
|
523
|
+
const intervalRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);
|
|
524
|
+
const refreshingRef = useRef(false);
|
|
525
|
+
|
|
526
|
+
useEffect(() => {
|
|
527
|
+
if (isAuthenticated) {
|
|
528
|
+
intervalRef.current = setInterval(
|
|
529
|
+
() => {
|
|
530
|
+
if (refreshingRef.current) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
refreshingRef.current = true;
|
|
534
|
+
refreshUser()
|
|
535
|
+
.catch((e) => {
|
|
536
|
+
// `useSession` lives outside the provider's
|
|
537
|
+
// reportError closure; auto-refresh failures only
|
|
538
|
+
// surface in dev. The `refreshUser()` impl itself
|
|
539
|
+
// reports via onError when wired.
|
|
540
|
+
if (__DEV__) {
|
|
541
|
+
console.warn("[auth:session-refresh-interval]", e);
|
|
542
|
+
}
|
|
543
|
+
})
|
|
544
|
+
.finally(() => {
|
|
545
|
+
refreshingRef.current = false;
|
|
546
|
+
});
|
|
547
|
+
},
|
|
548
|
+
10 * 60 * 1000
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return () => {
|
|
553
|
+
if (intervalRef.current) {
|
|
554
|
+
clearInterval(intervalRef.current);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}, [isAuthenticated, refreshUser]);
|
|
558
|
+
|
|
559
|
+
return { user };
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Validators live in `utils/validation` and are re-exported via the
|
|
563
|
+
// utils barrel. Existing consumers that imported them from this module
|
|
564
|
+
// pull them from the package root barrel instead.
|
|
565
|
+
export { validateEmail, validatePassword, validateUsername } from "../utils/validation";
|
|
566
|
+
|
|
567
|
+
export default useAuth;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { AppState as RNAppState, Platform } from "react-native";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_IDLE_LOCK_MS = 10 * 60 * 1000;
|
|
5
|
+
|
|
6
|
+
export interface UseAutoLockOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Reports whether the session is currently "unlocked" (vault-style
|
|
9
|
+
* apps) or any other state where the auto-lock should still fire.
|
|
10
|
+
* Returning `false` short-circuits the lock call.
|
|
11
|
+
*/
|
|
12
|
+
isUnlocked: () => boolean;
|
|
13
|
+
/** Called when the lock should fire. App owns side-effects. */
|
|
14
|
+
onLock: () => void;
|
|
15
|
+
/** Reports whether auto-lock is enabled for this user (settings flag). */
|
|
16
|
+
isEnabled?: () => boolean | Promise<boolean>;
|
|
17
|
+
/** Idle threshold in milliseconds. Defaults to 10 minutes. */
|
|
18
|
+
idleLockMs?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Auto-lock a session after a period of inactivity. Generic over the
|
|
23
|
+
* lock predicate — vault wires `KeyManager.isUnlocked` / `KeyManager.lock`,
|
|
24
|
+
* other apps wire their own state. Web uses an idle-timer only; native
|
|
25
|
+
* additionally locks immediately when the app goes to the background.
|
|
26
|
+
*/
|
|
27
|
+
export function useAutoLock(options: UseAutoLockOptions): void {
|
|
28
|
+
const { isUnlocked, onLock, isEnabled, idleLockMs = DEFAULT_IDLE_LOCK_MS } = options;
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
let idleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
32
|
+
let lastActivityAt = Date.now();
|
|
33
|
+
|
|
34
|
+
const lockIfEnabled = () => {
|
|
35
|
+
if (idleTimer !== null) {
|
|
36
|
+
clearTimeout(idleTimer);
|
|
37
|
+
idleTimer = null;
|
|
38
|
+
}
|
|
39
|
+
if (!isUnlocked()) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
Promise.resolve(isEnabled ? isEnabled() : true)
|
|
43
|
+
.then((enabled) => {
|
|
44
|
+
if (enabled && isUnlocked()) {
|
|
45
|
+
onLock();
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
.catch(() => {});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const armIdleTimer = () => {
|
|
52
|
+
if (idleTimer !== null) {
|
|
53
|
+
clearTimeout(idleTimer);
|
|
54
|
+
}
|
|
55
|
+
const remaining = idleLockMs - (Date.now() - lastActivityAt);
|
|
56
|
+
if (remaining <= 0) {
|
|
57
|
+
lockIfEnabled();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
idleTimer = setTimeout(lockIfEnabled, remaining);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const onActivity = () => {
|
|
64
|
+
lastActivityAt = Date.now();
|
|
65
|
+
armIdleTimer();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// On web, RN Web's AppState mirrors document visibility — tab-hidden
|
|
69
|
+
// becomes 'background', which would otherwise fire an immediate lock.
|
|
70
|
+
// Only treat 'background' as a lock signal on real native platforms.
|
|
71
|
+
const subscription = RNAppState.addEventListener("change", (nextState) => {
|
|
72
|
+
if (nextState === "background" && Platform.OS !== "web") {
|
|
73
|
+
lockIfEnabled();
|
|
74
|
+
} else if (nextState === "active") {
|
|
75
|
+
armIdleTimer();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const cleanups: (() => void)[] = [];
|
|
80
|
+
|
|
81
|
+
if (Platform.OS === "web" && typeof document !== "undefined") {
|
|
82
|
+
const onVisibility = () => {
|
|
83
|
+
if (document.visibilityState === "visible") {
|
|
84
|
+
armIdleTimer();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
88
|
+
cleanups.push(() => document.removeEventListener("visibilitychange", onVisibility));
|
|
89
|
+
|
|
90
|
+
const activityEvents = ["mousemove", "mousedown", "keydown", "touchstart", "scroll"] as const;
|
|
91
|
+
for (const e of activityEvents) {
|
|
92
|
+
window.addEventListener(e, onActivity, { passive: true });
|
|
93
|
+
cleanups.push(() => window.removeEventListener(e, onActivity));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
armIdleTimer();
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
subscription.remove();
|
|
101
|
+
for (const fn of cleanups) {
|
|
102
|
+
fn();
|
|
103
|
+
}
|
|
104
|
+
if (idleTimer !== null) {
|
|
105
|
+
clearTimeout(idleTimer);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
109
|
+
}, []);
|
|
110
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { debounce } from "../utils/debounce";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns a debounced copy of `value` that only updates after `value` has
|
|
7
|
+
* stopped changing for `delayMs`. Use it to keep a controlled input
|
|
8
|
+
* responsive (bind the input to the raw value) while throttling the
|
|
9
|
+
* expensive work it drives — a network query, a react-query cache key, a
|
|
10
|
+
* filter over a large list.
|
|
11
|
+
*
|
|
12
|
+
* The pending update is cancelled on unmount and whenever `value` changes
|
|
13
|
+
* again, so a fast typer never fires an update per keystroke and there is no
|
|
14
|
+
* setState-after-unmount on the trailing edge.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const [term, setTerm] = useState("");
|
|
18
|
+
* const debouncedTerm = useDebouncedValue(term, 300);
|
|
19
|
+
* // <Input value={term} onChangeText={setTerm} /> — instant feedback
|
|
20
|
+
* // useQuery({ queryKey: ["search", debouncedTerm], ... }) — one fetch per pause
|
|
21
|
+
*/
|
|
22
|
+
export function useDebouncedValue<T>(value: T, delayMs = 300): T {
|
|
23
|
+
const [debounced, setDebounced] = useState(value);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const update = debounce((next: T) => setDebounced(next), delayMs);
|
|
27
|
+
update(value);
|
|
28
|
+
return update.cancel;
|
|
29
|
+
}, [value, delayMs]);
|
|
30
|
+
|
|
31
|
+
return debounced;
|
|
32
|
+
}
|