UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

98 lines (97 loc) 3.65 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CloudflareKVNonceCache = void 0; /** * Cloudflare KV-based nonce cache implementation * Suitable for Cloudflare Workers deployments */ class CloudflareKVNonceCache { kv; keyPrefix; constructor(kv, keyPrefix = "nonce:") { this.kv = kv; this.keyPrefix = keyPrefix; } getKey(nonce, agentDid) { if (agentDid) { return `${this.keyPrefix}${agentDid}:${nonce}`; } return `${this.keyPrefix}${nonce}`; } async has(nonce, agentDid) { const key = this.getKey(nonce, agentDid); const value = await this.kv.get(key); if (!value) { return false; } // Parse the stored data to check expiry try { const data = JSON.parse(value); if (Date.now() > data.expiresAt) { // Clean up expired entry await this.kv.delete(key); return false; } return true; } catch { // If we can't parse, assume expired and clean up await this.kv.delete(key); return false; } } async add(nonce, ttl, agentDid) { const key = this.getKey(nonce, agentDid); const expiresAt = Date.now() + ttl * 1000; const data = { nonce, expiresAt, createdAt: Date.now(), }; // Cloudflare KV doesn't have native atomic add-if-absent // We implement a best-effort approach with metadata checking try { // First, try to get the existing value with metadata (if available) const existingWithMetadata = this.kv.getWithMetadata ? await this.kv.getWithMetadata(key) : null; if (existingWithMetadata && existingWithMetadata.value !== null) { // Key exists, check if it's still valid const existingData = JSON.parse(existingWithMetadata.value); if (Date.now() <= existingData.expiresAt) { throw new Error(`Nonce ${nonce} already exists - potential replay attack`); } // If expired, we can proceed to overwrite } // Store with KV TTL as backup (convert to seconds) await this.kv.put(key, JSON.stringify(data), { expirationTtl: ttl, }); } catch (error) { // If this is a replay attack error, always rethrow it if (error instanceof Error && error.message?.includes("potential replay attack")) { throw error; } // If getWithMetadata is not available, fall back to basic approach if (error instanceof Error && error.message?.includes("getWithMetadata")) { // Check if already exists first (less atomic but still functional) if (await this.has(nonce, agentDid)) { throw new Error(`Nonce ${nonce} already exists - potential replay attack`); } // Store with KV TTL as backup (convert to seconds) await this.kv.put(key, JSON.stringify(data), { expirationTtl: ttl, }); } else { throw error; } } } async cleanup() { // Cloudflare KV handles expiry automatically via TTL, so this is a no-op // This method exists to satisfy the interface } } exports.CloudflareKVNonceCache = CloudflareKVNonceCache;