| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- /*
- 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';
- import { isVerificationRequiredError } from '../../helpers/secureApiCall';
- /**
- * 通用安全验证 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 {
- // 先调用验证 API,成功后后端会设置 session
- await SecureVerificationService.verify(method, code);
- // 验证成功,调用业务 API(此时中间件会通过)
- 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]);
- /**
- * 包装 API 调用,自动处理验证错误
- * 当 API 返回需要验证的错误时,自动弹出验证模态框
- * @param {Function} apiCall - API 调用函数
- * @param {Object} options - 验证选项(同 startVerification)
- * @returns {Promise<any>}
- */
- const withVerification = useCallback(async (apiCall, options = {}) => {
- try {
- // 直接尝试调用 API
- 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
- };
- };
|