Spaces:
Build error
Build error
| /* | |
| 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 { API, showError } from '../helpers'; | |
| import { | |
| prepareCredentialRequestOptions, | |
| buildAssertionResult, | |
| isPasskeySupported | |
| } from '../helpers/passkey'; | |
| /** | |
| * 通用安全验证服务 | |
| * 验证状态完全由后端 Session 控制,前端不存储任何状态 | |
| */ | |
| export class SecureVerificationService { | |
| /** | |
| * 检查用户可用的验证方式 | |
| * @returns {Promise<{has2FA: boolean, hasPasskey: boolean, passkeySupported: boolean}>} | |
| */ | |
| static async checkAvailableVerificationMethods() { | |
| try { | |
| const [twoFAResponse, passkeyResponse, passkeySupported] = await Promise.all([ | |
| API.get('/api/user/2fa/status'), | |
| API.get('/api/user/passkey'), | |
| isPasskeySupported() | |
| ]); | |
| console.log('=== DEBUGGING VERIFICATION METHODS ==='); | |
| console.log('2FA Response:', JSON.stringify(twoFAResponse, null, 2)); | |
| console.log('Passkey Response:', JSON.stringify(passkeyResponse, null, 2)); | |
| const has2FA = twoFAResponse.data?.success && twoFAResponse.data?.data?.enabled === true; | |
| const hasPasskey = passkeyResponse.data?.success && passkeyResponse.data?.data?.enabled === true; | |
| console.log('has2FA calculation:', { | |
| success: twoFAResponse.data?.success, | |
| dataExists: !!twoFAResponse.data?.data, | |
| enabled: twoFAResponse.data?.data?.enabled, | |
| result: has2FA | |
| }); | |
| console.log('hasPasskey calculation:', { | |
| success: passkeyResponse.data?.success, | |
| dataExists: !!passkeyResponse.data?.data, | |
| enabled: passkeyResponse.data?.data?.enabled, | |
| result: hasPasskey | |
| }); | |
| const result = { | |
| has2FA, | |
| hasPasskey, | |
| passkeySupported | |
| }; | |
| return result; | |
| } catch (error) { | |
| console.error('Failed to check verification methods:', error); | |
| return { | |
| has2FA: false, | |
| hasPasskey: false, | |
| passkeySupported: false | |
| }; | |
| } | |
| } | |
| /** | |
| * 执行2FA验证 | |
| * @param {string} code - 验证码 | |
| * @returns {Promise<void>} | |
| */ | |
| static async verify2FA(code) { | |
| if (!code?.trim()) { | |
| throw new Error('请输入验证码或备用码'); | |
| } | |
| // 调用通用验证 API,验证成功后后端会设置 session | |
| const verifyResponse = await API.post('/api/verify', { | |
| method: '2fa', | |
| code: code.trim() | |
| }); | |
| if (!verifyResponse.data?.success) { | |
| throw new Error(verifyResponse.data?.message || '验证失败'); | |
| } | |
| // 验证成功,session 已在后端设置 | |
| } | |
| /** | |
| * 执行Passkey验证 | |
| * @returns {Promise<void>} | |
| */ | |
| static async verifyPasskey() { | |
| try { | |
| // 开始Passkey验证 | |
| const beginResponse = await API.post('/api/user/passkey/verify/begin'); | |
| if (!beginResponse.data?.success) { | |
| throw new Error(beginResponse.data?.message || '开始验证失败'); | |
| } | |
| // 准备WebAuthn选项 | |
| const publicKey = prepareCredentialRequestOptions(beginResponse.data.data.options); | |
| // 执行WebAuthn验证 | |
| const credential = await navigator.credentials.get({ publicKey }); | |
| if (!credential) { | |
| throw new Error('Passkey 验证被取消'); | |
| } | |
| // 构建验证结果 | |
| const assertionResult = buildAssertionResult(credential); | |
| // 完成验证 | |
| const finishResponse = await API.post('/api/user/passkey/verify/finish', assertionResult); | |
| if (!finishResponse.data?.success) { | |
| throw new Error(finishResponse.data?.message || '验证失败'); | |
| } | |
| // 调用通用验证 API 设置 session(Passkey 验证已完成) | |
| const verifyResponse = await API.post('/api/verify', { | |
| method: 'passkey' | |
| }); | |
| if (!verifyResponse.data?.success) { | |
| throw new Error(verifyResponse.data?.message || '验证失败'); | |
| } | |
| // 验证成功,session 已在后端设置 | |
| } catch (error) { | |
| if (error.name === 'NotAllowedError') { | |
| throw new Error('Passkey 验证被取消或超时'); | |
| } else if (error.name === 'InvalidStateError') { | |
| throw new Error('Passkey 验证状态无效'); | |
| } else { | |
| throw error; | |
| } | |
| } | |
| } | |
| /** | |
| * 通用验证方法,根据验证类型执行相应的验证流程 | |
| * @param {string} method - 验证方式: '2fa' | 'passkey' | |
| * @param {string} code - 2FA验证码(当method为'2fa'时必需) | |
| * @returns {Promise<void>} | |
| */ | |
| static async verify(method, code = '') { | |
| switch (method) { | |
| case '2fa': | |
| return await this.verify2FA(code); | |
| case 'passkey': | |
| return await this.verifyPasskey(); | |
| default: | |
| throw new Error(`不支持的验证方式: ${method}`); | |
| } | |
| } | |
| } | |
| /** | |
| * 预设的API调用函数工厂 | |
| */ | |
| export const createApiCalls = { | |
| /** | |
| * 创建查看渠道密钥的API调用 | |
| * @param {number} channelId - 渠道ID | |
| */ | |
| viewChannelKey: (channelId) => async () => { | |
| // 新系统中,验证已通过中间件处理,直接调用 API 即可 | |
| const response = await API.post(`/api/channel/${channelId}/key`, {}); | |
| return response.data; | |
| }, | |
| /** | |
| * 创建自定义API调用 | |
| * @param {string} url - API URL | |
| * @param {string} method - HTTP方法,默认为 'POST' | |
| * @param {Object} extraData - 额外的请求数据 | |
| */ | |
| custom: (url, method = 'POST', extraData = {}) => async () => { | |
| // 新系统中,验证已通过中间件处理 | |
| const data = extraData; | |
| let response; | |
| switch (method.toUpperCase()) { | |
| case 'GET': | |
| response = await API.get(url, { params: data }); | |
| break; | |
| case 'POST': | |
| response = await API.post(url, data); | |
| break; | |
| case 'PUT': | |
| response = await API.put(url, data); | |
| break; | |
| case 'DELETE': | |
| response = await API.delete(url, { data }); | |
| break; | |
| default: | |
| throw new Error(`不支持的HTTP方法: ${method}`); | |
| } | |
| return response.data; | |
| } | |
| }; |