secureVerification.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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. * 验证状态完全由后端 Session 控制,前端不存储任何状态
  24. */
  25. export class SecureVerificationService {
  26. /**
  27. * 检查用户可用的验证方式
  28. * @returns {Promise<{has2FA: boolean, hasPasskey: boolean, passkeySupported: boolean}>}
  29. */
  30. static async checkAvailableVerificationMethods() {
  31. try {
  32. const [twoFAResponse, passkeyResponse, passkeySupported] =
  33. await Promise.all([
  34. API.get('/api/user/2fa/status'),
  35. API.get('/api/user/passkey'),
  36. isPasskeySupported(),
  37. ]);
  38. console.log('=== DEBUGGING VERIFICATION METHODS ===');
  39. console.log('2FA Response:', JSON.stringify(twoFAResponse, null, 2));
  40. console.log(
  41. 'Passkey Response:',
  42. JSON.stringify(passkeyResponse, null, 2),
  43. );
  44. const has2FA =
  45. twoFAResponse.data?.success &&
  46. twoFAResponse.data?.data?.enabled === true;
  47. const hasPasskey =
  48. passkeyResponse.data?.success &&
  49. passkeyResponse.data?.data?.enabled === true;
  50. console.log('has2FA calculation:', {
  51. success: twoFAResponse.data?.success,
  52. dataExists: !!twoFAResponse.data?.data,
  53. enabled: twoFAResponse.data?.data?.enabled,
  54. result: has2FA,
  55. });
  56. console.log('hasPasskey calculation:', {
  57. success: passkeyResponse.data?.success,
  58. dataExists: !!passkeyResponse.data?.data,
  59. enabled: passkeyResponse.data?.data?.enabled,
  60. result: hasPasskey,
  61. });
  62. const result = {
  63. has2FA,
  64. hasPasskey,
  65. passkeySupported,
  66. };
  67. return result;
  68. } catch (error) {
  69. console.error('Failed to check verification methods:', error);
  70. return {
  71. has2FA: false,
  72. hasPasskey: false,
  73. passkeySupported: false,
  74. };
  75. }
  76. }
  77. /**
  78. * 执行2FA验证
  79. * @param {string} code - 验证码
  80. * @returns {Promise<void>}
  81. */
  82. static async verify2FA(code) {
  83. if (!code?.trim()) {
  84. throw new Error('请输入验证码或备用码');
  85. }
  86. // 调用通用验证 API,验证成功后后端会设置 session
  87. const verifyResponse = await API.post('/api/verify', {
  88. method: '2fa',
  89. code: code.trim(),
  90. });
  91. if (!verifyResponse.data?.success) {
  92. throw new Error(verifyResponse.data?.message || '验证失败');
  93. }
  94. // 验证成功,session 已在后端设置
  95. }
  96. /**
  97. * 执行Passkey验证
  98. * @returns {Promise<void>}
  99. */
  100. static async verifyPasskey() {
  101. try {
  102. // 开始Passkey验证
  103. const beginResponse = await API.post('/api/user/passkey/verify/begin');
  104. if (!beginResponse.data?.success) {
  105. throw new Error(beginResponse.data?.message || '开始验证失败');
  106. }
  107. // 准备WebAuthn选项
  108. const publicKey = prepareCredentialRequestOptions(
  109. beginResponse.data.data.options,
  110. );
  111. // 执行WebAuthn验证
  112. const credential = await navigator.credentials.get({ publicKey });
  113. if (!credential) {
  114. throw new Error('Passkey 验证被取消');
  115. }
  116. // 构建验证结果
  117. const assertionResult = buildAssertionResult(credential);
  118. // 完成验证
  119. const finishResponse = await API.post(
  120. '/api/user/passkey/verify/finish',
  121. assertionResult,
  122. );
  123. if (!finishResponse.data?.success) {
  124. throw new Error(finishResponse.data?.message || '验证失败');
  125. }
  126. // 调用通用验证 API 设置 session(Passkey 验证已完成)
  127. const verifyResponse = await API.post('/api/verify', {
  128. method: 'passkey',
  129. });
  130. if (!verifyResponse.data?.success) {
  131. throw new Error(verifyResponse.data?.message || '验证失败');
  132. }
  133. // 验证成功,session 已在后端设置
  134. } catch (error) {
  135. if (error.name === 'NotAllowedError') {
  136. throw new Error('Passkey 验证被取消或超时');
  137. } else if (error.name === 'InvalidStateError') {
  138. throw new Error('Passkey 验证状态无效');
  139. } else {
  140. throw error;
  141. }
  142. }
  143. }
  144. /**
  145. * 通用验证方法,根据验证类型执行相应的验证流程
  146. * @param {string} method - 验证方式: '2fa' | 'passkey'
  147. * @param {string} code - 2FA验证码(当method为'2fa'时必需)
  148. * @returns {Promise<void>}
  149. */
  150. static async verify(method, code = '') {
  151. switch (method) {
  152. case '2fa':
  153. return await this.verify2FA(code);
  154. case 'passkey':
  155. return await this.verifyPasskey();
  156. default:
  157. throw new Error(`不支持的验证方式: ${method}`);
  158. }
  159. }
  160. }
  161. /**
  162. * 预设的API调用函数工厂
  163. */
  164. export const createApiCalls = {
  165. /**
  166. * 创建查看渠道密钥的API调用
  167. * @param {number} channelId - 渠道ID
  168. */
  169. viewChannelKey: (channelId) => async () => {
  170. // 新系统中,验证已通过中间件处理,直接调用 API 即可
  171. const response = await API.post(`/api/channel/${channelId}/key`, {});
  172. return response.data;
  173. },
  174. /**
  175. * 创建自定义API调用
  176. * @param {string} url - API URL
  177. * @param {string} method - HTTP方法,默认为 'POST'
  178. * @param {Object} extraData - 额外的请求数据
  179. */
  180. custom:
  181. (url, method = 'POST', extraData = {}) =>
  182. async () => {
  183. // 新系统中,验证已通过中间件处理
  184. const data = extraData;
  185. let response;
  186. switch (method.toUpperCase()) {
  187. case 'GET':
  188. response = await API.get(url, { params: data });
  189. break;
  190. case 'POST':
  191. response = await API.post(url, data);
  192. break;
  193. case 'PUT':
  194. response = await API.put(url, data);
  195. break;
  196. case 'DELETE':
  197. response = await API.delete(url, { data });
  198. break;
  199. default:
  200. throw new Error(`不支持的HTTP方法: ${method}`);
  201. }
  202. return response.data;
  203. },
  204. };