import { createHash } from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import { withRegistryLock, writeFileAtomic } from 'soma-core/server/registry'; const MAX_ACTIVE_NONCES = 10_000; interface StoredReplayState { version: 1; nonces: Record; } export interface TrustedContextReplayBackend { claim(nonce: string, expiresAt: number, at: number): Promise; reset?(): Promise | void; } export class MemoryTrustedContextReplayBackend implements TrustedContextReplayBackend { private readonly nonces = new Map(); async claim(nonce: string, expiresAt: number, at: number): Promise { this.prune(at); const current = this.nonces.get(nonce); if (current !== undefined && current > at) return false; if (this.nonces.size >= MAX_ACTIVE_NONCES) return false; this.nonces.set(nonce, expiresAt); return true; } reset(): void { this.nonces.clear(); } private prune(at: number): void { for (const [nonce, expiresAt] of this.nonces) { if (expiresAt <= at) this.nonces.delete(nonce); } } } /** * 多进程共享的一次性 nonce 后端。文件必须位于所有实例可见的共享文件系统上; * 对象存储、Redis 或数据库部署可通过 `setTrustedContextReplayBackend` 注入原子实现。 */ export class SharedFileTrustedContextReplayBackend implements TrustedContextReplayBackend { constructor(private readonly filePath: string) { if (!path.isAbsolute(filePath)) { throw new Error('SOMA_TRUSTED_CONTEXT_REPLAY_STORE 必须是绝对路径'); } } async claim(nonce: string, expiresAt: number, at: number): Promise { return withRegistryLock(path.dirname(this.filePath), 'trusted-context-replay', () => { const state = this.read(); prune(state, at); const key = createHash('sha256').update(nonce, 'utf8').digest('hex'); if ((state.nonces[key] ?? 0) > at) return false; if (Object.keys(state.nonces).length >= MAX_ACTIVE_NONCES) return false; state.nonces[key] = expiresAt; writeFileAtomic(this.filePath, `${JSON.stringify(state)}\n`); return true; }); } async reset(): Promise { withRegistryLock(path.dirname(this.filePath), 'trusted-context-replay', () => { try { fs.unlinkSync(this.filePath); } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; } }); } private read(): StoredReplayState { try { const parsed = JSON.parse(fs.readFileSync(this.filePath, 'utf8')) as Partial; if (parsed.version === 1 && parsed.nonces && typeof parsed.nonces === 'object') { return { version: 1, nonces: parsed.nonces }; } throw new Error('文件结构无效'); } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') { return { version: 1, nonces: {} }; } throw new Error(`可信上下文防重放文件损坏:${this.filePath}`, { cause: error }); } } } function prune(state: StoredReplayState, at: number): void { for (const [key, expiresAt] of Object.entries(state.nonces)) { if (!Number.isFinite(expiresAt) || expiresAt <= at) delete state.nonces[key]; } }