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