@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
110 lines (109 loc) • 3.45 kB
JavaScript
;
/**
* 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;