new-api / web /src /hooks /common /useSecureVerification.jsx
liuzhao521
Deploy New API v0.9.25+ (commit b47cf4ef) to HuggingFace Spaces
4674012
/*
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,
};
};