| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| 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,
|
| };
|
| };
|
|
|