| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| import { API, showError } from '../helpers';
|
| import {
|
| prepareCredentialRequestOptions,
|
| buildAssertionResult,
|
| isPasskeySupported,
|
| } from '../helpers/passkey';
|
|
|
| |
| |
| |
|
|
| export class SecureVerificationService {
|
| |
| |
| |
|
|
| static async checkAvailableVerificationMethods() {
|
| try {
|
| const [twoFAResponse, passkeyResponse, passkeySupported] =
|
| await Promise.all([
|
| API.get('/api/user/2fa/status'),
|
| API.get('/api/user/passkey'),
|
| isPasskeySupported(),
|
| ]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|
| };
|
| }
|
| }
|
|
|
| |
| |
| |
| |
|
|
| static async verify2FA(code) {
|
| if (!code?.trim()) {
|
| throw new Error('请输入验证码或备用码');
|
| }
|
|
|
|
|
| const verifyResponse = await API.post('/api/verify', {
|
| method: '2fa',
|
| code: code.trim(),
|
| });
|
|
|
| if (!verifyResponse.data?.success) {
|
| throw new Error(verifyResponse.data?.message || '验证失败');
|
| }
|
|
|
|
|
| }
|
|
|
| |
| |
| |
|
|
| static async verifyPasskey() {
|
| try {
|
|
|
| const beginResponse = await API.post('/api/user/passkey/verify/begin');
|
| if (!beginResponse.data?.success) {
|
| throw new Error(beginResponse.data?.message || '开始验证失败');
|
| }
|
|
|
|
|
| const publicKey = prepareCredentialRequestOptions(
|
| beginResponse.data.data.options,
|
| );
|
|
|
|
|
| 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 || '验证失败');
|
| }
|
|
|
|
|
| const verifyResponse = await API.post('/api/verify', {
|
| method: 'passkey',
|
| });
|
|
|
| if (!verifyResponse.data?.success) {
|
| throw new Error(verifyResponse.data?.message || '验证失败');
|
| }
|
|
|
|
|
| } catch (error) {
|
| if (error.name === 'NotAllowedError') {
|
| throw new Error('Passkey 验证被取消或超时');
|
| } else if (error.name === 'InvalidStateError') {
|
| throw new Error('Passkey 验证状态无效');
|
| } else {
|
| throw error;
|
| }
|
| }
|
| }
|
|
|
| |
| |
| |
| |
| |
|
|
| 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}`);
|
| }
|
| }
|
| }
|
|
|
| |
| |
|
|
| export const createApiCalls = {
|
| |
| |
| |
|
|
| viewChannelKey: (channelId) => async () => {
|
|
|
| const response = await API.post(`/api/channel/${channelId}/key`, {});
|
| return response.data;
|
| },
|
|
|
| |
| |
| |
| |
| |
|
|
| 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;
|
| },
|
| };
|
|
|