|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(), |
|
|
]); |
|
|
|
|
|
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, |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
}, |
|
|
}; |
|
|
|