@kya-os/mcp-i 1.2.2 → 1.2.3

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.
Files changed (42) hide show
  1. package/dist/compiler/get-webpack-config/get-externals.js +2 -2
  2. package/dist/compiler/get-webpack-config/plugins.js +1 -13
  3. package/dist/runtime/session.js +4 -2
  4. package/dist/storage/encryption.d.ts +61 -0
  5. package/dist/storage/encryption.js +151 -0
  6. package/dist/storage/index.d.ts +11 -0
  7. package/dist/storage/index.js +26 -0
  8. package/package.json +2 -2
  9. package/dist/cache/__tests__/cloudflare-kv-nonce-cache.test.d.ts +0 -4
  10. package/dist/cache/__tests__/cloudflare-kv-nonce-cache.test.js +0 -176
  11. package/dist/cache/__tests__/concurrency.test.d.ts +0 -5
  12. package/dist/cache/__tests__/concurrency.test.js +0 -300
  13. package/dist/cache/__tests__/dynamodb-nonce-cache.test.d.ts +0 -4
  14. package/dist/cache/__tests__/dynamodb-nonce-cache.test.js +0 -176
  15. package/dist/cache/__tests__/memory-nonce-cache.test.d.ts +0 -4
  16. package/dist/cache/__tests__/memory-nonce-cache.test.js +0 -132
  17. package/dist/cache/__tests__/nonce-cache-factory-simple.test.d.ts +0 -4
  18. package/dist/cache/__tests__/nonce-cache-factory-simple.test.js +0 -133
  19. package/dist/cache/__tests__/nonce-cache-factory.test.d.ts +0 -4
  20. package/dist/cache/__tests__/nonce-cache-factory.test.js +0 -252
  21. package/dist/cache/__tests__/redis-nonce-cache.test.d.ts +0 -4
  22. package/dist/cache/__tests__/redis-nonce-cache.test.js +0 -95
  23. package/dist/runtime/__tests__/audit.test.d.ts +0 -4
  24. package/dist/runtime/__tests__/audit.test.js +0 -328
  25. package/dist/runtime/__tests__/identity.test.d.ts +0 -4
  26. package/dist/runtime/__tests__/identity.test.js +0 -164
  27. package/dist/runtime/__tests__/mcpi-runtime.test.d.ts +0 -4
  28. package/dist/runtime/__tests__/mcpi-runtime.test.js +0 -372
  29. package/dist/runtime/__tests__/proof.test.d.ts +0 -4
  30. package/dist/runtime/__tests__/proof.test.js +0 -302
  31. package/dist/runtime/__tests__/session.test.d.ts +0 -4
  32. package/dist/runtime/__tests__/session.test.js +0 -254
  33. package/dist/runtime/__tests__/well-known.test.d.ts +0 -4
  34. package/dist/runtime/__tests__/well-known.test.js +0 -312
  35. package/dist/test/__tests__/nonce-cache-integration.test.d.ts +0 -1
  36. package/dist/test/__tests__/nonce-cache-integration.test.js +0 -116
  37. package/dist/test/__tests__/nonce-cache.test.d.ts +0 -1
  38. package/dist/test/__tests__/nonce-cache.test.js +0 -122
  39. package/dist/test/__tests__/runtime-integration.test.d.ts +0 -4
  40. package/dist/test/__tests__/runtime-integration.test.js +0 -192
  41. package/dist/test/__tests__/test-infrastructure.test.d.ts +0 -4
  42. package/dist/test/__tests__/test-infrastructure.test.js +0 -178
@@ -70,8 +70,8 @@ function getExternals() {
70
70
  }
71
71
  let pathRequest = request;
72
72
  /**
73
- * Paths are relative to the .xmcp/nextjs-adapter folder,
74
- * but we are building in .xmcp/adapter/index.js, so we need to go up 2 levels
73
+ * Paths are relative to the .mcpi/nextjs-adapter folder,
74
+ * but we are building in .mcpi/adapter/index.js, so we need to go up 2 levels
75
75
  */
