/** * 授权验证 API 客户端 * 负责与后端 Python 验证服务进行通信 * Author: taiyi1224 */ // 配置常量 const AUTH_CONFIG = { // ⚠️ 请修改为你的实际服务器地址 API_URL: 'http://km.taisan.online/api/v1', SOFTWARE_ID: 'QFFB', // 软件 ID(与后端一致) SECRET_KEY: 'taiyi1224', // 密钥(与后端一致) TIMEOUT: 10000, // 请求超时时间(毫秒) RECHECK_INTERVAL: 60 * 60 * 1000 // ✅ 联网复查间隔:1小时(毫秒),可按需调整 }; /** * 生成签名 */ async function generateSignature(data) { const encoder = new TextEncoder(); const signatureData = `${data.software_id}${data.license_key}${data.machine_code}${data.timestamp}`; const combined = signatureData + AUTH_CONFIG.SECRET_KEY; const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(combined)); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); } /** * 获取机器码(基于浏览器指纹) */ async function getMachineCode() { const cacheKey = 'tb_auth_machine_code'; const cached = localStorage.getItem(cacheKey); if (cached) return cached; try { const fingerprint = [ navigator.userAgent, navigator.language, navigator.platform, screen.width, screen.height, new Date().getTimezoneOffset(), ].join('|'); const encoder = new TextEncoder(); const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(fingerprint)); const hashArray = Array.from(new Uint8Array(hashBuffer)); const machineCode = hashArray.map(b => b.toString(16).padStart(2, '0')).join('').substring(0, 32).toUpperCase(); localStorage.setItem(cacheKey, machineCode); return machineCode; } catch (error) { console.error('[Auth] 生成机器码失败:', error); const fallback = 'BROWSER_' + Date.now().toString(36).toUpperCase(); localStorage.setItem(cacheKey, fallback); return fallback; } } /** * 测试服务器连接 */ async function testConnection() { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), AUTH_CONFIG.TIMEOUT); const response = await fetch(`${AUTH_CONFIG.API_URL}/auth/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ software_id: AUTH_CONFIG.SOFTWARE_ID, license_key: 'TEST', machine_code: await getMachineCode(), timestamp: Math.floor(Date.now() / 1000), signature: 'test' }), signal: controller.signal }); clearTimeout(timeoutId); return { success: true, message: '服务器连接正常' }; } catch (error) { if (error.name === 'AbortError') { return { success: false, message: `连接超时,服务器可能无响应:${AUTH_CONFIG.API_URL}` }; } return { success: false, message: `无法连接到服务器:${error.message}\n请检查服务器地址是否正确:${AUTH_CONFIG.API_URL}` }; } } /** * 在线验证卡密 */ async function verifyLicense(licenseKey) { try { const machineCode = await getMachineCode(); const timestamp = Math.floor(Date.now() / 1000); const signature = await generateSignature({ software_id: AUTH_CONFIG.SOFTWARE_ID, license_key: licenseKey, machine_code: machineCode, timestamp: timestamp }); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), AUTH_CONFIG.TIMEOUT); const response = await fetch(`${AUTH_CONFIG.API_URL}/auth/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ software_id: AUTH_CONFIG.SOFTWARE_ID, license_key: licenseKey, machine_code: machineCode, timestamp: timestamp, signature: signature, software_version: chrome.runtime.getManifest().version }), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { let errorMsg = `服务器返回错误:${response.status}`; try { const errorData = await response.json(); errorMsg = errorData.message || errorMsg; } catch (e) {} switch (response.status) { case 503: errorMsg = '服务器暂时不可用,请稍后重试'; break; case 500: errorMsg = '服务器内部错误,请联系管理员'; break; case 404: errorMsg = 'API 接口不存在,请检查 API 地址'; break; case 401: errorMsg = '签名验证失败,请检查密钥配置'; break; } return { success: false, message: errorMsg }; } const result = await response.json(); if (!result.success) { return { success: false, message: result.message || '验证失败' }; } const data = result.data || {}; return { success: true, message: result.message || '验证成功', data: { expire_time: data.expire_time, machine_code: machineCode, last_check: new Date().toISOString(), license_key: data.license_key || licenseKey, type: data.type, type_name: data.type_name || '', remaining_days: data.remaining_days, product_name: data.product_name || '' } }; } catch (error) { if (error.name === 'AbortError') { return { success: false, message: `连接超时(${AUTH_CONFIG.TIMEOUT / 1000}秒),请检查网络连接或服务器地址:${AUTH_CONFIG.API_URL}` }; } return { success: false, message: `网络请求异常:${error.message}\n请检查网络连接和服务器地址:${AUTH_CONFIG.API_URL}` }; } } /** * 解绑卡密 */ async function unbindLicense(licenseKey) { try { const machineCode = await getMachineCode(); const timestamp = Math.floor(Date.now() / 1000); const signature = await generateSignature({ software_id: AUTH_CONFIG.SOFTWARE_ID, license_key: licenseKey, machine_code: machineCode, timestamp: timestamp }); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), AUTH_CONFIG.TIMEOUT); const response = await fetch(`${AUTH_CONFIG.API_URL}/auth/unbind`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ software_id: AUTH_CONFIG.SOFTWARE_ID, license_key: licenseKey, machine_code: machineCode, timestamp: timestamp, signature: signature }), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { let errorMsg = `服务器返回错误:${response.status}`; try { const errorData = await response.json(); errorMsg = errorData.message || errorMsg; } catch (e) {} switch (response.status) { case 503: errorMsg = '服务器暂时不可用,请稍后重试'; break; case 500: errorMsg = '服务器内部错误,请联系管理员'; break; case 404: errorMsg = 'API 接口不存在,请检查 API 地址'; break; case 401: errorMsg = '签名验证失败,请检查密钥配置'; break; } return { success: false, message: errorMsg }; } const result = await response.json(); return { success: result.success || false, message: result.message || (result.success ? '解绑成功' : '解绑失败') }; } catch (error) { if (error.name === 'AbortError') { return { success: false, message: `连接超时,请检查网络连接` }; } return { success: false, message: `网络请求失败:${error.message}` }; } } /** * 解绑设备 */ async function unbindDevice() { try { const machineCode = await getMachineCode(); const timestamp = Math.floor(Date.now() / 1000); const signature = await generateSignature({ software_id: AUTH_CONFIG.SOFTWARE_ID, machine_code: machineCode, timestamp: timestamp }); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), AUTH_CONFIG.TIMEOUT); const response = await fetch(`${AUTH_CONFIG.API_URL}/auth/unbind_device`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ software_id: AUTH_CONFIG.SOFTWARE_ID, machine_code: machineCode, timestamp: timestamp, signature: signature }), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { let errorMsg = `服务器返回错误:${response.status}`; try { const errorData = await response.json(); errorMsg = errorData.message || errorMsg; } catch (e) {} switch (response.status) { case 503: errorMsg = '服务器暂时不可用,请稍后重试'; break; case 500: errorMsg = '服务器内部错误,请联系管理员'; break; case 404: errorMsg = 'API 接口不存在,请检查 API 地址'; break; case 401: errorMsg = '签名验证失败,请检查密钥配置'; break; } return { success: false, message: errorMsg }; } const result = await response.json(); return { success: result.success || false, message: result.message || (result.success ? '设备解绑成功' : '解绑失败') }; } catch (error) { if (error.name === 'AbortError') { return { success: false, message: `连接超时,请检查网络连接` }; } return { success: false, message: `网络请求失败:${error.message}` }; } } /** * 保存授权信息到本地存储 */ async function saveAuthData(authData) { await chrome.storage.local.set({ tb_auth_data: authData }); } /** * 获取本地存储的授权信息 */ async function getAuthData() { const result = await chrome.storage.local.get('tb_auth_data'); return result.tb_auth_data || null; } /** * 清除授权信息 */ async function clearAuthData() { await chrome.storage.local.remove('tb_auth_data'); } /** * ✅ 核心修复:检查是否已授权 * * 逻辑: * 1. 本地无数据 → 未授权 * 2. 距上次联网复查超过 RECHECK_INTERVAL → 联网验证 * - 服务器返回成功 → 刷新 last_check,授权有效 * - 服务器返回失败(卡密被删/禁用/过期)→ 清除本地授权,返回未授权 * 3. 未超过间隔 → 使用本地缓存,避免频繁请求 */ async function isAuthorized() { const authData = await getAuthData(); // 1. 本地没有授权数据 if (!authData || !authData.license_key) { return false; } const now = Date.now(); const lastCheck = authData.last_check ? new Date(authData.last_check).getTime() : 0; const elapsed = now - lastCheck; // 2. 超过复查间隔,联网重新验证 if (elapsed >= AUTH_CONFIG.RECHECK_INTERVAL) { console.log('[Auth] 超过复查间隔,开始联网验证...'); const result = await verifyLicense(authData.license_key); if (result.success) { // 验证通过,更新本地缓存(刷新 last_check) await saveAuthData(result.data); console.log('[Auth] 联网验证通过,授权有效'); return true; } else { // 验证失败:卡密已被删除/禁用/过期,清除本地授权 await clearAuthData(); console.warn('[Auth] 联网验证失败,已清除本地授权:', result.message); return false; } } // 3. 未超过间隔,使用本地缓存 const remainingMin = Math.round((AUTH_CONFIG.RECHECK_INTERVAL - elapsed) / 60000); console.log(`[Auth] 使用本地缓存,距下次复查还有 ${remainingMin} 分钟`); return true; } // 导出 API export { AUTH_CONFIG, testConnection, verifyLicense, unbindLicense, unbindDevice, getMachineCode, saveAuthData, getAuthData, clearAuthData, isAuthorized };