UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

110 lines (109 loc) 3.45 kB
"use strict"; /** * In-memory nonce cache implementation * * WARNING: This implementation is only suitable for single-instance deployments. * For multi-instance deployments, use Redis, DynamoDB, or Cloudflare KV implementations. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryNonceCache = void 0; /** * In-memory nonce cache with TTL support */ class MemoryNonceCache { cache = new Map(); cleanupInterval; constructor(cleanupIntervalMs = 60000) { // Default: 1 minute cleanup console.warn("⚠️ MemoryNonceCache is not suitable for multi-instance deployments. " + "For production use with multiple instances, configure Redis, DynamoDB, or Cloudflare KV."); // Start periodic cleanup this.cleanupInterval = setInterval(() => { this.cleanup().catch(console.error); }, cleanupIntervalMs); } /** * Check if a nonce exists in the cache */ async has(nonce, agentDid) { const key = agentDid ? `nonce:${agentDid}:${nonce}` : nonce; const entry = this.cache.get(key); if (!entry) { return false; } // Check if expired if (Date.now() > entry.expiresAt) { this.cache.delete(key); return false; } return true; } /** * Add a nonce to the cache with TTL * MUST ensure atomic add-if-absent semantics for replay prevention */ async add(nonce, ttlSeconds, agentDid) { const key = agentDid ? `nonce:${agentDid}:${nonce}` : nonce; // Check if nonce already exists (atomic check-and-set) const existing = this.cache.get(key); if (existing && Date.now() <= existing.expiresAt) { throw new Error(`Nonce ${nonce} already exists - potential replay attack`); } // Handle zero or negative TTL - set expiration to past time const expiresAt = ttlSeconds <= 0 ? Date.now() - 1 : Date.now() + ttlSeconds * 1000; const entry = { sessionId: `mem_${Date.now()}_${Math.random().toString(36).substring(2)}`, expiresAt, }; this.cache.set(key, entry); } /** * Clean up expired entries * Safe to call frequently and should be no-op for backends that auto-expire */ async cleanup() { const now = Date.now(); const expiredKeys = []; for (const [nonce, entry] of this.cache.entries()) { if (now > entry.expiresAt) { expiredKeys.push(nonce); } } for (const key of expiredKeys) { this.cache.delete(key); } } /** * Get cache statistics */ getStats() { const now = Date.now(); let expired = 0; for (const entry of this.cache.values()) { if (now > entry.expiresAt) { expired++; } } return { size: this.cache.size, expired, }; } /** * Clear all entries (useful for testing) */ clear() { this.cache.clear(); } /** * Destroy the cache and stop cleanup interval */ destroy() { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = undefined; } this.cache.clear(); } } exports.MemoryNonceCache = MemoryNonceCache;