@frontmcp/utils 0.7.2 → 0.8.1
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/crypto/hmac-signing.d.ts +96 -0
- package/crypto/index.d.ts +11 -0
- package/crypto/key-persistence/factory.d.ts +58 -0
- package/crypto/key-persistence/index.d.ts +35 -0
- package/crypto/key-persistence/key-persistence.d.ts +143 -0
- package/crypto/key-persistence/schemas.d.ts +180 -0
- package/crypto/key-persistence/types.d.ts +120 -0
- package/esm/index.mjs +1778 -583
- package/esm/package.json +17 -2
- package/fs/fs.d.ts +16 -0
- package/fs/index.d.ts +1 -1
- package/index.d.ts +3 -3
- package/index.js +1802 -585
- package/package.json +17 -2
- package/regex/safe-regex.d.ts +1 -1
- package/storage/adapters/filesystem.d.ts +144 -0
- package/storage/adapters/index.d.ts +2 -0
- package/storage/encrypted-typed-storage.d.ts +196 -0
- package/storage/encrypted-typed-storage.types.d.ts +139 -0
- package/storage/errors.d.ts +11 -0
- package/storage/index.d.ts +7 -2
- package/storage/typed-storage.d.ts +129 -0
- package/storage/typed-storage.types.d.ts +47 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Shared utility functions for FrontMCP - string manipulation, URI handling, path utilities, and more",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -23,12 +23,27 @@
|
|
|
23
23
|
"url": "https://github.com/agentfront/frontmcp/issues"
|
|
24
24
|
},
|
|
25
25
|
"homepage": "https://github.com/agentfront/frontmcp/blob/main/libs/utils/README.md",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.0.0"
|
|
28
|
+
},
|
|
26
29
|
"dependencies": {
|
|
27
30
|
"@noble/hashes": "^2.0.1",
|
|
28
31
|
"@noble/ciphers": "^2.1.1",
|
|
29
|
-
"ast
|
|
32
|
+
"@enclave-vm/ast": "^2.10.1",
|
|
30
33
|
"zod": "^4.0.0"
|
|
31
34
|
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@vercel/kv": "^2.0.0 || ^3.0.0",
|
|
37
|
+
"ioredis": "^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@vercel/kv": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"ioredis": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
32
47
|
"type": "commonjs",
|
|
33
48
|
"main": "./index.js",
|
|
34
49
|
"module": "./esm/index.mjs",
|
package/regex/safe-regex.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* These utilities protect against ReDoS (Regular Expression Denial of Service)
|
|
5
5
|
* attacks by validating patterns and enforcing input length limits.
|
|
6
6
|
*/
|
|
7
|
-
import { REDOS_THRESHOLDS } from 'ast
|
|
7
|
+
import { REDOS_THRESHOLDS } from '@enclave-vm/ast';
|
|
8
8
|
/**
|
|
9
9
|
* Default maximum input length for safe regex operations.
|
|
10
10
|
* Inputs longer than this will be rejected to prevent ReDoS attacks.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem Storage Adapter
|
|
3
|
+
*
|
|
4
|
+
* File-based storage implementation for Node.js environments.
|
|
5
|
+
* Each key is stored as a separate JSON file in a directory.
|
|
6
|
+
*
|
|
7
|
+
* **Node.js only** - throws an error if used in browser environments.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const adapter = new FileSystemStorageAdapter({
|
|
12
|
+
* baseDir: '.frontmcp/storage',
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* await adapter.connect();
|
|
16
|
+
* await adapter.set('user:123', JSON.stringify({ name: 'John' }));
|
|
17
|
+
* const value = await adapter.get('user:123');
|
|
18
|
+
* await adapter.disconnect();
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import { BaseStorageAdapter } from './base';
|
|
22
|
+
import type { SetOptions } from '../types';
|
|
23
|
+
/**
|
|
24
|
+
* Options for FileSystemStorageAdapter.
|
|
25
|
+
*/
|
|
26
|
+
export interface FileSystemAdapterOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Base directory for storage (absolute or relative to cwd).
|
|
29
|
+
* Each key is stored as a file in this directory.
|
|
30
|
+
*/
|
|
31
|
+
baseDir: string;
|
|
32
|
+
/**
|
|
33
|
+
* File extension for stored files.
|
|
34
|
+
* @default '.json'
|
|
35
|
+
*/
|
|
36
|
+
extension?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Directory permissions (Unix mode).
|
|
39
|
+
* @default 0o700
|
|
40
|
+
*/
|
|
41
|
+
dirMode?: number;
|
|
42
|
+
/**
|
|
43
|
+
* File permissions (Unix mode).
|
|
44
|
+
* @default 0o600
|
|
45
|
+
*/
|
|
46
|
+
fileMode?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Filesystem-based storage adapter.
|
|
50
|
+
*
|
|
51
|
+
* Features:
|
|
52
|
+
* - Persists data to individual JSON files
|
|
53
|
+
* - Atomic writes using temp file + rename
|
|
54
|
+
* - Restricted file permissions (0o600)
|
|
55
|
+
* - TTL support with lazy expiration
|
|
56
|
+
*
|
|
57
|
+
* Limitations:
|
|
58
|
+
* - No pub/sub support
|
|
59
|
+
* - TTL not enforced with background sweeper
|
|
60
|
+
* - Not suitable for high-throughput scenarios
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* import { FileSystemStorageAdapter } from '@frontmcp/utils';
|
|
65
|
+
*
|
|
66
|
+
* const adapter = new FileSystemStorageAdapter({
|
|
67
|
+
* baseDir: '.frontmcp/keys',
|
|
68
|
+
* extension: '.json',
|
|
69
|
+
* });
|
|
70
|
+
*
|
|
71
|
+
* await adapter.connect();
|
|
72
|
+
*
|
|
73
|
+
* // Store data
|
|
74
|
+
* await adapter.set('my-key', 'my-value');
|
|
75
|
+
*
|
|
76
|
+
* // Retrieve data
|
|
77
|
+
* const value = await adapter.get('my-key');
|
|
78
|
+
*
|
|
79
|
+
* // List all keys
|
|
80
|
+
* const keys = await adapter.keys();
|
|
81
|
+
*
|
|
82
|
+
* await adapter.disconnect();
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare class FileSystemStorageAdapter extends BaseStorageAdapter {
|
|
86
|
+
protected readonly backendName = "filesystem";
|
|
87
|
+
private readonly baseDir;
|
|
88
|
+
private readonly extension;
|
|
89
|
+
private readonly dirMode;
|
|
90
|
+
private readonly fileMode;
|
|
91
|
+
constructor(options: FileSystemAdapterOptions);
|
|
92
|
+
connect(): Promise<void>;
|
|
93
|
+
disconnect(): Promise<void>;
|
|
94
|
+
ping(): Promise<boolean>;
|
|
95
|
+
get(key: string): Promise<string | null>;
|
|
96
|
+
protected doSet(key: string, value: string, options?: SetOptions): Promise<void>;
|
|
97
|
+
delete(key: string): Promise<boolean>;
|
|
98
|
+
exists(key: string): Promise<boolean>;
|
|
99
|
+
expire(key: string, ttlSeconds: number): Promise<boolean>;
|
|
100
|
+
ttl(key: string): Promise<number | null>;
|
|
101
|
+
keys(pattern?: string): Promise<string[]>;
|
|
102
|
+
incr(key: string): Promise<number>;
|
|
103
|
+
decr(key: string): Promise<number>;
|
|
104
|
+
incrBy(key: string, amount: number): Promise<number>;
|
|
105
|
+
/**
|
|
106
|
+
* Convert a storage key to a file path.
|
|
107
|
+
* Sanitizes the key to be filesystem-safe.
|
|
108
|
+
*/
|
|
109
|
+
private keyToPath;
|
|
110
|
+
/**
|
|
111
|
+
* Convert a filename back to a storage key.
|
|
112
|
+
*/
|
|
113
|
+
private pathToKey;
|
|
114
|
+
/**
|
|
115
|
+
* Encode a key to be filesystem-safe.
|
|
116
|
+
* Uses URL encoding to handle special characters.
|
|
117
|
+
*/
|
|
118
|
+
private encodeKey;
|
|
119
|
+
/**
|
|
120
|
+
* Decode a filesystem-safe key back to original.
|
|
121
|
+
*/
|
|
122
|
+
private decodeKey;
|
|
123
|
+
/**
|
|
124
|
+
* Read an entry from a file.
|
|
125
|
+
*/
|
|
126
|
+
private readEntry;
|
|
127
|
+
/**
|
|
128
|
+
* Check if an entry is expired.
|
|
129
|
+
*/
|
|
130
|
+
private isExpired;
|
|
131
|
+
/**
|
|
132
|
+
* Delete a file, returning true if it existed.
|
|
133
|
+
*/
|
|
134
|
+
private deleteFile;
|
|
135
|
+
/**
|
|
136
|
+
* Atomic write: write to temp file then rename.
|
|
137
|
+
* This ensures we don't corrupt the file if write is interrupted.
|
|
138
|
+
*/
|
|
139
|
+
private atomicWrite;
|
|
140
|
+
/**
|
|
141
|
+
* Get suggestion message for pub/sub not supported error.
|
|
142
|
+
*/
|
|
143
|
+
protected getPubSubSuggestion(): string;
|
|
144
|
+
}
|
|
@@ -8,3 +8,5 @@ export { MemoryStorageAdapter } from './memory';
|
|
|
8
8
|
export { RedisStorageAdapter } from './redis';
|
|
9
9
|
export { VercelKvStorageAdapter } from './vercel-kv';
|
|
10
10
|
export { UpstashStorageAdapter } from './upstash';
|
|
11
|
+
export { FileSystemStorageAdapter } from './filesystem';
|
|
12
|
+
export type { FileSystemAdapterOptions } from './filesystem';
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EncryptedTypedStorage
|
|
3
|
+
*
|
|
4
|
+
* A storage wrapper that provides transparent encryption/decryption
|
|
5
|
+
* with key rotation support on top of StorageAdapter.
|
|
6
|
+
*
|
|
7
|
+
* Uses AES-256-GCM for encryption with automatic JSON serialization.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createStorage, EncryptedTypedStorage, randomBytes } from '@frontmcp/utils';
|
|
12
|
+
*
|
|
13
|
+
* interface Secret {
|
|
14
|
+
* apiKey: string;
|
|
15
|
+
* refreshToken: string;
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* const storage = await createStorage({ type: 'memory' });
|
|
19
|
+
* const keys = [
|
|
20
|
+
* { kid: 'key-2024', key: randomBytes(32) },
|
|
21
|
+
* { kid: 'key-2023', key: oldKey }, // For decrypting old data
|
|
22
|
+
* ];
|
|
23
|
+
*
|
|
24
|
+
* const secrets = new EncryptedTypedStorage<Secret>(storage, { keys });
|
|
25
|
+
*
|
|
26
|
+
* // Data is encrypted before storage
|
|
27
|
+
* await secrets.set('user:123', { apiKey: 'sk-...', refreshToken: 'rt-...' });
|
|
28
|
+
*
|
|
29
|
+
* // Data is decrypted on retrieval
|
|
30
|
+
* const secret = await secrets.get('user:123');
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
import type { StorageAdapter, NamespacedStorage, SetOptions } from './types';
|
|
34
|
+
import type { EncryptedTypedStorageOptions, EncryptedSetEntry, EncryptionKey } from './encrypted-typed-storage.types';
|
|
35
|
+
/**
|
|
36
|
+
* EncryptedTypedStorage provides transparent encryption/decryption
|
|
37
|
+
* on top of StorageAdapter.
|
|
38
|
+
*
|
|
39
|
+
* Features:
|
|
40
|
+
* - AES-256-GCM encryption for all stored values
|
|
41
|
+
* - Automatic JSON serialization/deserialization
|
|
42
|
+
* - Key rotation support (multiple keys, first is active)
|
|
43
|
+
* - Optional Zod schema validation after decryption
|
|
44
|
+
* - Batch operations (mget/mset)
|
|
45
|
+
*/
|
|
46
|
+
export declare class EncryptedTypedStorage<T> {
|
|
47
|
+
private readonly storage;
|
|
48
|
+
private activeKey;
|
|
49
|
+
private readonly keyMap;
|
|
50
|
+
private readonly schema?;
|
|
51
|
+
private readonly throwOnError;
|
|
52
|
+
private readonly onKeyRotationNeeded?;
|
|
53
|
+
private readonly clientBinding?;
|
|
54
|
+
constructor(storage: StorageAdapter | NamespacedStorage, options: EncryptedTypedStorageOptions<T>);
|
|
55
|
+
/**
|
|
56
|
+
* Get a decrypted value by key.
|
|
57
|
+
*
|
|
58
|
+
* @param key - Storage key
|
|
59
|
+
* @returns The decrypted value, or null if not found or decryption fails
|
|
60
|
+
*/
|
|
61
|
+
get(key: string): Promise<T | null>;
|
|
62
|
+
/**
|
|
63
|
+
* Encrypt and store a value.
|
|
64
|
+
*
|
|
65
|
+
* @param key - Storage key
|
|
66
|
+
* @param value - Value to encrypt and store
|
|
67
|
+
* @param options - Optional TTL and conditional flags
|
|
68
|
+
*/
|
|
69
|
+
set(key: string, value: T, options?: SetOptions): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Delete a key.
|
|
72
|
+
*
|
|
73
|
+
* @param key - Storage key
|
|
74
|
+
* @returns true if key existed and was deleted
|
|
75
|
+
*/
|
|
76
|
+
delete(key: string): Promise<boolean>;
|
|
77
|
+
/**
|
|
78
|
+
* Check if a key exists.
|
|
79
|
+
*
|
|
80
|
+
* @param key - Storage key
|
|
81
|
+
* @returns true if key exists
|
|
82
|
+
*/
|
|
83
|
+
exists(key: string): Promise<boolean>;
|
|
84
|
+
/**
|
|
85
|
+
* Get multiple decrypted values.
|
|
86
|
+
*
|
|
87
|
+
* @param keys - Array of storage keys
|
|
88
|
+
* @returns Array of decrypted values (null for missing/invalid keys)
|
|
89
|
+
*/
|
|
90
|
+
mget(keys: string[]): Promise<(T | null)[]>;
|
|
91
|
+
/**
|
|
92
|
+
* Encrypt and store multiple values.
|
|
93
|
+
*
|
|
94
|
+
* @param entries - Array of key-value-options entries
|
|
95
|
+
*/
|
|
96
|
+
mset(entries: EncryptedSetEntry<T>[]): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Delete multiple keys.
|
|
99
|
+
*
|
|
100
|
+
* @param keys - Array of storage keys
|
|
101
|
+
* @returns Number of keys actually deleted
|
|
102
|
+
*/
|
|
103
|
+
mdelete(keys: string[]): Promise<number>;
|
|
104
|
+
/**
|
|
105
|
+
* Update TTL on an existing key.
|
|
106
|
+
*
|
|
107
|
+
* @param key - Storage key
|
|
108
|
+
* @param ttlSeconds - New TTL in seconds
|
|
109
|
+
* @returns true if key exists and TTL was set
|
|
110
|
+
*/
|
|
111
|
+
expire(key: string, ttlSeconds: number): Promise<boolean>;
|
|
112
|
+
/**
|
|
113
|
+
* Get remaining TTL for a key.
|
|
114
|
+
*
|
|
115
|
+
* @param key - Storage key
|
|
116
|
+
* @returns TTL in seconds, -1 if no TTL, or null if key doesn't exist
|
|
117
|
+
*/
|
|
118
|
+
ttl(key: string): Promise<number | null>;
|
|
119
|
+
/**
|
|
120
|
+
* List keys matching a pattern.
|
|
121
|
+
*
|
|
122
|
+
* @param pattern - Glob pattern (default: '*' for all keys)
|
|
123
|
+
* @returns Array of matching keys
|
|
124
|
+
*/
|
|
125
|
+
keys(pattern?: string): Promise<string[]>;
|
|
126
|
+
/**
|
|
127
|
+
* Count keys matching a pattern.
|
|
128
|
+
*
|
|
129
|
+
* @param pattern - Glob pattern (default: '*' for all keys)
|
|
130
|
+
* @returns Number of matching keys
|
|
131
|
+
*/
|
|
132
|
+
count(pattern?: string): Promise<number>;
|
|
133
|
+
/**
|
|
134
|
+
* Re-encrypt a value with the active key.
|
|
135
|
+
* Useful for key rotation - reads with old key, writes with new key.
|
|
136
|
+
*
|
|
137
|
+
* @param key - Storage key to re-encrypt
|
|
138
|
+
* @param options - Optional TTL for the re-encrypted value
|
|
139
|
+
* @returns true if value was re-encrypted, false if not found
|
|
140
|
+
*/
|
|
141
|
+
reencrypt(key: string, options?: SetOptions): Promise<boolean>;
|
|
142
|
+
/**
|
|
143
|
+
* Rotate the active encryption key.
|
|
144
|
+
* New encryptions will use the new key; old keys remain for decryption.
|
|
145
|
+
*
|
|
146
|
+
* @param newKey - New encryption key to use for writes
|
|
147
|
+
*/
|
|
148
|
+
rotateKey(newKey: EncryptionKey): void;
|
|
149
|
+
/**
|
|
150
|
+
* Get the active key ID.
|
|
151
|
+
*/
|
|
152
|
+
get activeKeyId(): string;
|
|
153
|
+
/**
|
|
154
|
+
* Get all known key IDs.
|
|
155
|
+
*/
|
|
156
|
+
get keyIds(): string[];
|
|
157
|
+
/**
|
|
158
|
+
* Get the underlying storage adapter.
|
|
159
|
+
* Use with caution - operations bypass encryption.
|
|
160
|
+
*/
|
|
161
|
+
get raw(): StorageAdapter | NamespacedStorage;
|
|
162
|
+
/**
|
|
163
|
+
* Check if client-side key binding is enabled.
|
|
164
|
+
* When true, encryption keys are derived from both server key and client secret.
|
|
165
|
+
*/
|
|
166
|
+
get hasClientBinding(): boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Derive the actual encryption key.
|
|
169
|
+
*
|
|
170
|
+
* If clientBinding is configured, combines server key with client secret
|
|
171
|
+
* using HKDF-SHA256 (RFC 5869) to produce the actual encryption key.
|
|
172
|
+
* Otherwise, returns the server key as-is.
|
|
173
|
+
*
|
|
174
|
+
* This provides zero-knowledge encryption where:
|
|
175
|
+
* - Server cannot decrypt without client secret (sessionId)
|
|
176
|
+
* - Client cannot decrypt without server key
|
|
177
|
+
* - Key derivation is deterministic (same inputs -> same derived key)
|
|
178
|
+
*
|
|
179
|
+
* @param serverKey - The server-side encryption key
|
|
180
|
+
* @returns The key to use for actual encryption/decryption
|
|
181
|
+
*/
|
|
182
|
+
private deriveKey;
|
|
183
|
+
/**
|
|
184
|
+
* Encrypt a value using the active key.
|
|
185
|
+
* If client binding is configured, uses derived key from server key + client secret.
|
|
186
|
+
*/
|
|
187
|
+
private encryptValue;
|
|
188
|
+
/**
|
|
189
|
+
* Decrypt and parse a stored value.
|
|
190
|
+
*/
|
|
191
|
+
private decryptAndParse;
|
|
192
|
+
/**
|
|
193
|
+
* Validate blob structure.
|
|
194
|
+
*/
|
|
195
|
+
private isValidBlob;
|
|
196
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EncryptedTypedStorage Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the EncryptedTypedStorage wrapper that provides
|
|
5
|
+
* transparent encryption/decryption with key rotation support.
|
|
6
|
+
*/
|
|
7
|
+
import type { SetOptions } from './types';
|
|
8
|
+
import type { z } from 'zod';
|
|
9
|
+
/**
|
|
10
|
+
* Encryption key with identifier for key rotation support.
|
|
11
|
+
*/
|
|
12
|
+
export interface EncryptionKey {
|
|
13
|
+
/**
|
|
14
|
+
* Unique key identifier.
|
|
15
|
+
* Used to identify which key was used to encrypt a value.
|
|
16
|
+
*/
|
|
17
|
+
kid: string;
|
|
18
|
+
/**
|
|
19
|
+
* 32-byte (256-bit) AES key.
|
|
20
|
+
*/
|
|
21
|
+
key: Uint8Array;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Client-side key binding configuration for SOC2/ISO compliance.
|
|
25
|
+
*
|
|
26
|
+
* When provided, the actual encryption key is derived from BOTH
|
|
27
|
+
* the server key AND a client-provided secret using HKDF-SHA256.
|
|
28
|
+
*
|
|
29
|
+
* Security properties:
|
|
30
|
+
* - Server cannot decrypt without client secret (e.g., sessionId)
|
|
31
|
+
* - Client cannot decrypt without server key
|
|
32
|
+
* - Prevents RCE attacks from stealing user data
|
|
33
|
+
* - Deterministic derivation (same inputs -> same key)
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const storage = new EncryptedTypedStorage(adapter, {
|
|
38
|
+
* keys: [{ kid: 'server-2024', key: serverKey }],
|
|
39
|
+
* clientBinding: {
|
|
40
|
+
* secret: sessionId, // From MCP client
|
|
41
|
+
* info: 'user-secrets-v1'
|
|
42
|
+
* }
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export interface ClientKeyBinding {
|
|
47
|
+
/**
|
|
48
|
+
* Client-provided secret (e.g., sessionId, token hash).
|
|
49
|
+
* This MUST be provided by the MCP client for every operation.
|
|
50
|
+
*
|
|
51
|
+
* Can be provided as a string (will be UTF-8 encoded) or raw bytes.
|
|
52
|
+
*/
|
|
53
|
+
secret: Uint8Array | string;
|
|
54
|
+
/**
|
|
55
|
+
* Optional salt for HKDF derivation.
|
|
56
|
+
* If not provided, uses empty salt (per RFC 5869).
|
|
57
|
+
*/
|
|
58
|
+
salt?: Uint8Array;
|
|
59
|
+
/**
|
|
60
|
+
* Domain separation info for HKDF.
|
|
61
|
+
* Different values produce different keys even with same inputs.
|
|
62
|
+
* Use to separate keys for different purposes (e.g., 'vault', 'tokens').
|
|
63
|
+
* @default 'frontmcp-client-bound-v1'
|
|
64
|
+
*/
|
|
65
|
+
info?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Options for EncryptedTypedStorage wrapper.
|
|
69
|
+
*/
|
|
70
|
+
export interface EncryptedTypedStorageOptions<T> {
|
|
71
|
+
/**
|
|
72
|
+
* Encryption keys for the storage.
|
|
73
|
+
* First key is the "active" key used for new encryptions.
|
|
74
|
+
* All keys are tried for decryption (supports key rotation).
|
|
75
|
+
*
|
|
76
|
+
* Must have at least one key.
|
|
77
|
+
*/
|
|
78
|
+
keys: EncryptionKey[];
|
|
79
|
+
/**
|
|
80
|
+
* Optional Zod schema for validation after decryption.
|
|
81
|
+
* If provided, values will be validated after decryption.
|
|
82
|
+
*/
|
|
83
|
+
schema?: z.ZodType<T>;
|
|
84
|
+
/**
|
|
85
|
+
* Whether to throw an error when data fails validation or decryption.
|
|
86
|
+
* If false (default), returns null for invalid/undecryptable data.
|
|
87
|
+
* @default false
|
|
88
|
+
*/
|
|
89
|
+
throwOnError?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Optional callback when a value is decrypted with a non-active key.
|
|
92
|
+
* Useful for implementing transparent re-encryption during reads.
|
|
93
|
+
*/
|
|
94
|
+
onKeyRotationNeeded?: (key: string, oldKid: string, newKid: string) => void;
|
|
95
|
+
/**
|
|
96
|
+
* Optional client-side key binding for SOC2/ISO compliance.
|
|
97
|
+
*
|
|
98
|
+
* When provided, the actual encryption key is derived from BOTH
|
|
99
|
+
* the server key AND this client secret using HKDF-SHA256 (RFC 5869).
|
|
100
|
+
*
|
|
101
|
+
* Security implications:
|
|
102
|
+
* - Without the client secret, data CANNOT be decrypted even
|
|
103
|
+
* if the server key is compromised
|
|
104
|
+
* - Prevents RCE attacks from stealing user data
|
|
105
|
+
* - Each session can only access its own encrypted data
|
|
106
|
+
*
|
|
107
|
+
* Omit for debugging/development to use server key directly.
|
|
108
|
+
*/
|
|
109
|
+
clientBinding?: ClientKeyBinding;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Options for encrypted set operations.
|
|
113
|
+
* Re-export for convenience.
|
|
114
|
+
*/
|
|
115
|
+
export type EncryptedSetOptions = SetOptions;
|
|
116
|
+
/**
|
|
117
|
+
* Entry for batch set operations with encrypted values.
|
|
118
|
+
*/
|
|
119
|
+
export interface EncryptedSetEntry<T> {
|
|
120
|
+
key: string;
|
|
121
|
+
value: T;
|
|
122
|
+
options?: EncryptedSetOptions;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Encrypted blob stored in the underlying storage.
|
|
126
|
+
* Includes key ID for identifying which key was used.
|
|
127
|
+
*/
|
|
128
|
+
export interface StoredEncryptedBlob {
|
|
129
|
+
/** Algorithm used (always A256GCM) */
|
|
130
|
+
alg: 'A256GCM';
|
|
131
|
+
/** Key ID used for encryption */
|
|
132
|
+
kid: string;
|
|
133
|
+
/** Base64url-encoded initialization vector */
|
|
134
|
+
iv: string;
|
|
135
|
+
/** Base64url-encoded authentication tag */
|
|
136
|
+
tag: string;
|
|
137
|
+
/** Base64url-encoded ciphertext */
|
|
138
|
+
data: string;
|
|
139
|
+
}
|
package/storage/errors.d.ts
CHANGED
|
@@ -115,3 +115,14 @@ export declare class StorageNotConnectedError extends StorageError {
|
|
|
115
115
|
readonly backend: string;
|
|
116
116
|
constructor(backend: string);
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Error thrown when encryption/decryption operations fail.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* throw new EncryptedStorageError('Failed to decrypt: invalid key');
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export declare class EncryptedStorageError extends StorageError {
|
|
127
|
+
constructor(message: string);
|
|
128
|
+
}
|
package/storage/index.d.ts
CHANGED
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
export type { StorageAdapter, NamespacedStorage, RootStorage, SetOptions, SetEntry, MessageHandler, Unsubscribe, MemoryAdapterOptions, RedisAdapterOptions, VercelKvAdapterOptions, UpstashAdapterOptions, StorageType, StorageConfig, } from './types';
|
|
8
8
|
export { createStorage, createMemoryStorage, getDetectedStorageType } from './factory';
|
|
9
9
|
export { NamespacedStorageImpl, createRootStorage, createNamespacedStorage, buildPrefix, NAMESPACE_SEPARATOR, } from './namespace';
|
|
10
|
-
export { StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError, } from './errors';
|
|
11
|
-
export {
|
|
10
|
+
export { StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError, EncryptedStorageError, } from './errors';
|
|
11
|
+
export { TypedStorage } from './typed-storage';
|
|
12
|
+
export type { TypedStorageOptions, TypedSetOptions, TypedSetEntry } from './typed-storage.types';
|
|
13
|
+
export { EncryptedTypedStorage } from './encrypted-typed-storage';
|
|
14
|
+
export type { EncryptedTypedStorageOptions, EncryptedSetOptions, EncryptedSetEntry, EncryptionKey, StoredEncryptedBlob, ClientKeyBinding, } from './encrypted-typed-storage.types';
|
|
15
|
+
export { BaseStorageAdapter, MemoryStorageAdapter, RedisStorageAdapter, VercelKvStorageAdapter, UpstashStorageAdapter, FileSystemStorageAdapter, } from './adapters';
|
|
16
|
+
export type { FileSystemAdapterOptions } from './adapters';
|
|
12
17
|
export { globToRegex, matchesPattern, validatePattern, escapeGlob } from './utils/pattern';
|
|
13
18
|
export { MAX_TTL_SECONDS, validateTTL, validateOptionalTTL, ttlToExpiresAt, expiresAtToTTL, isExpired, normalizeTTL, } from './utils/ttl';
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypedStorage
|
|
3
|
+
*
|
|
4
|
+
* A generic wrapper that provides type-safe JSON serialization
|
|
5
|
+
* on top of StorageAdapter or NamespacedStorage.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createStorage, TypedStorage } from '@frontmcp/utils';
|
|
10
|
+
*
|
|
11
|
+
* interface User {
|
|
12
|
+
* id: string;
|
|
13
|
+
* name: string;
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* const storage = await createStorage({ type: 'memory' });
|
|
17
|
+
* const users = new TypedStorage<User>(storage);
|
|
18
|
+
*
|
|
19
|
+
* await users.set('user:1', { id: '1', name: 'Alice' });
|
|
20
|
+
* const user = await users.get('user:1');
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import type { StorageAdapter, NamespacedStorage, SetOptions } from './types';
|
|
24
|
+
import type { TypedStorageOptions, TypedSetEntry } from './typed-storage.types';
|
|
25
|
+
/**
|
|
26
|
+
* TypedStorage provides type-safe JSON serialization on top of StorageAdapter.
|
|
27
|
+
*
|
|
28
|
+
* Features:
|
|
29
|
+
* - Automatic JSON serialization/deserialization
|
|
30
|
+
* - Optional Zod schema validation on read
|
|
31
|
+
* - Custom serializer support
|
|
32
|
+
* - Batch operations (mget/mset)
|
|
33
|
+
* - Full StorageAdapter method access
|
|
34
|
+
*/
|
|
35
|
+
export declare class TypedStorage<T> {
|
|
36
|
+
private readonly storage;
|
|
37
|
+
private readonly serialize;
|
|
38
|
+
private readonly deserialize;
|
|
39
|
+
private readonly schema?;
|
|
40
|
+
private readonly throwOnInvalid;
|
|
41
|
+
constructor(storage: StorageAdapter | NamespacedStorage, options?: TypedStorageOptions<T>);
|
|
42
|
+
/**
|
|
43
|
+
* Get a typed value by key.
|
|
44
|
+
*
|
|
45
|
+
* @param key - Storage key
|
|
46
|
+
* @returns The typed value, or null if not found or invalid
|
|
47
|
+
*/
|
|
48
|
+
get(key: string): Promise<T | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Set a typed value with optional TTL.
|
|
51
|
+
*
|
|
52
|
+
* @param key - Storage key
|
|
53
|
+
* @param value - Typed value to store
|
|
54
|
+
* @param options - Optional TTL and conditional flags
|
|
55
|
+
*/
|
|
56
|
+
set(key: string, value: T, options?: SetOptions): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Delete a key.
|
|
59
|
+
*
|
|
60
|
+
* @param key - Storage key
|
|
61
|
+
* @returns true if key existed and was deleted
|
|
62
|
+
*/
|
|
63
|
+
delete(key: string): Promise<boolean>;
|
|
64
|
+
/**
|
|
65
|
+
* Check if a key exists.
|
|
66
|
+
*
|
|
67
|
+
* @param key - Storage key
|
|
68
|
+
* @returns true if key exists
|
|
69
|
+
*/
|
|
70
|
+
exists(key: string): Promise<boolean>;
|
|
71
|
+
/**
|
|
72
|
+
* Get multiple typed values.
|
|
73
|
+
*
|
|
74
|
+
* @param keys - Array of storage keys
|
|
75
|
+
* @returns Array of typed values (null for missing/invalid keys)
|
|
76
|
+
*/
|
|
77
|
+
mget(keys: string[]): Promise<(T | null)[]>;
|
|
78
|
+
/**
|
|
79
|
+
* Set multiple typed values.
|
|
80
|
+
*
|
|
81
|
+
* @param entries - Array of key-value-options entries
|
|
82
|
+
*/
|
|
83
|
+
mset(entries: TypedSetEntry<T>[]): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Delete multiple keys.
|
|
86
|
+
*
|
|
87
|
+
* @param keys - Array of storage keys
|
|
88
|
+
* @returns Number of keys actually deleted
|
|
89
|
+
*/
|
|
90
|
+
mdelete(keys: string[]): Promise<number>;
|
|
91
|
+
/**
|
|
92
|
+
* Update TTL on an existing key.
|
|
93
|
+
*
|
|
94
|
+
* @param key - Storage key
|
|
95
|
+
* @param ttlSeconds - New TTL in seconds
|
|
96
|
+
* @returns true if key exists and TTL was set
|
|
97
|
+
*/
|
|
98
|
+
expire(key: string, ttlSeconds: number): Promise<boolean>;
|
|
99
|
+
/**
|
|
100
|
+
* Get remaining TTL for a key.
|
|
101
|
+
*
|
|
102
|
+
* @param key - Storage key
|
|
103
|
+
* @returns TTL in seconds, -1 if no TTL, or null if key doesn't exist
|
|
104
|
+
*/
|
|
105
|
+
ttl(key: string): Promise<number | null>;
|
|
106
|
+
/**
|
|
107
|
+
* List keys matching a pattern.
|
|
108
|
+
*
|
|
109
|
+
* @param pattern - Glob pattern (default: '*' for all keys)
|
|
110
|
+
* @returns Array of matching keys
|
|
111
|
+
*/
|
|
112
|
+
keys(pattern?: string): Promise<string[]>;
|
|
113
|
+
/**
|
|
114
|
+
* Count keys matching a pattern.
|
|
115
|
+
*
|
|
116
|
+
* @param pattern - Glob pattern (default: '*' for all keys)
|
|
117
|
+
* @returns Number of matching keys
|
|
118
|
+
*/
|
|
119
|
+
count(pattern?: string): Promise<number>;
|
|
120
|
+
/**
|
|
121
|
+
* Get the underlying storage adapter.
|
|
122
|
+
* Use with caution - operations bypass type safety.
|
|
123
|
+
*/
|
|
124
|
+
get raw(): StorageAdapter | NamespacedStorage;
|
|
125
|
+
/**
|
|
126
|
+
* Parse and validate a raw value.
|
|
127
|
+
*/
|
|
128
|
+
private parseValue;
|
|
129
|
+
}
|