refactor: 重构硬件指纹模块并清理无关依赖
- 添加 ohash 和 systeminformation 依赖项到项目中 - 将硬件指纹生成模块从工具包移至服务器应用,并统一优化注释中的标点符号为中文全角格式,提升代码注释的可读性与一致性。 - 将硬件指纹获取方法的导入路径从 '@furtherverse/utils/fingerprint' 更新为 '@/lib/fingerprint'。 - 移除对本地 workspace 包 @furtherverse/utils 的依赖并清理相关配置 - 删除未使用的工具包配置文件并移除相关依赖项 - 删除硬件指纹测试文件,移除对 systeminformation 模块的模拟和相关测试用例。 - 移除对 fingerprint 工具函数的导出 - 删除未使用的 tsconfig 配置文件
This commit is contained in:
231
apps/server/src/lib/fingerprint.ts
Normal file
231
apps/server/src/lib/fingerprint.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { hash } from 'ohash'
|
||||
import si from 'systeminformation'
|
||||
|
||||
/**
|
||||
* 硬件指纹质量等级
|
||||
* - strong: 2+ 个强标识符可用(推荐用于生产授权)
|
||||
* - medium: 1 个强标识符可用(可用但不理想)
|
||||
* - weak: 无强标识符(仅适合开发/测试)
|
||||
*/
|
||||
export type FingerprintQuality = 'strong' | 'medium' | 'weak'
|
||||
|
||||
/**
|
||||
* 标准化的系统信息(用于机器码生成)
|
||||
*/
|
||||
export type NormalizedSystemInfo = {
|
||||
/** 系统 UUID(最稳定的硬件标识符) */
|
||||
systemUuid: string | null
|
||||
/** 系统序列号 */
|
||||
systemSerial: string | null
|
||||
/** 主板序列号 */
|
||||
baseboardSerial: string | null
|
||||
/** 主板制造商 */
|
||||
baseboardManufacturer: string | null
|
||||
/** BIOS 版本 */
|
||||
biosVersion: string | null
|
||||
/** BIOS 供应商 */
|
||||
biosVendor: string | null
|
||||
/** CPU 品牌标识(用于质量评估) */
|
||||
cpuBrand: string | null
|
||||
/** 主硬盘序列号(可选,高稳定性) */
|
||||
primaryDiskSerial?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 硬件指纹配置选项
|
||||
*/
|
||||
export type HardwareFingerprintOptions = {
|
||||
/**
|
||||
* 缓存 TTL(毫秒),默认 10 分钟
|
||||
* 硬件信息变化频率极低,缓存可大幅提升性能
|
||||
*/
|
||||
cacheTtlMs?: number
|
||||
|
||||
/**
|
||||
* 是否包含主硬盘序列号(默认 true)
|
||||
* 注意:在容器/虚拟机环境可能获取失败
|
||||
*/
|
||||
includePrimaryDisk?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 硬件指纹响应
|
||||
*/
|
||||
export type HardwareFingerprintResult = {
|
||||
/** 机器码(HMAC-SHA256 哈希,64 字符十六进制) */
|
||||
fingerprint: string
|
||||
/** 指纹质量等级 */
|
||||
quality: FingerprintQuality
|
||||
/** 可用的强标识符数量 */
|
||||
strongIdentifiersCount: number
|
||||
/** 生成时间戳 */
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
// 缓存实例
|
||||
let cache: {
|
||||
expiresAt: number
|
||||
value: HardwareFingerprintResult
|
||||
} | null = null
|
||||
|
||||
// 防止并发重复请求
|
||||
let inFlight: Promise<HardwareFingerprintResult> | null = null
|
||||
|
||||
/**
|
||||
* 计算指纹质量
|
||||
*/
|
||||
function computeQuality(info: NormalizedSystemInfo): {
|
||||
quality: FingerprintQuality
|
||||
count: number
|
||||
} {
|
||||
const strongKeys = [
|
||||
info.systemUuid,
|
||||
info.systemSerial,
|
||||
info.baseboardSerial,
|
||||
info.primaryDiskSerial,
|
||||
].filter(Boolean).length
|
||||
|
||||
if (strongKeys >= 2) return { quality: 'strong', count: strongKeys }
|
||||
if (strongKeys === 1) return { quality: 'medium', count: strongKeys }
|
||||
return { quality: 'weak', count: 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地收集标准化系统信息(容错处理)
|
||||
*/
|
||||
async function collectNormalizedInfo(
|
||||
opts: HardwareFingerprintOptions,
|
||||
): Promise<NormalizedSystemInfo> {
|
||||
// 使用 Promise.allSettled 避免单点失败
|
||||
const tasks = await Promise.allSettled([
|
||||
si.uuid(),
|
||||
si.system(),
|
||||
si.baseboard(),
|
||||
si.bios(),
|
||||
si.cpu(),
|
||||
opts.includePrimaryDisk !== false ? si.diskLayout() : Promise.resolve([]),
|
||||
])
|
||||
|
||||
const [uuidRes, systemRes, baseboardRes, biosRes, cpuRes, diskRes] = tasks
|
||||
|
||||
const uuid = uuidRes.status === 'fulfilled' ? uuidRes.value : null
|
||||
const system = systemRes.status === 'fulfilled' ? systemRes.value : null
|
||||
const baseboard =
|
||||
baseboardRes.status === 'fulfilled' ? baseboardRes.value : null
|
||||
const bios = biosRes.status === 'fulfilled' ? biosRes.value : null
|
||||
const cpu = cpuRes.status === 'fulfilled' ? cpuRes.value : null
|
||||
|
||||
// 提取主硬盘序列号(通常是第一个物理磁盘)
|
||||
let primaryDiskSerial: string | null = null
|
||||
if (diskRes.status === 'fulfilled' && Array.isArray(diskRes.value)) {
|
||||
const disks = diskRes.value as Array<{ serialNum?: string; type?: string }>
|
||||
const physicalDisk = disks.find(
|
||||
(d) => d.type !== 'USB' && d.serialNum && d.serialNum.trim(),
|
||||
)
|
||||
primaryDiskSerial = physicalDisk?.serialNum?.trim() || null
|
||||
}
|
||||
|
||||
return {
|
||||
// 系统级标识符(最稳定)
|
||||
systemUuid: (system?.uuid ?? uuid?.hardware ?? null) || null,
|
||||
systemSerial: (system?.serial ?? null) || null,
|
||||
|
||||
// 主板标识符(次稳定)
|
||||
baseboardSerial: (baseboard?.serial ?? null) || null,
|
||||
baseboardManufacturer: (baseboard?.manufacturer ?? null) || null,
|
||||
|
||||
// BIOS 信息(辅助识别)
|
||||
biosVersion: (bios?.version ?? null) || null,
|
||||
biosVendor: (bios?.vendor ?? null) || null,
|
||||
|
||||
// CPU 信息(辅助识别)
|
||||
cpuBrand: (cpu?.brand ?? null) || null,
|
||||
|
||||
// 磁盘序列号(可选,高稳定性)
|
||||
...(opts.includePrimaryDisk !== false ? { primaryDiskSerial } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 获取硬件指纹(机器码)
|
||||
*
|
||||
* 适用场景:客户端部署的软件授权、机器绑定
|
||||
*
|
||||
* 安全说明:
|
||||
* - 返回 SHA-256 哈希(Base64URL 编码,43 字符),不可逆推原始硬件信息
|
||||
* - 使用 ohash 自动处理对象序列化和哈希
|
||||
* - 客户端部署场景:客户可以看到代码,无法使用密钥加密
|
||||
* - 安全性依赖硬件信息本身的不可伪造性(来自操作系统)
|
||||
* - 自动缓存减少系统调用开销
|
||||
*
|
||||
* 稳定性:
|
||||
* - 优先使用系统 UUID、序列号等不易变更的标识符
|
||||
* - 避免网络接口等易变信息
|
||||
* - 容错处理,部分信息缺失不影响生成
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const result = await getHardwareFingerprint({
|
||||
* cacheTtlMs: 600000, // 10 分钟
|
||||
* includePrimaryDisk: true,
|
||||
* })
|
||||
*
|
||||
* console.log(result.fingerprint) // "a3f5e8c2d1b4..."
|
||||
* console.log(result.quality) // "strong"
|
||||
* ```
|
||||
*/
|
||||
export async function getHardwareFingerprint(
|
||||
opts: HardwareFingerprintOptions,
|
||||
): Promise<HardwareFingerprintResult> {
|
||||
const ttl = opts.cacheTtlMs ?? 10 * 60 * 1000
|
||||
const now = Date.now()
|
||||
|
||||
// 返回缓存结果
|
||||
if (cache && cache.expiresAt > now) {
|
||||
return cache.value
|
||||
}
|
||||
|
||||
// 防止并发重复请求
|
||||
if (inFlight) {
|
||||
return inFlight
|
||||
}
|
||||
|
||||
inFlight = (async () => {
|
||||
// 收集标准化信息
|
||||
const info = await collectNormalizedInfo(opts)
|
||||
|
||||
// 计算质量
|
||||
const { quality, count } = computeQuality(info)
|
||||
|
||||
// 使用 ohash 生成指纹(自动序列化 + SHA-256 + Base64URL)
|
||||
const fingerprint = hash({
|
||||
v: 1, // 版本号,未来如需变更采集策略可递增
|
||||
info,
|
||||
})
|
||||
|
||||
const result: HardwareFingerprintResult = {
|
||||
fingerprint,
|
||||
quality,
|
||||
strongIdentifiersCount: count,
|
||||
timestamp: now,
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
cache = { expiresAt: now + ttl, value: result }
|
||||
|
||||
return result
|
||||
})().finally(() => {
|
||||
inFlight = null
|
||||
})
|
||||
|
||||
return inFlight
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指纹缓存(用于测试或强制刷新)
|
||||
*/
|
||||
export function clearFingerprintCache(): void {
|
||||
cache = null
|
||||
inFlight = null
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getHardwareFingerprint } from '@furtherverse/utils/fingerprint'
|
||||
import { getHardwareFingerprint } from '@/lib/fingerprint'
|
||||
import { os } from '../server'
|
||||
|
||||
export const get = os.fingerprint.get.handler(async () => {
|
||||
|
||||
Reference in New Issue
Block a user