UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

109 lines (108 loc) 4.34 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DynamoNonceCache = void 0; /** * DynamoDB-based nonce cache implementation * Suitable for AWS Lambda deployments */ class DynamoNonceCache { dynamodb; tableName; keyAttribute; ttlAttribute; constructor(dynamodb, tableName, keyAttribute = "nonce", ttlAttribute = "expiresAt") { this.dynamodb = dynamodb; this.tableName = tableName; this.keyAttribute = keyAttribute; this.ttlAttribute = ttlAttribute; } getKey(nonce, agentDid) { if (agentDid) { return `nonce:${agentDid}:${nonce}`; } return nonce; } async has(nonce, agentDid) { try { const key = this.getKey(nonce, agentDid); const result = await this.dynamodb .getItem({ TableName: this.tableName, Key: { [this.keyAttribute]: { S: key }, }, ConsistentRead: true, // Ensure we get the latest data }) .promise(); if (!result.Item) { return false; } // Check if expired (DynamoDB TTL might not have cleaned up yet) const ttlValue = result.Item[this.ttlAttribute]?.N; if (!ttlValue) { return false; } const expiresAt = parseInt(ttlValue); if (Date.now() / 1000 > expiresAt) { return false; } return true; } catch (error) { // Log error but don't expose internal details console.error("DynamoDB has() operation failed:", error); // Type guard for AWS SDK errors const awsError = error; // For certain errors, we can safely return false if (awsError.code === "ResourceNotFoundException" || awsError.code === "ItemCollectionSizeLimitExceededException") { return false; } // For other errors, we should throw to avoid security issues throw new Error(`Failed to check nonce existence: ${awsError.code || awsError.message || "Unknown error"}`); } } async add(nonce, ttl, agentDid) { const expiresAt = Math.floor(Date.now() / 1000) + ttl; const key = this.getKey(nonce, agentDid); try { // Use conditional write to ensure atomic add-if-absent // This is truly atomic in DynamoDB await this.dynamodb .putItem({ TableName: this.tableName, Item: { [this.keyAttribute]: { S: key }, [this.ttlAttribute]: { N: expiresAt.toString() }, createdAt: { N: Math.floor(Date.now() / 1000).toString() }, }, ConditionExpression: `attribute_not_exists(${this.keyAttribute})`, }) .promise(); } catch (error) { // Type guard for AWS SDK errors const awsError = error; if (awsError.code === "ConditionalCheckFailedException") { throw new Error(`Nonce ${nonce} already exists - potential replay attack`); } console.error("DynamoDB add() operation failed:", error); // Provide more specific error messages for common issues if (awsError.code === "ResourceNotFoundException") { throw new Error(`DynamoDB table ${this.tableName} not found`); } else if (awsError.code === "ValidationException") { throw new Error(`Invalid DynamoDB operation: ${awsError.message || "Unknown validation error"}`); } else if (awsError.code === "ProvisionedThroughputExceededException") { throw new Error("DynamoDB throughput exceeded - consider increasing capacity"); } throw new Error(`Failed to add nonce to cache: ${awsError.code || awsError.message || "Unknown error"}`); } } async cleanup() { // DynamoDB handles TTL cleanup automatically, so this is a no-op // This method exists to satisfy the interface } } exports.DynamoNonceCache = DynamoNonceCache;