382 lines
12 KiB
JavaScript
382 lines
12 KiB
JavaScript
/**
|
||
* 授权验证 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
|
||
}; |