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