| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { useState, useEffect, useCallback } from 'react'; |
| import { useTranslation } from 'react-i18next'; |
| import { SecureVerificationService } from '../../services/secureVerification'; |
| import { showError, showSuccess } from '../../helpers'; |
| import { isVerificationRequiredError } from '../../helpers/secureApiCall'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export const useSecureVerification = ({ |
| onSuccess, |
| onError, |
| successMessage, |
| autoReset = true, |
| } = {}) => { |
| const { t } = useTranslation(); |
|
|
| |
| const [verificationMethods, setVerificationMethods] = useState({ |
| has2FA: false, |
| hasPasskey: false, |
| passkeySupported: false, |
| }); |
|
|
| |
| const [isModalVisible, setIsModalVisible] = useState(false); |
|
|
| |
| const [verificationState, setVerificationState] = useState({ |
| method: null, |
| loading: false, |
| code: '', |
| apiCall: null, |
| }); |
|
|
| |
| const checkVerificationMethods = useCallback(async () => { |
| const methods = |
| await SecureVerificationService.checkAvailableVerificationMethods(); |
| setVerificationMethods(methods); |
| return methods; |
| }, []); |
|
|
| |
| useEffect(() => { |
| checkVerificationMethods(); |
| }, [checkVerificationMethods]); |
|
|
| |
| const resetState = useCallback(() => { |
| setVerificationState({ |
| method: null, |
| loading: false, |
| code: '', |
| apiCall: null, |
| }); |
| setIsModalVisible(false); |
| }, []); |
|
|
| |
| const startVerification = useCallback( |
| async (apiCall, options = {}) => { |
| const { preferredMethod, title, description } = options; |
|
|
| |
| const methods = await checkVerificationMethods(); |
|
|
| if (!methods.has2FA && !methods.hasPasskey) { |
| const errorMessage = t('您需要先启用两步验证或 Passkey 才能执行此操作'); |
| showError(errorMessage); |
| onError?.(new Error(errorMessage)); |
| return false; |
| } |
|
|
| |
| let defaultMethod = preferredMethod; |
| if (!defaultMethod) { |
| if (methods.hasPasskey && methods.passkeySupported) { |
| defaultMethod = 'passkey'; |
| } else if (methods.has2FA) { |
| defaultMethod = '2fa'; |
| } |
| } |
|
|
| setVerificationState((prev) => ({ |
| ...prev, |
| method: defaultMethod, |
| apiCall, |
| title, |
| description, |
| })); |
| setIsModalVisible(true); |
|
|
| return true; |
| }, |
| [checkVerificationMethods, onError, t], |
| ); |
|
|
| |
| const executeVerification = useCallback( |
| async (method, code = '') => { |
| if (!verificationState.apiCall) { |
| showError(t('验证配置错误')); |
| return; |
| } |
|
|
| setVerificationState((prev) => ({ ...prev, loading: true })); |
|
|
| try { |
| |
| await SecureVerificationService.verify(method, code); |
|
|
| |
| const result = await verificationState.apiCall(); |
|
|
| |
| if (successMessage) { |
| showSuccess(successMessage); |
| } |
|
|
| |
| onSuccess?.(result, method); |
|
|
| |
| if (autoReset) { |
| resetState(); |
| } |
|
|
| return result; |
| } catch (error) { |
| showError(error.message || t('验证失败,请重试')); |
| onError?.(error); |
| throw error; |
| } finally { |
| setVerificationState((prev) => ({ ...prev, loading: false })); |
| } |
| }, |
| [ |
| verificationState.apiCall, |
| successMessage, |
| onSuccess, |
| onError, |
| autoReset, |
| resetState, |
| t, |
| ], |
| ); |
|
|
| |
| const setVerificationCode = useCallback((code) => { |
| setVerificationState((prev) => ({ ...prev, code })); |
| }, []); |
|
|
| |
| const switchVerificationMethod = useCallback((method) => { |
| setVerificationState((prev) => ({ ...prev, method, code: '' })); |
| }, []); |
|
|
| |
| const cancelVerification = useCallback(() => { |
| resetState(); |
| }, [resetState]); |
|
|
| |
| const canUseMethod = useCallback( |
| (method) => { |
| switch (method) { |
| case '2fa': |
| return verificationMethods.has2FA; |
| case 'passkey': |
| return ( |
| verificationMethods.hasPasskey && |
| verificationMethods.passkeySupported |
| ); |
| default: |
| return false; |
| } |
| }, |
| [verificationMethods], |
| ); |
|
|
| |
| const getRecommendedMethod = useCallback(() => { |
| if ( |
| verificationMethods.hasPasskey && |
| verificationMethods.passkeySupported |
| ) { |
| return 'passkey'; |
| } |
| if (verificationMethods.has2FA) { |
| return '2fa'; |
| } |
| return null; |
| }, [verificationMethods]); |
|
|
| |
| |
| |
| |
| |
| |
| |
| const withVerification = useCallback( |
| async (apiCall, options = {}) => { |
| try { |
| |
| return await apiCall(); |
| } catch (error) { |
| |
| if (isVerificationRequiredError(error)) { |
| |
| await startVerification(apiCall, options); |
| |
| return null; |
| } |
| |
| throw error; |
| } |
| }, |
| [startVerification], |
| ); |
|
|
| return { |
| |
| isModalVisible, |
| verificationMethods, |
| verificationState, |
|
|
| |
| startVerification, |
| executeVerification, |
| cancelVerification, |
| resetState, |
| setVerificationCode, |
| switchVerificationMethod, |
| checkVerificationMethods, |
|
|
| |
| canUseMethod, |
| getRecommendedMethod, |
| withVerification, |
|
|
| |
| hasAnyVerificationMethod: |
| verificationMethods.has2FA || verificationMethods.hasPasskey, |
| isLoading: verificationState.loading, |
| currentMethod: verificationState.method, |
| code: verificationState.code, |
| }; |
| }; |
|
|