219 lines
6.0 KiB
JavaScript
219 lines
6.0 KiB
JavaScript
/*
|
|
Copyright (C) 2025 QuantumNous
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
For commercial licensing, please contact support@quantumnous.com
|
|
*/
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { SecureVerificationService } from '../../services/secureVerification';
|
|
import { showError, showSuccess } from '../../helpers';
|
|
|
|
/**
|
|
* 通用安全验证 Hook
|
|
* @param {Object} options - 配置选项
|
|
* @param {Function} options.onSuccess - 验证成功回调
|
|
* @param {Function} options.onError - 验证失败回调
|
|
* @param {string} options.successMessage - 成功提示消息
|
|
* @param {boolean} options.autoReset - 验证完成后是否自动重置状态,默认为 true
|
|
*/
|
|
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, // '2fa' | 'passkey'
|
|
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 {
|
|
const result = await SecureVerificationService.verify(method, {
|
|
code,
|
|
apiCall: 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]);
|
|
|
|
return {
|
|
// 状态
|
|
isModalVisible,
|
|
verificationMethods,
|
|
verificationState,
|
|
|
|
// 方法
|
|
startVerification,
|
|
executeVerification,
|
|
cancelVerification,
|
|
resetState,
|
|
setVerificationCode,
|
|
switchVerificationMethod,
|
|
checkVerificationMethods,
|
|
|
|
// 辅助方法
|
|
canUseMethod,
|
|
getRecommendedMethod,
|
|
|
|
// 便捷属性
|
|
hasAnyVerificationMethod: verificationMethods.has2FA || verificationMethods.hasPasskey,
|
|
isLoading: verificationState.loading,
|
|
currentMethod: verificationState.method,
|
|
code: verificationState.code
|
|
};
|
|
}; |