secureVerification.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import { API, showError } from '../helpers';
  16. import {
  17. prepareCredentialRequestOptions,
  18. buildAssertionResult,
  19. isPasskeySupported
  20. } from '../helpers/passkey';
  21. /**
  22. * 通用安全验证服务
  23. */
  24. export class SecureVerificationService {
  25. /**
  26. * 检查用户可用的验证方式
  27. * @returns {Promise<{has2FA: boolean, hasPasskey: boolean, passkeySupported: boolean}>}
  28. */
  29. static async checkAvailableVerificationMethods() {
  30. try {
  31. console.log('Checking user verification methods...');
  32. const [twoFAResponse, passkeyResponse, passkeySupported] = await Promise.all([
  33. API.get('/api/user/2fa/status'),
  34. API.get('/api/user/passkey'),
  35. isPasskeySupported()
  36. ]);
  37. console.log('2FA response:', twoFAResponse);
  38. console.log('Passkey response:', passkeyResponse);
  39. console.log('Passkey browser support:', passkeySupported);
  40. const result = {
  41. has2FA: twoFAResponse.success && twoFAResponse.data?.enabled === true,
  42. hasPasskey: passkeyResponse.success && (passkeyResponse.data?.enabled === true || passkeyResponse.data?.status === 'enabled' || passkeyResponse.data !== null),
  43. passkeySupported
  44. };
  45. console.log('Final verification methods result:', result);
  46. return result;
  47. } catch (error) {
  48. console.error('Failed to check verification methods:', error);
  49. return {
  50. has2FA: false,
  51. hasPasskey: false,
  52. passkeySupported: false
  53. };
  54. }
  55. }
  56. /**
  57. * 执行2FA验证
  58. * @param {string} code - 验证码
  59. * @param {Function} apiCall - API调用函数,接收 {method: '2fa', code} 参数
  60. * @returns {Promise<any>} API响应结果
  61. */
  62. static async verify2FA(code, apiCall) {
  63. if (!code?.trim()) {
  64. throw new Error('请输入验证码或备用码');
  65. }
  66. return await apiCall({
  67. method: '2fa',
  68. code: code.trim()
  69. });
  70. }
  71. /**
  72. * 执行Passkey验证
  73. * @param {Function} apiCall - API调用函数,接收 {method: 'passkey'} 参数
  74. * @returns {Promise<any>} API响应结果
  75. */
  76. static async verifyPasskey(apiCall) {
  77. try {
  78. // 开始Passkey验证
  79. const beginResponse = await API.post('/api/user/passkey/verify/begin');
  80. if (!beginResponse.success) {
  81. throw new Error(beginResponse.message);
  82. }
  83. // 准备WebAuthn选项
  84. const publicKey = prepareCredentialRequestOptions(beginResponse.data);
  85. // 执行WebAuthn验证
  86. const credential = await navigator.credentials.get({ publicKey });
  87. if (!credential) {
  88. throw new Error('Passkey 验证被取消');
  89. }
  90. // 构建验证结果
  91. const assertionResult = buildAssertionResult(credential);
  92. // 完成验证
  93. const finishResponse = await API.post('/api/user/passkey/verify/finish', assertionResult);
  94. if (!finishResponse.success) {
  95. throw new Error(finishResponse.message);
  96. }
  97. // 调用业务API
  98. return await apiCall({
  99. method: 'passkey'
  100. });
  101. } catch (error) {
  102. if (error.name === 'NotAllowedError') {
  103. throw new Error('Passkey 验证被取消或超时');
  104. } else if (error.name === 'InvalidStateError') {
  105. throw new Error('Passkey 验证状态无效');
  106. } else {
  107. throw error;
  108. }
  109. }
  110. }
  111. /**
  112. * 通用验证方法,根据验证类型执行相应的验证流程
  113. * @param {string} method - 验证方式: '2fa' | 'passkey'
  114. * @param {Object} params - 参数对象
  115. * @param {string} params.code - 2FA验证码(当method为'2fa'时必需)
  116. * @param {Function} params.apiCall - API调用函数
  117. * @returns {Promise<any>} API响应结果
  118. */
  119. static async verify(method, { code, apiCall }) {
  120. switch (method) {
  121. case '2fa':
  122. return await this.verify2FA(code, apiCall);
  123. case 'passkey':
  124. return await this.verifyPasskey(apiCall);
  125. default:
  126. throw new Error(`不支持的验证方式: ${method}`);
  127. }
  128. }
  129. }
  130. /**
  131. * 预设的API调用函数工厂
  132. */
  133. export const createApiCalls = {
  134. /**
  135. * 创建查看渠道密钥的API调用
  136. * @param {number} channelId - 渠道ID
  137. */
  138. viewChannelKey: (channelId) => async (verificationData) => {
  139. return await API.post(`/api/channel/${channelId}/key`, verificationData);
  140. },
  141. /**
  142. * 创建自定义API调用
  143. * @param {string} url - API URL
  144. * @param {string} method - HTTP方法,默认为 'POST'
  145. * @param {Object} extraData - 额外的请求数据
  146. */
  147. custom: (url, method = 'POST', extraData = {}) => async (verificationData) => {
  148. const data = { ...extraData, ...verificationData };
  149. switch (method.toUpperCase()) {
  150. case 'GET':
  151. return await API.get(url, { params: data });
  152. case 'POST':
  153. return await API.post(url, data);
  154. case 'PUT':
  155. return await API.put(url, data);
  156. case 'DELETE':
  157. return await API.delete(url, { data });
  158. default:
  159. throw new Error(`不支持的HTTP方法: ${method}`);
  160. }
  161. }
  162. };