xiaohongshufabu/authApi.js

382 lines
12 KiB
JavaScript
Raw Normal View History

2026-07-22 10:07:46 +08:00
/**
* 授权验证 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
};