xiaohongshufabu/authApi.js
2026-07-22 10:07:46 +08:00

382 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 授权验证 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
};