refactor: 重构硬件指纹模块并清理无关依赖
- 添加 ohash 和 systeminformation 依赖项到项目中 - 将硬件指纹生成模块从工具包移至服务器应用,并统一优化注释中的标点符号为中文全角格式,提升代码注释的可读性与一致性。 - 将硬件指纹获取方法的导入路径从 '@furtherverse/utils/fingerprint' 更新为 '@/lib/fingerprint'。 - 移除对本地 workspace 包 @furtherverse/utils 的依赖并清理相关配置 - 删除未使用的工具包配置文件并移除相关依赖项 - 删除硬件指纹测试文件,移除对 systeminformation 模块的模拟和相关测试用例。 - 移除对 fingerprint 工具函数的导出 - 删除未使用的 tsconfig 配置文件
This commit is contained in:
@@ -15,7 +15,6 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@furtherverse/utils": "workspace:*",
|
||||
"@orpc/client": "catalog:",
|
||||
"@orpc/contract": "catalog:",
|
||||
"@orpc/openapi": "catalog:",
|
||||
@@ -30,9 +29,11 @@
|
||||
"@tauri-apps/api": "catalog:",
|
||||
"drizzle-orm": "catalog:",
|
||||
"drizzle-zod": "catalog:",
|
||||
"ohash": "catalog:",
|
||||
"postgres": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"systeminformation": "catalog:",
|
||||
"uuid": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
|
||||
@@ -3,17 +3,17 @@ import si from 'systeminformation'
|
||||
|
||||
/**
|
||||
* 硬件指纹质量等级
|
||||
* - strong: 2+ 个强标识符可用(推荐用于生产授权)
|
||||
* - medium: 1 个强标识符可用(可用但不理想)
|
||||
* - weak: 无强标识符(仅适合开发/测试)
|
||||
* - strong: 2+ 个强标识符可用(推荐用于生产授权)
|
||||
* - medium: 1 个强标识符可用(可用但不理想)
|
||||
* - weak: 无强标识符(仅适合开发/测试)
|
||||
*/
|
||||
export type FingerprintQuality = 'strong' | 'medium' | 'weak'
|
||||
|
||||
/**
|
||||
* 标准化的系统信息(用于机器码生成)
|
||||
* 标准化的系统信息(用于机器码生成)
|
||||
*/
|
||||
export type NormalizedSystemInfo = {
|
||||
/** 系统 UUID(最稳定的硬件标识符) */
|
||||
/** 系统 UUID(最稳定的硬件标识符) */
|
||||
systemUuid: string | null
|
||||
/** 系统序列号 */
|
||||
systemSerial: string | null
|
||||
@@ -25,9 +25,9 @@ export type NormalizedSystemInfo = {
|
||||
biosVersion: string | null
|
||||
/** BIOS 供应商 */
|
||||
biosVendor: string | null
|
||||
/** CPU 品牌标识(用于质量评估) */
|
||||
/** CPU 品牌标识(用于质量评估) */
|
||||
cpuBrand: string | null
|
||||
/** 主硬盘序列号(可选,高稳定性) */
|
||||
/** 主硬盘序列号(可选,高稳定性) */
|
||||
primaryDiskSerial?: string | null
|
||||
}
|
||||
|
||||
@@ -36,14 +36,14 @@ export type NormalizedSystemInfo = {
|
||||
*/
|
||||
export type HardwareFingerprintOptions = {
|
||||
/**
|
||||
* 缓存 TTL(毫秒),默认 10 分钟
|
||||
* 硬件信息变化频率极低,缓存可大幅提升性能
|
||||
* 缓存 TTL(毫秒),默认 10 分钟
|
||||
* 硬件信息变化频率极低,缓存可大幅提升性能
|
||||
*/
|
||||
cacheTtlMs?: number
|
||||
|
||||
/**
|
||||
* 是否包含主硬盘序列号(默认 true)
|
||||
* 注意:在容器/虚拟机环境可能获取失败
|
||||
* 是否包含主硬盘序列号(默认 true)
|
||||
* 注意:在容器/虚拟机环境可能获取失败
|
||||
*/
|
||||
includePrimaryDisk?: boolean
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export type HardwareFingerprintOptions = {
|
||||
* 硬件指纹响应
|
||||
*/
|
||||
export type HardwareFingerprintResult = {
|
||||
/** 机器码(HMAC-SHA256 哈希,64 字符十六进制) */
|
||||
/** 机器码(HMAC-SHA256 哈希,64 字符十六进制) */
|
||||
fingerprint: string
|
||||
/** 指纹质量等级 */
|
||||
quality: FingerprintQuality
|
||||
@@ -91,7 +91,7 @@ function computeQuality(info: NormalizedSystemInfo): {
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地收集标准化系统信息(容错处理)
|
||||
* 安全地收集标准化系统信息(容错处理)
|
||||
*/
|
||||
async function collectNormalizedInfo(
|
||||
opts: HardwareFingerprintOptions,
|
||||
@@ -115,7 +115,7 @@ async function collectNormalizedInfo(
|
||||
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 }>
|
||||
@@ -126,43 +126,43 @@ async function collectNormalizedInfo(
|
||||
}
|
||||
|
||||
return {
|
||||
// 系统级标识符(最稳定)
|
||||
// 系统级标识符(最稳定)
|
||||
systemUuid: (system?.uuid ?? uuid?.hardware ?? null) || null,
|
||||
systemSerial: (system?.serial ?? null) || null,
|
||||
|
||||
// 主板标识符(次稳定)
|
||||
// 主板标识符(次稳定)
|
||||
baseboardSerial: (baseboard?.serial ?? null) || null,
|
||||
baseboardManufacturer: (baseboard?.manufacturer ?? null) || null,
|
||||
|
||||
// BIOS 信息(辅助识别)
|
||||
// BIOS 信息(辅助识别)
|
||||
biosVersion: (bios?.version ?? null) || null,
|
||||
biosVendor: (bios?.vendor ?? null) || null,
|
||||
|
||||
// CPU 信息(辅助识别)
|
||||
// CPU 信息(辅助识别)
|
||||
cpuBrand: (cpu?.brand ?? null) || null,
|
||||
|
||||
// 磁盘序列号(可选,高稳定性)
|
||||
// 磁盘序列号(可选,高稳定性)
|
||||
...(opts.includePrimaryDisk !== false ? { primaryDiskSerial } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 获取硬件指纹(机器码)
|
||||
* 获取硬件指纹(机器码)
|
||||
*
|
||||
* 适用场景:客户端部署的软件授权、机器绑定
|
||||
* 适用场景:客户端部署的软件授权、机器绑定
|
||||
*
|
||||
* 安全说明:
|
||||
* - 返回 SHA-256 哈希(Base64URL 编码,43 字符),不可逆推原始硬件信息
|
||||
* 安全说明:
|
||||
* - 返回 SHA-256 哈希(Base64URL 编码,43 字符),不可逆推原始硬件信息
|
||||
* - 使用 ohash 自动处理对象序列化和哈希
|
||||
* - 客户端部署场景:客户可以看到代码,无法使用密钥加密
|
||||
* - 安全性依赖硬件信息本身的不可伪造性(来自操作系统)
|
||||
* - 客户端部署场景:客户可以看到代码,无法使用密钥加密
|
||||
* - 安全性依赖硬件信息本身的不可伪造性(来自操作系统)
|
||||
* - 自动缓存减少系统调用开销
|
||||
*
|
||||
* 稳定性:
|
||||
* 稳定性:
|
||||
* - 优先使用系统 UUID、序列号等不易变更的标识符
|
||||
* - 避免网络接口等易变信息
|
||||
* - 容错处理,部分信息缺失不影响生成
|
||||
* - 容错处理,部分信息缺失不影响生成
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@@ -198,9 +198,9 @@ export async function getHardwareFingerprint(
|
||||
// 计算质量
|
||||
const { quality, count } = computeQuality(info)
|
||||
|
||||
// 使用 ohash 生成指纹(自动序列化 + SHA-256 + Base64URL)
|
||||
// 使用 ohash 生成指纹(自动序列化 + SHA-256 + Base64URL)
|
||||
const fingerprint = hash({
|
||||
v: 1, // 版本号,未来如需变更采集策略可递增
|
||||
v: 1, // 版本号,未来如需变更采集策略可递增
|
||||
info,
|
||||
})
|
||||
|
||||
@@ -223,7 +223,7 @@ export async function getHardwareFingerprint(
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指纹缓存(用于测试或强制刷新)
|
||||
* 清除指纹缓存(用于测试或强制刷新)
|
||||
*/
|
||||
export function clearFingerprintCache(): void {
|
||||
cache = 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 () => {
|
||||
|
||||
17
bun.lock
17
bun.lock
@@ -25,7 +25,6 @@
|
||||
"name": "@furtherverse/server",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@furtherverse/utils": "workspace:*",
|
||||
"@orpc/client": "catalog:",
|
||||
"@orpc/contract": "catalog:",
|
||||
"@orpc/openapi": "catalog:",
|
||||
@@ -40,9 +39,11 @@
|
||||
"@tauri-apps/api": "catalog:",
|
||||
"drizzle-orm": "catalog:",
|
||||
"drizzle-zod": "catalog:",
|
||||
"ohash": "catalog:",
|
||||
"postgres": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"systeminformation": "catalog:",
|
||||
"uuid": "catalog:",
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -71,18 +72,6 @@
|
||||
"name": "@furtherverse/tsconfig",
|
||||
"version": "1.0.0",
|
||||
},
|
||||
"packages/utils": {
|
||||
"name": "@furtherverse/utils",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"ohash": "catalog:",
|
||||
"systeminformation": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@furtherverse/tsconfig": "workspace:*",
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
},
|
||||
},
|
||||
"catalog": {
|
||||
"@biomejs/biome": "^2.3.11",
|
||||
@@ -262,8 +251,6 @@
|
||||
|
||||
"@furtherverse/tsconfig": ["@furtherverse/tsconfig@workspace:packages/tsconfig"],
|
||||
|
||||
"@furtherverse/utils": ["@furtherverse/utils@workspace:packages/utils"],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"name": "@furtherverse/utils",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"fix": "biome check --write",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"imports": {
|
||||
"#*": "./src/*"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"ohash": "catalog:",
|
||||
"systeminformation": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@furtherverse/tsconfig": "workspace:*",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, mock } from 'bun:test'
|
||||
import { getHardwareFingerprint } from './fingerprint'
|
||||
|
||||
// Mock systeminformation module
|
||||
const mockSystemInfo = {
|
||||
uuid: mock(() =>
|
||||
Promise.resolve({
|
||||
os: 'test-os-uuid',
|
||||
hardware: 'test-hardware-uuid',
|
||||
}),
|
||||
),
|
||||
baseboard: mock(() =>
|
||||
Promise.resolve({
|
||||
manufacturer: 'Test Manufacturer',
|
||||
model: 'Test Model',
|
||||
version: '1.0',
|
||||
serial: 'TEST123',
|
||||
}),
|
||||
),
|
||||
bios: mock(() =>
|
||||
Promise.resolve({
|
||||
vendor: 'Test Vendor',
|
||||
version: '1.0.0',
|
||||
releaseDate: '2024-01-01',
|
||||
}),
|
||||
),
|
||||
system: mock(() =>
|
||||
Promise.resolve({
|
||||
manufacturer: 'Test System',
|
||||
model: 'Test Model',
|
||||
version: '1.0',
|
||||
sku: 'TEST-SKU',
|
||||
}),
|
||||
),
|
||||
diskLayout: mock(() =>
|
||||
Promise.resolve([
|
||||
{
|
||||
device: '/dev/sda',
|
||||
type: 'SSD',
|
||||
name: 'Test Disk',
|
||||
size: 512000000000,
|
||||
},
|
||||
]),
|
||||
),
|
||||
networkInterfaces: mock(() =>
|
||||
Promise.resolve([
|
||||
{
|
||||
iface: 'eth0',
|
||||
mac: '00:11:22:33:44:55',
|
||||
ip4: '192.168.1.1',
|
||||
},
|
||||
]),
|
||||
),
|
||||
}
|
||||
|
||||
mock.module('systeminformation', () => ({
|
||||
default: mockSystemInfo,
|
||||
}))
|
||||
|
||||
describe('fingerprint', () => {
|
||||
beforeEach(() => {
|
||||
// Reset all mocks before each test
|
||||
mockSystemInfo.uuid.mockClear()
|
||||
mockSystemInfo.baseboard.mockClear()
|
||||
mockSystemInfo.bios.mockClear()
|
||||
mockSystemInfo.system.mockClear()
|
||||
mockSystemInfo.diskLayout.mockClear()
|
||||
mockSystemInfo.networkInterfaces.mockClear()
|
||||
})
|
||||
|
||||
describe('getHardwareFingerprint', () => {
|
||||
it('should return a fingerprint hash', async () => {
|
||||
const fingerprint = await getHardwareFingerprint()
|
||||
|
||||
expect(fingerprint).toBeDefined()
|
||||
expect(typeof fingerprint).toBe('string')
|
||||
expect(fingerprint.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should call all system information methods', async () => {
|
||||
await getHardwareFingerprint()
|
||||
|
||||
expect(mockSystemInfo.uuid).toHaveBeenCalledTimes(1)
|
||||
expect(mockSystemInfo.baseboard).toHaveBeenCalledTimes(1)
|
||||
expect(mockSystemInfo.bios).toHaveBeenCalledTimes(1)
|
||||
expect(mockSystemInfo.system).toHaveBeenCalledTimes(1)
|
||||
expect(mockSystemInfo.diskLayout).toHaveBeenCalledTimes(1)
|
||||
expect(mockSystemInfo.networkInterfaces).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should return the same fingerprint for the same system info', async () => {
|
||||
const fingerprint1 = await getHardwareFingerprint()
|
||||
const fingerprint2 = await getHardwareFingerprint()
|
||||
|
||||
expect(fingerprint1).toBe(fingerprint2)
|
||||
})
|
||||
|
||||
it('should return different fingerprint when system info changes', async () => {
|
||||
const fingerprint1 = await getHardwareFingerprint()
|
||||
|
||||
// Change mock data
|
||||
mockSystemInfo.uuid.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
os: 'different-os-uuid',
|
||||
hardware: 'different-hardware-uuid',
|
||||
}),
|
||||
)
|
||||
|
||||
const fingerprint2 = await getHardwareFingerprint()
|
||||
|
||||
expect(fingerprint1).not.toBe(fingerprint2)
|
||||
})
|
||||
|
||||
it('should handle empty system information gracefully', async () => {
|
||||
// Mock empty responses
|
||||
mockSystemInfo.uuid.mockImplementationOnce(() =>
|
||||
Promise.resolve({ os: '', hardware: '' }),
|
||||
)
|
||||
mockSystemInfo.baseboard.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
manufacturer: '',
|
||||
model: '',
|
||||
version: '',
|
||||
serial: '',
|
||||
}),
|
||||
)
|
||||
mockSystemInfo.bios.mockImplementationOnce(() =>
|
||||
Promise.resolve({ vendor: '', version: '', releaseDate: '' }),
|
||||
)
|
||||
mockSystemInfo.system.mockImplementationOnce(() =>
|
||||
Promise.resolve({ manufacturer: '', model: '', version: '', sku: '' }),
|
||||
)
|
||||
mockSystemInfo.diskLayout.mockImplementationOnce(() =>
|
||||
Promise.resolve([]),
|
||||
)
|
||||
mockSystemInfo.networkInterfaces.mockImplementationOnce(() =>
|
||||
Promise.resolve([]),
|
||||
)
|
||||
|
||||
const fingerprint = await getHardwareFingerprint()
|
||||
|
||||
expect(fingerprint).toBeDefined()
|
||||
expect(typeof fingerprint).toBe('string')
|
||||
})
|
||||
|
||||
it('should handle partial system information', async () => {
|
||||
mockSystemInfo.baseboard.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
manufacturer: 'Only Manufacturer',
|
||||
model: '',
|
||||
version: '',
|
||||
serial: '',
|
||||
}),
|
||||
)
|
||||
|
||||
const fingerprint = await getHardwareFingerprint()
|
||||
|
||||
expect(fingerprint).toBeDefined()
|
||||
expect(typeof fingerprint).toBe('string')
|
||||
})
|
||||
|
||||
it('should be deterministic with the same input', async () => {
|
||||
const results = await Promise.all([
|
||||
getHardwareFingerprint(),
|
||||
getHardwareFingerprint(),
|
||||
getHardwareFingerprint(),
|
||||
])
|
||||
|
||||
expect(results[0]).toBe(results[1])
|
||||
expect(results[1]).toBe(results[2])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1 +0,0 @@
|
||||
export * from './fingerprint'
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "@furtherverse/tsconfig/base.json"
|
||||
}
|
||||
Reference in New Issue
Block a user