@kya-os/mcp-i 1.5.6-canary.2 → 1.5.8-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/cloudflare-kv-nonce-cache.d.ts +20 -3
- package/dist/cache/cloudflare-kv-nonce-cache.js +16 -11
- package/dist/cache/cloudflare-kv.d.ts +5 -3
- package/dist/cache/cloudflare-kv.js +6 -4
- package/dist/cache/dynamodb-nonce-cache.d.ts +10 -3
- package/dist/cache/dynamodb-nonce-cache.js +31 -15
- package/dist/cache/memory-nonce-cache.d.ts +2 -2
- package/dist/cache/memory-nonce-cache.js +8 -6
- package/dist/cache/nonce-cache-factory.js +11 -8
- package/dist/cache/redis-nonce-cache.d.ts +2 -2
- package/dist/cache/redis-nonce-cache.js +8 -5
- package/dist/runtime/adapter-express.js +1 -1
- package/dist/runtime/adapter-nextjs.js +1 -1
- package/dist/runtime/delegation-verifier-agentshield.d.ts +1 -0
- package/dist/runtime/delegation-verifier-agentshield.js +15 -59
- package/dist/runtime/http.js +1 -1
- package/dist/runtime/mcpi-runtime-wrapper.d.ts +10 -1
- package/dist/runtime/mcpi-runtime-wrapper.js +40 -0
- package/dist/runtime/session.js +2 -2
- package/dist/runtime/stdio.js +1 -1
- package/package.json +4 -4
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import { NonceCache } from "@kya-os/contracts/handshake";
|
|
2
|
+
/**
|
|
3
|
+
* Cloudflare KV namespace interface
|
|
4
|
+
*/
|
|
5
|
+
interface KVNamespace {
|
|
6
|
+
get(key: string, options?: {
|
|
7
|
+
type?: string;
|
|
8
|
+
}): Promise<string | null>;
|
|
9
|
+
getWithMetadata?(key: string): Promise<{
|
|
10
|
+
value: string | null;
|
|
11
|
+
metadata?: unknown;
|
|
12
|
+
}>;
|
|
13
|
+
put(key: string, value: string, options?: {
|
|
14
|
+
expirationTtl?: number;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
delete(key: string): Promise<void>;
|
|
17
|
+
}
|
|
2
18
|
/**
|
|
3
19
|
* Cloudflare KV-based nonce cache implementation
|
|
4
20
|
* Suitable for Cloudflare Workers deployments
|
|
@@ -6,9 +22,10 @@ import { NonceCache } from "@kya-os/contracts/handshake";
|
|
|
6
22
|
export declare class CloudflareKVNonceCache implements NonceCache {
|
|
7
23
|
private kv;
|
|
8
24
|
private keyPrefix;
|
|
9
|
-
constructor(kv:
|
|
25
|
+
constructor(kv: KVNamespace, keyPrefix?: string);
|
|
10
26
|
private getKey;
|
|
11
|
-
has(nonce: string): Promise<boolean>;
|
|
12
|
-
add(nonce: string, ttl: number): Promise<void>;
|
|
27
|
+
has(nonce: string, agentDid?: string): Promise<boolean>;
|
|
28
|
+
add(nonce: string, ttl: number, agentDid?: string): Promise<void>;
|
|
13
29
|
cleanup(): Promise<void>;
|
|
14
30
|
}
|
|
31
|
+
export {};
|
|
@@ -6,17 +6,20 @@ exports.CloudflareKVNonceCache = void 0;
|
|
|
6
6
|
* Suitable for Cloudflare Workers deployments
|
|
7
7
|
*/
|
|
8
8
|
class CloudflareKVNonceCache {
|
|
9
|
-
kv;
|
|
9
|
+
kv;
|
|
10
10
|
keyPrefix;
|
|
11
11
|
constructor(kv, keyPrefix = "nonce:") {
|
|
12
12
|
this.kv = kv;
|
|
13
13
|
this.keyPrefix = keyPrefix;
|
|
14
14
|
}
|
|
15
|
-
getKey(nonce) {
|
|
15
|
+
getKey(nonce, agentDid) {
|
|
16
|
+
if (agentDid) {
|
|
17
|
+
return `${this.keyPrefix}${agentDid}:${nonce}`;
|
|
18
|
+
}
|
|
16
19
|
return `${this.keyPrefix}${nonce}`;
|
|
17
20
|
}
|
|
18
|
-
async has(nonce) {
|
|
19
|
-
const key = this.getKey(nonce);
|
|
21
|
+
async has(nonce, agentDid) {
|
|
22
|
+
const key = this.getKey(nonce, agentDid);
|
|
20
23
|
const value = await this.kv.get(key);
|
|
21
24
|
if (!value) {
|
|
22
25
|
return false;
|
|
@@ -37,8 +40,8 @@ class CloudflareKVNonceCache {
|
|
|
37
40
|
return false;
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
|
-
async add(nonce, ttl) {
|
|
41
|
-
const key = this.getKey(nonce);
|
|
43
|
+
async add(nonce, ttl, agentDid) {
|
|
44
|
+
const key = this.getKey(nonce, agentDid);
|
|
42
45
|
const expiresAt = Date.now() + ttl * 1000;
|
|
43
46
|
const data = {
|
|
44
47
|
nonce,
|
|
@@ -48,8 +51,10 @@ class CloudflareKVNonceCache {
|
|
|
48
51
|
// Cloudflare KV doesn't have native atomic add-if-absent
|
|
49
52
|
// We implement a best-effort approach with metadata checking
|
|
50
53
|
try {
|
|
51
|
-
// First, try to get the existing value with metadata
|
|
52
|
-
const existingWithMetadata =
|
|
54
|
+
// First, try to get the existing value with metadata (if available)
|
|
55
|
+
const existingWithMetadata = this.kv.getWithMetadata
|
|
56
|
+
? await this.kv.getWithMetadata(key)
|
|
57
|
+
: null;
|
|
53
58
|
if (existingWithMetadata && existingWithMetadata.value !== null) {
|
|
54
59
|
// Key exists, check if it's still valid
|
|
55
60
|
const existingData = JSON.parse(existingWithMetadata.value);
|
|
@@ -65,13 +70,13 @@ class CloudflareKVNonceCache {
|
|
|
65
70
|
}
|
|
66
71
|
catch (error) {
|
|
67
72
|
// If this is a replay attack error, always rethrow it
|
|
68
|
-
if (error.message?.includes("potential replay attack")) {
|
|
73
|
+
if (error instanceof Error && error.message?.includes("potential replay attack")) {
|
|
69
74
|
throw error;
|
|
70
75
|
}
|
|
71
76
|
// If getWithMetadata is not available, fall back to basic approach
|
|
72
|
-
if (error.message?.includes("getWithMetadata")) {
|
|
77
|
+
if (error instanceof Error && error.message?.includes("getWithMetadata")) {
|
|
73
78
|
// Check if already exists first (less atomic but still functional)
|
|
74
|
-
if (await this.has(nonce)) {
|
|
79
|
+
if (await this.has(nonce, agentDid)) {
|
|
75
80
|
throw new Error(`Nonce ${nonce} already exists - potential replay attack`);
|
|
76
81
|
}
|
|
77
82
|
// Store with KV TTL as backup (convert to seconds)
|
|
@@ -11,7 +11,7 @@ import type { NonceCache } from "@kya-os/contracts/handshake";
|
|
|
11
11
|
export interface KVNamespace {
|
|
12
12
|
get(key: string, options?: {
|
|
13
13
|
type?: "text" | "json" | "arrayBuffer" | "stream";
|
|
14
|
-
}): Promise<
|
|
14
|
+
}): Promise<string | null>;
|
|
15
15
|
put(key: string, value: string | ArrayBuffer | ReadableStream, options?: {
|
|
16
16
|
expiration?: number;
|
|
17
17
|
expirationTtl?: number;
|
|
@@ -71,17 +71,19 @@ export declare class CloudflareKVNonceCache implements NonceCache {
|
|
|
71
71
|
* Check if a nonce exists in the cache
|
|
72
72
|
*
|
|
73
73
|
* @param nonce - The nonce to check
|
|
74
|
+
* @param agentDid - Optional agent DID for agent-scoped nonces (prevents cross-agent replay attacks)
|
|
74
75
|
* @returns Promise<boolean> - true if exists, false if not
|
|
75
76
|
*/
|
|
76
|
-
has(nonce: string): Promise<boolean>;
|
|
77
|
+
has(nonce: string, agentDid?: string): Promise<boolean>;
|
|
77
78
|
/**
|
|
78
79
|
* Add a nonce to the cache with TTL
|
|
79
80
|
* Implements atomic add-if-absent semantics for replay prevention
|
|
80
81
|
*
|
|
81
82
|
* @param nonce - The nonce to add
|
|
82
83
|
* @param ttl - Time-to-live in seconds
|
|
84
|
+
* @param agentDid - Optional agent DID for agent-scoped nonces (prevents cross-agent replay attacks)
|
|
83
85
|
*/
|
|
84
|
-
add(nonce: string, ttl: number): Promise<void>;
|
|
86
|
+
add(nonce: string, ttl: number, agentDid?: string): Promise<void>;
|
|
85
87
|
/**
|
|
86
88
|
* Cleanup expired nonces
|
|
87
89
|
* Note: Cloudflare KV handles expiration automatically via TTL
|
|
@@ -44,10 +44,11 @@ class CloudflareKVNonceCache {
|
|
|
44
44
|
* Check if a nonce exists in the cache
|
|
45
45
|
*
|
|
46
46
|
* @param nonce - The nonce to check
|
|
47
|
+
* @param agentDid - Optional agent DID for agent-scoped nonces (prevents cross-agent replay attacks)
|
|
47
48
|
* @returns Promise<boolean> - true if exists, false if not
|
|
48
49
|
*/
|
|
49
|
-
async has(nonce) {
|
|
50
|
-
const key = this.keyPrefix + nonce;
|
|
50
|
+
async has(nonce, agentDid) {
|
|
51
|
+
const key = agentDid ? `${this.keyPrefix}${agentDid}:${nonce}` : this.keyPrefix + nonce;
|
|
51
52
|
try {
|
|
52
53
|
const existing = await this.kv.get(key, { type: "text" });
|
|
53
54
|
return existing !== null;
|
|
@@ -64,9 +65,10 @@ class CloudflareKVNonceCache {
|
|
|
64
65
|
*
|
|
65
66
|
* @param nonce - The nonce to add
|
|
66
67
|
* @param ttl - Time-to-live in seconds
|
|
68
|
+
* @param agentDid - Optional agent DID for agent-scoped nonces (prevents cross-agent replay attacks)
|
|
67
69
|
*/
|
|
68
|
-
async add(nonce, ttl) {
|
|
69
|
-
const key = this.keyPrefix + nonce;
|
|
70
|
+
async add(nonce, ttl, agentDid) {
|
|
71
|
+
const key = agentDid ? `${this.keyPrefix}${agentDid}:${nonce}` : this.keyPrefix + nonce;
|
|
70
72
|
try {
|
|
71
73
|
// Check if nonce already exists
|
|
72
74
|
const existing = await this.kv.get(key, { type: "text" });
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { NonceCache } from "@kya-os/contracts/handshake";
|
|
2
|
+
/**
|
|
3
|
+
* DynamoDB client interface for SDK v3
|
|
4
|
+
* Using 'unknown' for the client type to work with any AWS SDK version
|
|
5
|
+
*/
|
|
6
|
+
type DynamoDBClient = any;
|
|
2
7
|
/**
|
|
3
8
|
* DynamoDB-based nonce cache implementation
|
|
4
9
|
* Suitable for AWS Lambda deployments
|
|
@@ -8,8 +13,10 @@ export declare class DynamoNonceCache implements NonceCache {
|
|
|
8
13
|
private tableName;
|
|
9
14
|
private keyAttribute;
|
|
10
15
|
private ttlAttribute;
|
|
11
|
-
constructor(dynamodb:
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
constructor(dynamodb: DynamoDBClient, tableName: string, keyAttribute?: string, ttlAttribute?: string);
|
|
17
|
+
private getKey;
|
|
18
|
+
has(nonce: string, agentDid?: string): Promise<boolean>;
|
|
19
|
+
add(nonce: string, ttl: number, agentDid?: string): Promise<void>;
|
|
14
20
|
cleanup(): Promise<void>;
|
|
15
21
|
}
|
|
22
|
+
export {};
|
|
@@ -6,7 +6,7 @@ exports.DynamoNonceCache = void 0;
|
|
|
6
6
|
* Suitable for AWS Lambda deployments
|
|
7
7
|
*/
|
|
8
8
|
class DynamoNonceCache {
|
|
9
|
-
dynamodb;
|
|
9
|
+
dynamodb;
|
|
10
10
|
tableName;
|
|
11
11
|
keyAttribute;
|
|
12
12
|
ttlAttribute;
|
|
@@ -16,13 +16,20 @@ class DynamoNonceCache {
|
|
|
16
16
|
this.keyAttribute = keyAttribute;
|
|
17
17
|
this.ttlAttribute = ttlAttribute;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
getKey(nonce, agentDid) {
|
|
20
|
+
if (agentDid) {
|
|
21
|
+
return `nonce:${agentDid}:${nonce}`;
|
|
22
|
+
}
|
|
23
|
+
return nonce;
|
|
24
|
+
}
|
|
25
|
+
async has(nonce, agentDid) {
|
|
20
26
|
try {
|
|
27
|
+
const key = this.getKey(nonce, agentDid);
|
|
21
28
|
const result = await this.dynamodb
|
|
22
29
|
.getItem({
|
|
23
30
|
TableName: this.tableName,
|
|
24
31
|
Key: {
|
|
25
|
-
[this.keyAttribute]: { S:
|
|
32
|
+
[this.keyAttribute]: { S: key },
|
|
26
33
|
},
|
|
27
34
|
ConsistentRead: true, // Ensure we get the latest data
|
|
28
35
|
})
|
|
@@ -31,7 +38,11 @@ class DynamoNonceCache {
|
|
|
31
38
|
return false;
|
|
32
39
|
}
|
|
33
40
|
// Check if expired (DynamoDB TTL might not have cleaned up yet)
|
|
34
|
-
const
|
|
41
|
+
const ttlValue = result.Item[this.ttlAttribute]?.N;
|
|
42
|
+
if (!ttlValue) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const expiresAt = parseInt(ttlValue);
|
|
35
46
|
if (Date.now() / 1000 > expiresAt) {
|
|
36
47
|
return false;
|
|
37
48
|
}
|
|
@@ -40,17 +51,20 @@ class DynamoNonceCache {
|
|
|
40
51
|
catch (error) {
|
|
41
52
|
// Log error but don't expose internal details
|
|
42
53
|
console.error("DynamoDB has() operation failed:", error);
|
|
54
|
+
// Type guard for AWS SDK errors
|
|
55
|
+
const awsError = error;
|
|
43
56
|
// For certain errors, we can safely return false
|
|
44
|
-
if (
|
|
45
|
-
|
|
57
|
+
if (awsError.code === "ResourceNotFoundException" ||
|
|
58
|
+
awsError.code === "ItemCollectionSizeLimitExceededException") {
|
|
46
59
|
return false;
|
|
47
60
|
}
|
|
48
61
|
// For other errors, we should throw to avoid security issues
|
|
49
|
-
throw new Error(`Failed to check nonce existence: ${
|
|
62
|
+
throw new Error(`Failed to check nonce existence: ${awsError.code || awsError.message || "Unknown error"}`);
|
|
50
63
|
}
|
|
51
64
|
}
|
|
52
|
-
async add(nonce, ttl) {
|
|
65
|
+
async add(nonce, ttl, agentDid) {
|
|
53
66
|
const expiresAt = Math.floor(Date.now() / 1000) + ttl;
|
|
67
|
+
const key = this.getKey(nonce, agentDid);
|
|
54
68
|
try {
|
|
55
69
|
// Use conditional write to ensure atomic add-if-absent
|
|
56
70
|
// This is truly atomic in DynamoDB
|
|
@@ -58,7 +72,7 @@ class DynamoNonceCache {
|
|
|
58
72
|
.putItem({
|
|
59
73
|
TableName: this.tableName,
|
|
60
74
|
Item: {
|
|
61
|
-
[this.keyAttribute]: { S:
|
|
75
|
+
[this.keyAttribute]: { S: key },
|
|
62
76
|
[this.ttlAttribute]: { N: expiresAt.toString() },
|
|
63
77
|
createdAt: { N: Math.floor(Date.now() / 1000).toString() },
|
|
64
78
|
},
|
|
@@ -67,21 +81,23 @@ class DynamoNonceCache {
|
|
|
67
81
|
.promise();
|
|
68
82
|
}
|
|
69
83
|
catch (error) {
|
|
70
|
-
|
|
84
|
+
// Type guard for AWS SDK errors
|
|
85
|
+
const awsError = error;
|
|
86
|
+
if (awsError.code === "ConditionalCheckFailedException") {
|
|
71
87
|
throw new Error(`Nonce ${nonce} already exists - potential replay attack`);
|
|
72
88
|
}
|
|
73
89
|
console.error("DynamoDB add() operation failed:", error);
|
|
74
90
|
// Provide more specific error messages for common issues
|
|
75
|
-
if (
|
|
91
|
+
if (awsError.code === "ResourceNotFoundException") {
|
|
76
92
|
throw new Error(`DynamoDB table ${this.tableName} not found`);
|
|
77
93
|
}
|
|
78
|
-
else if (
|
|
79
|
-
throw new Error(`Invalid DynamoDB operation: ${
|
|
94
|
+
else if (awsError.code === "ValidationException") {
|
|
95
|
+
throw new Error(`Invalid DynamoDB operation: ${awsError.message || "Unknown validation error"}`);
|
|
80
96
|
}
|
|
81
|
-
else if (
|
|
97
|
+
else if (awsError.code === "ProvisionedThroughputExceededException") {
|
|
82
98
|
throw new Error("DynamoDB throughput exceeded - consider increasing capacity");
|
|
83
99
|
}
|
|
84
|
-
throw new Error(`Failed to add nonce to cache: ${
|
|
100
|
+
throw new Error(`Failed to add nonce to cache: ${awsError.code || awsError.message || "Unknown error"}`);
|
|
85
101
|
}
|
|
86
102
|
}
|
|
87
103
|
async cleanup() {
|
|
@@ -15,12 +15,12 @@ export declare class MemoryNonceCache implements NonceCache {
|
|
|
15
15
|
/**
|
|
16
16
|
* Check if a nonce exists in the cache
|
|
17
17
|
*/
|
|
18
|
-
has(nonce: string): Promise<boolean>;
|
|
18
|
+
has(nonce: string, agentDid?: string): Promise<boolean>;
|
|
19
19
|
/**
|
|
20
20
|
* Add a nonce to the cache with TTL
|
|
21
21
|
* MUST ensure atomic add-if-absent semantics for replay prevention
|
|
22
22
|
*/
|
|
23
|
-
add(nonce: string, ttlSeconds: number): Promise<void>;
|
|
23
|
+
add(nonce: string, ttlSeconds: number, agentDid?: string): Promise<void>;
|
|
24
24
|
/**
|
|
25
25
|
* Clean up expired entries
|
|
26
26
|
* Safe to call frequently and should be no-op for backends that auto-expire
|
|
@@ -25,14 +25,15 @@ class MemoryNonceCache {
|
|
|
25
25
|
/**
|
|
26
26
|
* Check if a nonce exists in the cache
|
|
27
27
|
*/
|
|
28
|
-
async has(nonce) {
|
|
29
|
-
const
|
|
28
|
+
async has(nonce, agentDid) {
|
|
29
|
+
const key = agentDid ? `nonce:${agentDid}:${nonce}` : nonce;
|
|
30
|
+
const entry = this.cache.get(key);
|
|
30
31
|
if (!entry) {
|
|
31
32
|
return false;
|
|
32
33
|
}
|
|
33
34
|
// Check if expired
|
|
34
35
|
if (Date.now() > entry.expiresAt) {
|
|
35
|
-
this.cache.delete(
|
|
36
|
+
this.cache.delete(key);
|
|
36
37
|
return false;
|
|
37
38
|
}
|
|
38
39
|
return true;
|
|
@@ -41,9 +42,10 @@ class MemoryNonceCache {
|
|
|
41
42
|
* Add a nonce to the cache with TTL
|
|
42
43
|
* MUST ensure atomic add-if-absent semantics for replay prevention
|
|
43
44
|
*/
|
|
44
|
-
async add(nonce, ttlSeconds) {
|
|
45
|
+
async add(nonce, ttlSeconds, agentDid) {
|
|
46
|
+
const key = agentDid ? `nonce:${agentDid}:${nonce}` : nonce;
|
|
45
47
|
// Check if nonce already exists (atomic check-and-set)
|
|
46
|
-
const existing = this.cache.get(
|
|
48
|
+
const existing = this.cache.get(key);
|
|
47
49
|
if (existing && Date.now() <= existing.expiresAt) {
|
|
48
50
|
throw new Error(`Nonce ${nonce} already exists - potential replay attack`);
|
|
49
51
|
}
|
|
@@ -53,7 +55,7 @@ class MemoryNonceCache {
|
|
|
53
55
|
sessionId: `mem_${Date.now()}_${Math.random().toString(36).substring(2)}`,
|
|
54
56
|
expiresAt,
|
|
55
57
|
};
|
|
56
|
-
this.cache.set(
|
|
58
|
+
this.cache.set(key, entry);
|
|
57
59
|
}
|
|
58
60
|
/**
|
|
59
61
|
* Clean up expired entries
|
|
@@ -123,11 +123,13 @@ async function createNonceCache(config, options) {
|
|
|
123
123
|
try {
|
|
124
124
|
// Dynamic import to avoid bundling AWS SDK in environments that don't need it
|
|
125
125
|
const awsSdk = await import("@aws-sdk/client-dynamodb");
|
|
126
|
-
|
|
127
|
-
const DynamoDBClient =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
const sdkModule = awsSdk;
|
|
127
|
+
const DynamoDBClient = "DynamoDBClient" in sdkModule
|
|
128
|
+
? sdkModule.DynamoDBClient
|
|
129
|
+
: sdkModule.default.DynamoDBClient;
|
|
130
|
+
const DescribeTableCommand = "DescribeTableCommand" in sdkModule
|
|
131
|
+
? sdkModule.DescribeTableCommand
|
|
132
|
+
: sdkModule.default.DescribeTableCommand;
|
|
131
133
|
const region = config?.dynamodb?.region ||
|
|
132
134
|
process.env.AWS_REGION ||
|
|
133
135
|
process.env.AWS_DEFAULT_REGION ||
|
|
@@ -179,13 +181,14 @@ async function createNonceCache(config, options) {
|
|
|
179
181
|
// Verify KV namespace has required methods
|
|
180
182
|
const kvInterface = kv;
|
|
181
183
|
if (typeof kvInterface.get !== "function" ||
|
|
182
|
-
typeof kvInterface.put !== "function"
|
|
184
|
+
typeof kvInterface.put !== "function" ||
|
|
185
|
+
typeof kvInterface.delete !== "function") {
|
|
183
186
|
throw new Error(`KV namespace '${namespace}' does not have required methods`);
|
|
184
187
|
}
|
|
185
188
|
// Test basic KV operation (this will be very fast in Workers)
|
|
186
189
|
try {
|
|
187
|
-
const
|
|
188
|
-
await
|
|
190
|
+
const kvTest = kv;
|
|
191
|
+
await kvTest.get("__mcpi_test_key__");
|
|
189
192
|
}
|
|
190
193
|
catch (kvError) {
|
|
191
194
|
const error = kvError;
|
|
@@ -8,7 +8,7 @@ export declare class RedisNonceCache implements NonceCache {
|
|
|
8
8
|
private keyPrefix;
|
|
9
9
|
constructor(redis: any, keyPrefix?: string);
|
|
10
10
|
private getKey;
|
|
11
|
-
has(nonce: string): Promise<boolean>;
|
|
12
|
-
add(nonce: string, ttl: number): Promise<void>;
|
|
11
|
+
has(nonce: string, agentDid?: string): Promise<boolean>;
|
|
12
|
+
add(nonce: string, ttl: number, agentDid?: string): Promise<void>;
|
|
13
13
|
cleanup(): Promise<void>;
|
|
14
14
|
}
|
|
@@ -12,12 +12,15 @@ class RedisNonceCache {
|
|
|
12
12
|
this.redis = redis;
|
|
13
13
|
this.keyPrefix = keyPrefix;
|
|
14
14
|
}
|
|
15
|
-
getKey(nonce) {
|
|
15
|
+
getKey(nonce, agentDid) {
|
|
16
|
+
if (agentDid) {
|
|
17
|
+
return `${this.keyPrefix}${agentDid}:${nonce}`;
|
|
18
|
+
}
|
|
16
19
|
return `${this.keyPrefix}${nonce}`;
|
|
17
20
|
}
|
|
18
|
-
async has(nonce) {
|
|
21
|
+
async has(nonce, agentDid) {
|
|
19
22
|
try {
|
|
20
|
-
const key = this.getKey(nonce);
|
|
23
|
+
const key = this.getKey(nonce, agentDid);
|
|
21
24
|
const exists = await this.redis.exists(key);
|
|
22
25
|
return exists === 1;
|
|
23
26
|
}
|
|
@@ -26,9 +29,9 @@ class RedisNonceCache {
|
|
|
26
29
|
throw new Error(`Failed to check nonce existence: ${error}`);
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
|
-
async add(nonce, ttl) {
|
|
32
|
+
async add(nonce, ttl, agentDid) {
|
|
30
33
|
try {
|
|
31
|
-
const key = this.getKey(nonce);
|
|
34
|
+
const key = this.getKey(nonce, agentDid);
|
|
32
35
|
// Use SET with NX (not exists) and EX (expiry) for atomic add-if-absent
|
|
33
36
|
// This is truly atomic in Redis
|
|
34
37
|
const result = await this.redis.set(key, "1", "EX", ttl, "NX");
|