76
76
  if (request.startsWith("../")) {
77
77
  // Only replace the import if it hasn't been replaced yet
@@ -12,7 +12,6 @@ const compiler_context_1 = require("../compiler-context");
12
12
  const getDefaultRuntimeFiles = () => {
13
13
  const path = require("path");
14
14
  const fs = require("fs");
15
- console.log("getDefaultRuntimeFiles: Starting from __dirname:", __dirname);
16
15
  // Try multiple possible locations for the mcp-i runtime dist directory
17
16
  const possiblePaths = [
18
17
  // When running from compiled dist
@@ -27,17 +26,14 @@ const getDefaultRuntimeFiles = () => {
27
26
  ];
28
27
  let mcpDistPath = "";
29
28
  for (const possiblePath of possiblePaths) {
30
- console.log("getDefaultRuntimeFiles: Checking path:", possiblePath);
31
29
  if (fs.existsSync(possiblePath) &&
32
30
  fs.existsSync(path.join(possiblePath, "http.js"))) {
33
31
  mcpDistPath = possiblePath;
34
- console.log("getDefaultRuntimeFiles: Found valid dist path:", mcpDistPath);
35
32
  break;
36
33
  }
37
34
  }
38
35
  const runtimeFiles = {};
39
36
  if (mcpDistPath) {
40
- console.log("getDefaultRuntimeFiles: Loading runtime files from:", mcpDistPath);
41
37
  // Add known runtime files
42
38
  const knownFiles = [
43
39
  "http.js",
@@ -48,17 +44,11 @@ const getDefaultRuntimeFiles = () => {
48
44
  ];
49
45
  for (const file of knownFiles) {
50
46
  const filePath = path.join(mcpDistPath, file);
51
- console.log("getDefaultRuntimeFiles: Checking for file:", filePath);
52
47
  if (fs.existsSync(filePath)) {
53
- console.log("getDefaultRuntimeFiles: Found file:", file);
54
48
  runtimeFiles[file] = fs.readFileSync(filePath, "utf8");
55
49
  }
56
50
  }
57
51
  }
58
- else {
59
- console.log("getDefaultRuntimeFiles: No valid dist path found");
60
- }
61
- console.log("getDefaultRuntimeFiles: Final runtime files:", Object.keys(runtimeFiles));
62
52
  return runtimeFiles;
63
53
  };
64
54
  exports.runtimeFiles = (typeof RUNTIME_FILES !== "undefined"
@@ -71,10 +61,8 @@ class InjectRuntimePlugin {
71
61
  if (hasRun)
72
62
  return;
73
63
  hasRun = true;
74
- console.log("InjectRuntimePlugin: Found runtime files:", Object.keys(exports.runtimeFiles));
75
64
  for (const [fileName, fileContent] of Object.entries(exports.runtimeFiles)) {
76
65
  const targetPath = path_1.default.join(constants_1.runtimeFolderPath, fileName);
77
- console.log(`InjectRuntimePlugin: Writing ${fileName} to ${targetPath}`);
78
66
  fs_extra_1.default.writeFileSync(targetPath, fileContent);
79
67
  }
80
68
  });
@@ -114,7 +102,7 @@ class CreateTypeDefinitionPlugin {
114
102
  return;
115
103
  hasRun = true;
116
104
  const xmcpConfig = (0, compiler_context_1.getXmcpConfig)();
117
- // Manually type the .xmcp/adapter/index.js file using a .xmcp/adapter/index.d.ts file
105
+ // Manually type the .mcpi/adapter/index.js file using a .mcpi/adapter/index.d.ts file
118
106
  // TO DO add withAuth to the type definition & AuthConfig
119
107
  if (xmcpConfig.experimental?.adapter) {
120
108
  let typeDefinitionContent = "";
@@ -25,8 +25,10 @@ class SessionManager {
25
25
  nonceCache: new memory_nonce_cache_1.MemoryNonceCache(),
26
26
  ...config,
27
27
  };
28
- // Warn about multi-instance deployments with memory cache
29
- if (this.config.nonceCache instanceof memory_nonce_cache_1.MemoryNonceCache) {
28
+ // Warn about multi-instance deployments with memory cache (only in production or if explicitly enabled)
29
+ if (this.config.nonceCache instanceof memory_nonce_cache_1.MemoryNonceCache &&
30
+ (process.env.NODE_ENV === "production" ||
31
+ process.env.MCPI_WARN_MEMORY_CACHE === "true")) {
30
32
  console.warn("Warning: Using MemoryNonceCache - not suitable for multi-instance deployments. " +
31
33
  "Consider using Redis, DynamoDB, or Cloudflare KV for production.");
32
34
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Encryption utilities for ktaEncrypted storage mode
3
+ */
4
+ export interface EncryptionResult {
5
+ ciphertext: string;
6
+ nonce: string;
7
+ publicKey: string;
8
+ }
9
+ export interface DecryptionParams {
10
+ ciphertext: string;
11
+ nonce: string;
12
+ publicKey: string;
13
+ privateKey: string;
14
+ }
15
+ /**
16
+ * Audience-key encryption for ktaEncrypted mode
17
+ * Uses X25519 key exchange + ChaCha20-Poly1305 AEAD
18
+ */
19
+ export declare class AudienceKeyEncryption {
20
+ /**
21
+ * Generate a new key pair for encryption
22
+ */
23
+ static generateKeyPair(): Promise<CryptoKeyPair>;
24
+ /**
25
+ * Encrypt data for a specific audience public key
26
+ */
27
+ static encrypt(data: string, audiencePublicKey: string): Promise<EncryptionResult>;
28
+ /**
29
+ * Decrypt data using private key
30
+ */
31
+ static decrypt(params: DecryptionParams): Promise<string>;
32
+ /**
33
+ * Import public key from base64 string
34
+ */
35
+ private static importPublicKey;
36
+ /**
37
+ * Import private key from base64 string
38
+ */
39
+ private static importPrivateKey;
40
+ /**
41
+ * Export public key to base64 string
42
+ */
43
+ private static exportPublicKey;
44
+ /**
45
+ * Export private key to base64 string
46
+ */
47
+ static exportPrivateKey(privateKey: CryptoKey): Promise<string>;
48
+ }
49
+ /**
50
+ * Simple symmetric encryption for testing
51
+ */
52
+ export declare class SimpleEncryption {
53
+ /**
54
+ * Encrypt data with a password (for testing only)
55
+ */
56
+ static encrypt(data: string, password: string): Promise<string>;
57
+ /**
58
+ * Decrypt data with a password (for testing only)
59
+ */
60
+ static decrypt(encryptedData: string, password: string): Promise<string>;
61
+ }
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SimpleEncryption = exports.AudienceKeyEncryption = void 0;
4
+ const crypto_1 = require("crypto");
5
+ /**
6
+ * Audience-key encryption for ktaEncrypted mode
7
+ * Uses X25519 key exchange + ChaCha20-Poly1305 AEAD
8
+ */
9
+ class AudienceKeyEncryption {
10
+ /**
11
+ * Generate a new key pair for encryption
12
+ */
13
+ static async generateKeyPair() {
14
+ const keyPair = await crypto_1.webcrypto.subtle.generateKey({
15
+ name: "X25519",
16
+ }, true, // extractable
17
+ ["deriveKey"]);
18
+ // Type assertion since we know X25519 returns a CryptoKeyPair
19
+ return keyPair;
20
+ }
21
+ /**
22
+ * Encrypt data for a specific audience public key
23
+ */
24
+ static async encrypt(data, audiencePublicKey) {
25
+ // Generate ephemeral key pair
26
+ const ephemeralKeyPair = await this.generateKeyPair();
27
+ // Import audience public key
28
+ const audiencePubKey = await this.importPublicKey(audiencePublicKey);
29
+ // Derive shared secret
30
+ const sharedSecret = await crypto_1.webcrypto.subtle.deriveKey({
31
+ name: "X25519",
32
+ public: audiencePubKey,
33
+ }, ephemeralKeyPair.privateKey, {
34
+ name: "ChaCha20-Poly1305",
35
+ length: 256,
36
+ }, false, // not extractable
37
+ ["encrypt"]);
38
+ // Generate random nonce
39
+ const nonce = crypto_1.webcrypto.getRandomValues(new Uint8Array(12));
40
+ // Encrypt the data
41
+ const encoder = new TextEncoder();
42
+ const dataBytes = encoder.encode(data);
43
+ const ciphertext = await crypto_1.webcrypto.subtle.encrypt({
44
+ name: "ChaCha20-Poly1305",
45
+ iv: nonce,
46
+ }, sharedSecret, dataBytes);
47
+ // Export ephemeral public key
48
+ const ephemeralPublicKey = await this.exportPublicKey(ephemeralKeyPair.publicKey);
49
+ return {
50
+ ciphertext: Buffer.from(ciphertext).toString("base64"),
51
+ nonce: Buffer.from(nonce).toString("base64"),
52
+ publicKey: ephemeralPublicKey,
53
+ };
54
+ }
55
+ /**
56
+ * Decrypt data using private key
57
+ */
58
+ static async decrypt(params) {
59
+ // Import keys
60
+ const ephemeralPublicKey = await this.importPublicKey(params.publicKey);
61
+ const privateKey = await this.importPrivateKey(params.privateKey);
62
+ // Derive shared secret
63
+ const sharedSecret = await crypto_1.webcrypto.subtle.deriveKey({
64
+ name: "X25519",
65
+ public: ephemeralPublicKey,
66
+ }, privateKey, {
67
+ name: "ChaCha20-Poly1305",
68
+ length: 256,
69
+ }, false, // not extractable
70
+ ["decrypt"]);
71
+ // Decrypt the data
72
+ const ciphertext = Buffer.from(params.ciphertext, "base64");
73
+ const nonce = Buffer.from(params.nonce, "base64");
74
+ const decrypted = await crypto_1.webcrypto.subtle.decrypt({
75
+ name: "ChaCha20-Poly1305",
76
+ iv: nonce,
77
+ }, sharedSecret, ciphertext);
78
+ const decoder = new TextDecoder();
79
+ return decoder.decode(decrypted);
80
+ }
81
+ /**
82
+ * Import public key from base64 string
83
+ */
84
+ static async importPublicKey(publicKey) {
85
+ const keyBytes = Buffer.from(publicKey, "base64");
86
+ return await crypto_1.webcrypto.subtle.importKey("raw", keyBytes, {
87
+ name: "X25519",
88
+ }, false, []);
89
+ }
90
+ /**
91
+ * Import private key from base64 string
92
+ */
93
+ static async importPrivateKey(privateKey) {
94
+ const keyBytes = Buffer.from(privateKey, "base64");
95
+ return await crypto_1.webcrypto.subtle.importKey("raw", keyBytes, {
96
+ name: "X25519",
97
+ }, false, ["deriveKey"]);
98
+ }
99
+ /**
100
+ * Export public key to base64 string
101
+ */
102
+ static async exportPublicKey(publicKey) {
103
+ const keyBytes = await crypto_1.webcrypto.subtle.exportKey("raw", publicKey);
104
+ return Buffer.from(keyBytes).toString("base64");
105
+ }
106
+ /**
107
+ * Export private key to base64 string
108
+ */
109
+ static async exportPrivateKey(privateKey) {
110
+ const keyBytes = await crypto_1.webcrypto.subtle.exportKey("raw", privateKey);
111
+ return Buffer.from(keyBytes).toString("base64");
112
+ }
113
+ }
114
+ exports.AudienceKeyEncryption = AudienceKeyEncryption;
115
+ /**
116
+ * Simple symmetric encryption for testing
117
+ */
118
+ class SimpleEncryption {
119
+ /**
120
+ * Encrypt data with a password (for testing only)
121
+ */
122
+ static async encrypt(data, password) {
123
+ // This is a simple implementation for testing
124
+ // In production, use proper key derivation and authenticated encryption
125
+ const encoder = new TextEncoder();
126
+ const dataBytes = encoder.encode(data);
127
+ const passwordBytes = encoder.encode(password);
128
+ // Simple XOR encryption (NOT secure, for testing only)
129
+ const encrypted = new Uint8Array(dataBytes.length);
130
+ for (let i = 0; i < dataBytes.length; i++) {
131
+ encrypted[i] = dataBytes[i] ^ passwordBytes[i % passwordBytes.length];
132
+ }
133
+ return Buffer.from(encrypted).toString("base64");
134
+ }
135
+ /**
136
+ * Decrypt data with a password (for testing only)
137
+ */
138
+ static async decrypt(encryptedData, password) {
139
+ const encrypted = Buffer.from(encryptedData, "base64");
140
+ const encoder = new TextEncoder();
141
+ const passwordBytes = encoder.encode(password);
142
+ // Simple XOR decryption (NOT secure, for testing only)
143
+ const decrypted = new Uint8Array(encrypted.length);
144
+ for (let i = 0; i < encrypted.length; i++) {
145
+ decrypted[i] = encrypted[i] ^ passwordBytes[i % passwordBytes.length];
146
+ }
147
+ const decoder = new TextDecoder();
148
+ return decoder.decode(decrypted);
149
+ }
150
+ }
151
+ exports.SimpleEncryption = SimpleEncryption;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Storage and receipts module for XMCP-I
3
+ *
4
+ * This module provides storage mode configuration, receipt handling,
5
+ * delegation management, and encryption utilities for verifiable credentials.
6
+ */
7
+ export * from "./config";
8
+ export * from "./delegation";
9
+ export * from "./encryption";
10
+ export * from "./merkle-verifier";
11
+ export type { StorageMode, StorageConfig, Receipt, Delegation, DelegationRequest, DelegationResponse, } from "@kya-os/contracts/registry";
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ /**
3
+ * Storage and receipts module for XMCP-I
4
+ *
5
+ * This module provides storage mode configuration, receipt handling,
6
+ * delegation management, and encryption utilities for verifiable credentials.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
20
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
21
+ };
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ __exportStar(require("./config"), exports);
24
+ __exportStar(require("./delegation"), exports);
25
+ __exportStar(require("./encryption"), exports);
26
+ __exportStar(require("./merkle-verifier"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kya-os/mcp-i",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "The TypeScript MCP framework with identity features built-in",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",
@@ -46,7 +46,7 @@
46
46
  "model-context-protocol"
47
47
  ],
48
48
  "dependencies": {
49
- "@kya-os/contracts": "^1.2.0",
49
+ "@kya-os/contracts": "workspace:*",
50
50
  "@modelcontextprotocol/inspector": "^0.16.6",
51
51
  "@modelcontextprotocol/sdk": "^1.11.4",
52
52
  "@swc/core": "^1.11.24",
@@ -1,4 +0,0 @@
1
- /**
2
- * Tests for Cloudflare KV Nonce Cache
3
- */
4
- export {};
@@ -1,176 +0,0 @@
1
- "use strict";
2
- /**
3
- * Tests for Cloudflare KV Nonce Cache
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const vitest_1 = require("vitest");
7
- const cloudflare_kv_nonce_cache_js_1 = require("../cloudflare-kv-nonce-cache.js");
8
- // Mock Cloudflare KV namespace
9
- const mockKV = {
10
- get: vitest_1.vi.fn(),
11
- getWithMetadata: vitest_1.vi.fn(),
12
- put: vitest_1.vi.fn(),
13
- delete: vitest_1.vi.fn(),
14
- };
15
- (0, vitest_1.describe)("CloudflareKVNonceCache", () => {
16
- let cache;
17
- (0, vitest_1.beforeEach)(() => {
18
- vitest_1.vi.clearAllMocks();
19
- cache = new cloudflare_kv_nonce_cache_js_1.CloudflareKVNonceCache(mockKV, "test:");
20
- });
21
- (0, vitest_1.describe)("Basic Operations", () => {
22
- (0, vitest_1.it)("should add and check nonce existence", async () => {
23
- const nonce = "test-nonce-123";
24
- // Mock KV responses
25
- mockKV.get.mockResolvedValue(null); // doesn't exist initially
26
- mockKV.put.mockResolvedValue(undefined); // successful put
27
- // Initially should not exist
28
- (0, vitest_1.expect)(await cache.has(nonce)).toBe(false);
29
- (0, vitest_1.expect)(mockKV.get).toHaveBeenCalledWith("test:test-nonce-123");
30
- // Mock getWithMetadata for add operation
31
- mockKV.getWithMetadata.mockResolvedValue({ value: null, metadata: null });
32
- // Add nonce
33
- await cache.add(nonce, 60);
34
- (0, vitest_1.expect)(mockKV.put).toHaveBeenCalledWith("test:test-nonce-123", vitest_1.expect.stringContaining('"nonce":"test-nonce-123"'), { expirationTtl: 60 });
35
- // Mock that it now exists and is valid
36
- const futureTime = Date.now() + 50000;
37
- mockKV.get.mockResolvedValue(JSON.stringify({
38
- nonce,
39
- expiresAt: futureTime,
40
- createdAt: Date.now(),
41
- }));
42
- (0, vitest_1.expect)(await cache.has(nonce)).toBe(true);
43
- });
44
- (0, vitest_1.it)("should handle expired nonces correctly", async () => {
45
- const nonce = "expired-nonce";
46
- // Mock expired nonce data
47
- const pastTime = Date.now() - 10000;
48
- mockKV.get.mockResolvedValue(JSON.stringify({
49
- nonce,
50
- expiresAt: pastTime,
51
- createdAt: Date.now() - 20000,
52
- }));
53
- mockKV.delete.mockResolvedValue(undefined);
54
- // Should return false and clean up expired entry
55
- (0, vitest_1.expect)(await cache.has(nonce)).toBe(false);
56
- (0, vitest_1.expect)(mockKV.delete).toHaveBeenCalledWith("test:expired-nonce");
57
- });
58
- (0, vitest_1.it)("should handle corrupted data gracefully", async () => {
59
- const nonce = "corrupted-nonce";
60
- // Mock corrupted JSON data
61
- mockKV.get.mockResolvedValue("invalid-json");
62
- mockKV.delete.mockResolvedValue(undefined);
63
- // Should return false and clean up corrupted entry
64
- (0, vitest_1.expect)(await cache.has(nonce)).toBe(false);
65
- (0, vitest_1.expect)(mockKV.delete).toHaveBeenCalledWith("test:corrupted-nonce");
66
- });
67
- });
68
- (0, vitest_1.describe)("Atomic Operations with getWithMetadata", () => {
69
- (0, vitest_1.it)("should use getWithMetadata for better atomicity when available", async () => {
70
- const nonce = "atomic-test-nonce";
71
- // Mock getWithMetadata returning no existing value
72
- mockKV.getWithMetadata.mockResolvedValue({ value: null, metadata: null });
73
- mockKV.put.mockResolvedValue(undefined);
74
- await cache.add(nonce, 60);
75
- (0, vitest_1.expect)(mockKV.getWithMetadata).toHaveBeenCalledWith("test:atomic-test-nonce");
76
- (0, vitest_1.expect)(mockKV.put).toHaveBeenCalled();
77
- });
78
- (0, vitest_1.it)("should prevent duplicate addition with getWithMetadata", async () => {
79
- const nonce = "duplicate-nonce";
80
- // Mock existing valid nonce
81
- const futureTime = Date.now() + 50000;
82
- mockKV.getWithMetadata.mockResolvedValue({
83
- value: JSON.stringify({
84
- nonce,
85
- expiresAt: futureTime,
86
- createdAt: Date.now(),
87
- }),
88
- metadata: null,
89
- });
90
- await (0, vitest_1.expect)(cache.add(nonce, 60)).rejects.toThrow("Nonce duplicate-nonce already exists - potential replay attack");
91
- });
92
- (0, vitest_1.it)("should allow overwrite of expired nonce with getWithMetadata", async () => {
93
- const nonce = "expired-overwrite-nonce";
94
- // Mock existing expired nonce
95
- const pastTime = Date.now() - 10000;
96
- mockKV.getWithMetadata.mockResolvedValue({
97
- value: JSON.stringify({
98
- nonce,
99
- expiresAt: pastTime,
100
- createdAt: Date.now() - 20000,
101
- }),
102
- metadata: null,
103
- });
104
- mockKV.put.mockResolvedValue(undefined);
105
- // Should succeed in overwriting expired nonce
106
- await (0, vitest_1.expect)(cache.add(nonce, 60)).resolves.not.toThrow();
107
- (0, vitest_1.expect)(mockKV.put).toHaveBeenCalled();
108
- });
109
- });
110
- (0, vitest_1.describe)("Fallback to Basic Operations", () => {
111
- (0, vitest_1.it)("should fall back to basic operations when getWithMetadata fails", async () => {
112
- const nonce = "fallback-nonce";
113
- // Mock getWithMetadata throwing error
114
- const getWithMetadataError = new Error("getWithMetadata is not available");
115
- mockKV.getWithMetadata.mockRejectedValue(getWithMetadataError);
116
- // Mock basic operations
117
- mockKV.get.mockResolvedValue(null);
118
- mockKV.put.mockResolvedValue(undefined);
119
- await cache.add(nonce, 60);
120
- // Should have fallen back to basic has() check
121
- (0, vitest_1.expect)(mockKV.get).toHaveBeenCalledWith("test:fallback-nonce");
122
- (0, vitest_1.expect)(mockKV.put).toHaveBeenCalled();
123
- });
124
- (0, vitest_1.it)("should prevent duplicate addition in fallback mode", async () => {
125
- const nonce = "fallback-duplicate-nonce";
126
- // Mock getWithMetadata not available
127
- const getWithMetadataError = new Error("getWithMetadata is not available");
128
- mockKV.getWithMetadata.mockRejectedValue(getWithMetadataError);
129
- // Mock existing nonce in basic mode
130
- const futureTime = Date.now() + 50000;
131
- mockKV.get.mockResolvedValue(JSON.stringify({
132
- nonce,
133
- expiresAt: futureTime,
134
- createdAt: Date.now(),
135
- }));
136
- await (0, vitest_1.expect)(cache.add(nonce, 60)).rejects.toThrow("Nonce fallback-duplicate-nonce already exists - potential replay attack");
137
- });
138
- });
139
- (0, vitest_1.describe)("Error Handling", () => {
140
- (0, vitest_1.it)("should propagate unexpected errors", async () => {
141
- const nonce = "error-nonce";
142
- const unexpectedError = new Error("Network error");
143
- mockKV.getWithMetadata.mockRejectedValue(unexpectedError);
144
- await (0, vitest_1.expect)(cache.add(nonce, 60)).rejects.toThrow("Network error");
145
- });
146
- (0, vitest_1.it)("should handle KV operation failures gracefully", async () => {
147
- const nonce = "kv-error-nonce";
148
- mockKV.get.mockRejectedValue(new Error("KV service unavailable"));
149
- // has() should handle KV errors gracefully
150
- await (0, vitest_1.expect)(cache.has(nonce)).rejects.toThrow("KV service unavailable");
151
- });
152
- });
153
- (0, vitest_1.describe)("Cleanup", () => {
154
- (0, vitest_1.it)("should be a no-op since KV handles expiry", async () => {
155
- // cleanup() should not call any KV methods
156
- await cache.cleanup();
157
- (0, vitest_1.expect)(mockKV.get).not.toHaveBeenCalled();
158
- (0, vitest_1.expect)(mockKV.put).not.toHaveBeenCalled();
159
- (0, vitest_1.expect)(mockKV.delete).not.toHaveBeenCalled();
160
- });
161
- });
162
- (0, vitest_1.describe)("Key Prefix", () => {
163
- (0, vitest_1.it)("should use custom key prefix", async () => {
164
- const customCache = new cloudflare_kv_nonce_cache_js_1.CloudflareKVNonceCache(mockKV, "custom:");
165
- mockKV.get.mockResolvedValue(null);
166
- await customCache.has("test-nonce");
167
- (0, vitest_1.expect)(mockKV.get).toHaveBeenCalledWith("custom:test-nonce");
168
- });
169
- (0, vitest_1.it)("should use default key prefix", async () => {
170
- const defaultCache = new cloudflare_kv_nonce_cache_js_1.CloudflareKVNonceCache(mockKV);
171
- mockKV.get.mockResolvedValue(null);
172
- await defaultCache.has("test-nonce");
173
- (0, vitest_1.expect)(mockKV.get).toHaveBeenCalledWith("nonce:test-nonce");
174
- });
175
- });
176
- });
@@ -1,5 +0,0 @@
1
- /**
2
- * Concurrency tests for nonce cache implementations
3
- * Tests multi-instance replay prevention and atomic operations
4
- */
5
- export {};