@frontmcp/utils 0.7.2 → 0.8.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/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 +1777 -582
- package/esm/package.json +16 -1
- package/fs/fs.d.ts +16 -0
- package/fs/index.d.ts +1 -1
- package/index.d.ts +3 -3
- package/index.js +1799 -582
- package/package.json +16 -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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic HMAC Signing Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides HMAC-SHA256 signing and verification for any JSON-serializable data.
|
|
5
|
+
* Protects against data tampering when stored in external systems.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { signData, verifyData } from '@frontmcp/utils';
|
|
10
|
+
*
|
|
11
|
+
* // Sign any data
|
|
12
|
+
* const signed = signData({ userId: '123', role: 'admin' }, { secret: 'my-secret' });
|
|
13
|
+
*
|
|
14
|
+
* // Verify and extract
|
|
15
|
+
* const data = verifyData(signed, { secret: 'my-secret' });
|
|
16
|
+
* if (data) {
|
|
17
|
+
* console.log(data.userId); // '123'
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Signed data wrapper structure.
|
|
23
|
+
* Contains the data and its HMAC signature.
|
|
24
|
+
*/
|
|
25
|
+
export interface SignedData<T> {
|
|
26
|
+
/** The signed data */
|
|
27
|
+
data: T;
|
|
28
|
+
/** HMAC-SHA256 signature in base64url format */
|
|
29
|
+
sig: string;
|
|
30
|
+
/** Signature version for future algorithm changes */
|
|
31
|
+
v: 1;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Configuration for HMAC signing operations.
|
|
35
|
+
*/
|
|
36
|
+
export interface HmacSigningConfig {
|
|
37
|
+
/**
|
|
38
|
+
* The secret key used for HMAC signing.
|
|
39
|
+
* Should be at least 32 bytes for security.
|
|
40
|
+
*/
|
|
41
|
+
secret: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Sign data with HMAC-SHA256.
|
|
45
|
+
*
|
|
46
|
+
* Creates a tamper-evident wrapper around the data.
|
|
47
|
+
* Any modification to the data will invalidate the signature.
|
|
48
|
+
*
|
|
49
|
+
* @param data - The data to sign (must be JSON-serializable)
|
|
50
|
+
* @param config - Signing configuration with secret
|
|
51
|
+
* @returns JSON string containing signed data
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const signed = signData({ key: 'value' }, { secret: 'my-secret' });
|
|
56
|
+
* await redis.set('mykey', signed);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function signData<T>(data: T, config: HmacSigningConfig): string;
|
|
60
|
+
/**
|
|
61
|
+
* Verify and extract signed data.
|
|
62
|
+
*
|
|
63
|
+
* Validates the HMAC signature and returns the data if valid.
|
|
64
|
+
* Returns null if the signature is invalid or the data is corrupted.
|
|
65
|
+
*
|
|
66
|
+
* @param signedJson - JSON string from signData()
|
|
67
|
+
* @param config - Signing configuration with secret
|
|
68
|
+
* @returns The verified data or null if invalid
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* const raw = await redis.get('mykey');
|
|
73
|
+
* const data = verifyData<MyType>(raw, { secret: 'my-secret' });
|
|
74
|
+
* if (!data) {
|
|
75
|
+
* // Data was tampered with or corrupted
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function verifyData<T>(signedJson: string, config: HmacSigningConfig): T | null;
|
|
80
|
+
/**
|
|
81
|
+
* Check if a stored value is signed data.
|
|
82
|
+
* Useful for migrating from unsigned to signed data.
|
|
83
|
+
*
|
|
84
|
+
* @param json - Raw JSON string from storage
|
|
85
|
+
* @returns true if the data appears to be signed
|
|
86
|
+
*/
|
|
87
|
+
export declare function isSignedData(json: string): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Verify or parse data, supporting both signed and unsigned formats.
|
|
90
|
+
* Useful for backwards compatibility during migration.
|
|
91
|
+
*
|
|
92
|
+
* @param json - Raw JSON string from storage
|
|
93
|
+
* @param config - Signing configuration with secret
|
|
94
|
+
* @returns The data (verified if signed, parsed if unsigned) or null
|
|
95
|
+
*/
|
|
96
|
+
export declare function verifyOrParseData<T>(json: string, config: HmacSigningConfig): T | null;
|
package/crypto/index.d.ts
CHANGED
|
@@ -86,6 +86,15 @@ export declare function base64urlEncode(data: Uint8Array): string;
|
|
|
86
86
|
* Decode a base64url string to Uint8Array.
|
|
87
87
|
*/
|
|
88
88
|
export declare function base64urlDecode(data: string): Uint8Array;
|
|
89
|
+
/**
|
|
90
|
+
* Encode a Uint8Array to standard base64 string.
|
|
91
|
+
* RFC 4648 Section 4: Uses +/= characters, suitable for HTTP Basic auth.
|
|
92
|
+
*/
|
|
93
|
+
export declare function base64Encode(data: Uint8Array): string;
|
|
94
|
+
/**
|
|
95
|
+
* Decode a standard base64 string to Uint8Array.
|
|
96
|
+
*/
|
|
97
|
+
export declare function base64Decode(data: string): Uint8Array;
|
|
89
98
|
/**
|
|
90
99
|
* Compute SHA-256 hash and return as base64url string.
|
|
91
100
|
* Commonly used for PKCE code_challenge (S256 method).
|
|
@@ -96,3 +105,5 @@ export { isNode, isBrowser, assertNode } from './runtime';
|
|
|
96
105
|
export { type EncryptedBlob, EncryptedBlobError, encryptValue, decryptValue, tryDecryptValue, serializeBlob, deserializeBlob, tryDeserializeBlob, isValidEncryptedBlob, encryptAndSerialize, deserializeAndDecrypt, tryDeserializeAndDecrypt, } from './encrypted-blob';
|
|
97
106
|
export { MIN_CODE_VERIFIER_LENGTH, MAX_CODE_VERIFIER_LENGTH, DEFAULT_CODE_VERIFIER_LENGTH, PkceError, generateCodeVerifier, generateCodeChallenge, verifyCodeChallenge, generatePkcePair, isValidCodeVerifier, isValidCodeChallenge, type PkcePair, } from './pkce';
|
|
98
107
|
export { type SecretData, type SecretPersistenceOptions, type SecretValidationResult, secretDataSchema, validateSecretData, parseSecretData, isSecretPersistenceEnabled, resolveSecretPath, loadSecret, saveSecret, deleteSecret, generateSecret, createSecretData, getOrCreateSecret, clearCachedSecret, isSecretCached, } from './secret-persistence';
|
|
108
|
+
export { type SignedData, type HmacSigningConfig, signData, verifyData, isSignedData, verifyOrParseData, } from './hmac-signing';
|
|
109
|
+
export { type BaseKeyData, type SecretKeyData, type AsymmetricKeyData, type AnyKeyData, type KeyPersistenceOptions, type CreateKeyPersistenceOptions, type CreateSecretOptions, type CreateAsymmetricOptions, type KeyValidationResult, asymmetricAlgSchema, secretKeyDataSchema, asymmetricKeyDataSchema, anyKeyDataSchema, validateKeyData, parseKeyData, isSecretKeyData, isAsymmetricKeyData, KeyPersistence, createKeyPersistence, createKeyPersistenceWithStorage, } from './key-persistence';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory functions for KeyPersistence.
|
|
3
|
+
*
|
|
4
|
+
* Provides convenient ways to create KeyPersistence instances
|
|
5
|
+
* with auto-detection of storage backend.
|
|
6
|
+
*
|
|
7
|
+
* @module @frontmcp/utils/key-persistence
|
|
8
|
+
*/
|
|
9
|
+
import type { StorageAdapter } from '../../storage/types';
|
|
10
|
+
import { KeyPersistence } from './key-persistence';
|
|
11
|
+
import type { CreateKeyPersistenceOptions } from './types';
|
|
12
|
+
/**
|
|
13
|
+
* Create a KeyPersistence instance with auto-detected storage.
|
|
14
|
+
*
|
|
15
|
+
* In Node.js: Uses filesystem storage at `.frontmcp/keys/` by default
|
|
16
|
+
* In browser: Uses memory storage (keys lost on refresh)
|
|
17
|
+
*
|
|
18
|
+
* @param options - Configuration options
|
|
19
|
+
* @returns KeyPersistence instance (storage already connected)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Auto-detect storage
|
|
24
|
+
* const keys = await createKeyPersistence();
|
|
25
|
+
*
|
|
26
|
+
* // Force memory storage
|
|
27
|
+
* const memKeys = await createKeyPersistence({ type: 'memory' });
|
|
28
|
+
*
|
|
29
|
+
* // Custom directory for filesystem
|
|
30
|
+
* const fsKeys = await createKeyPersistence({
|
|
31
|
+
* type: 'filesystem',
|
|
32
|
+
* baseDir: '.my-app/keys',
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function createKeyPersistence(options?: CreateKeyPersistenceOptions): Promise<KeyPersistence>;
|
|
37
|
+
/**
|
|
38
|
+
* Create a KeyPersistence instance with an explicit storage adapter.
|
|
39
|
+
*
|
|
40
|
+
* Use this when you want to provide your own storage backend
|
|
41
|
+
* (e.g., Redis, custom adapter).
|
|
42
|
+
*
|
|
43
|
+
* Note: The storage adapter must already be connected.
|
|
44
|
+
*
|
|
45
|
+
* @param storage - Connected storage adapter
|
|
46
|
+
* @param options - Additional options
|
|
47
|
+
* @returns KeyPersistence instance
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* import { createStorage } from '@frontmcp/utils';
|
|
52
|
+
*
|
|
53
|
+
* // Use Redis for distributed key storage
|
|
54
|
+
* const redis = await createStorage({ type: 'redis' });
|
|
55
|
+
* const keys = createKeyPersistenceWithStorage(redis);
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function createKeyPersistenceWithStorage(storage: StorageAdapter, options?: Pick<CreateKeyPersistenceOptions, 'throwOnInvalid' | 'enableCache'>): KeyPersistence;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KeyPersistence Module
|
|
3
|
+
*
|
|
4
|
+
* Unified key persistence for browser and Node.js environments.
|
|
5
|
+
* Uses StorageAdapter abstraction to support both memory (browser)
|
|
6
|
+
* and filesystem (Node.js) backends.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { createKeyPersistence } from '@frontmcp/utils';
|
|
11
|
+
*
|
|
12
|
+
* // Auto-detect storage (filesystem in Node.js, memory in browser)
|
|
13
|
+
* const keys = await createKeyPersistence();
|
|
14
|
+
*
|
|
15
|
+
* // Get or create a secret
|
|
16
|
+
* const secret = await keys.getOrCreateSecret('encryption-key');
|
|
17
|
+
* console.log(secret.secret); // base64url-encoded secret
|
|
18
|
+
*
|
|
19
|
+
* // Store a custom key
|
|
20
|
+
* await keys.set({
|
|
21
|
+
* type: 'secret',
|
|
22
|
+
* kid: 'my-key',
|
|
23
|
+
* secret: 'abc...',
|
|
24
|
+
* bytes: 32,
|
|
25
|
+
* createdAt: Date.now(),
|
|
26
|
+
* version: 1,
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @module @frontmcp/utils/key-persistence
|
|
31
|
+
*/
|
|
32
|
+
export type { BaseKeyData, SecretKeyData, AsymmetricKeyData, AnyKeyData, KeyPersistenceOptions, CreateKeyPersistenceOptions, CreateSecretOptions, CreateAsymmetricOptions, } from './types';
|
|
33
|
+
export { asymmetricAlgSchema, secretKeyDataSchema, asymmetricKeyDataSchema, anyKeyDataSchema, validateKeyData, parseKeyData, isSecretKeyData, isAsymmetricKeyData, type KeyValidationResult, } from './schemas';
|
|
34
|
+
export { KeyPersistence } from './key-persistence';
|
|
35
|
+
export { createKeyPersistence, createKeyPersistenceWithStorage } from './factory';
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KeyPersistence - Unified key management with storage abstraction.
|
|
3
|
+
*
|
|
4
|
+
* Provides a high-level API for storing and retrieving cryptographic keys
|
|
5
|
+
* with any storage backend (memory, filesystem, Redis, etc.).
|
|
6
|
+
*
|
|
7
|
+
* @module @frontmcp/utils/key-persistence
|
|
8
|
+
*/
|
|
9
|
+
import type { StorageAdapter } from '../../storage/types';
|
|
10
|
+
import type { AnyKeyData, SecretKeyData, AsymmetricKeyData, KeyPersistenceOptions, CreateSecretOptions } from './types';
|
|
11
|
+
/**
|
|
12
|
+
* KeyPersistence provides a unified API for storing and retrieving
|
|
13
|
+
* cryptographic keys with any storage backend.
|
|
14
|
+
*
|
|
15
|
+
* Features:
|
|
16
|
+
* - Type-safe key storage with Zod validation
|
|
17
|
+
* - In-memory caching for fast reads
|
|
18
|
+
* - Support for symmetric secrets and asymmetric key pairs
|
|
19
|
+
* - Works with any StorageAdapter (Memory, FileSystem, Redis, etc.)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { KeyPersistence, MemoryStorageAdapter } from '@frontmcp/utils';
|
|
24
|
+
*
|
|
25
|
+
* const storage = new MemoryStorageAdapter();
|
|
26
|
+
* await storage.connect();
|
|
27
|
+
*
|
|
28
|
+
* const keys = new KeyPersistence({ storage });
|
|
29
|
+
*
|
|
30
|
+
* // Get or create a secret
|
|
31
|
+
* const secret = await keys.getOrCreateSecret('encryption-key');
|
|
32
|
+
* console.log(secret.secret); // base64url string
|
|
33
|
+
*
|
|
34
|
+
* // Store a custom key
|
|
35
|
+
* await keys.set({
|
|
36
|
+
* type: 'secret',
|
|
37
|
+
* kid: 'my-key',
|
|
38
|
+
* secret: 'abc...',
|
|
39
|
+
* bytes: 32,
|
|
40
|
+
* createdAt: Date.now(),
|
|
41
|
+
* version: 1,
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* // List all keys
|
|
45
|
+
* const kids = await keys.list();
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare class KeyPersistence {
|
|
49
|
+
private readonly storage;
|
|
50
|
+
private readonly cache;
|
|
51
|
+
private readonly enableCache;
|
|
52
|
+
private readonly throwOnInvalid;
|
|
53
|
+
constructor(options: KeyPersistenceOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Get a key by ID.
|
|
56
|
+
*
|
|
57
|
+
* @param kid - Key identifier
|
|
58
|
+
* @returns Key data or null if not found
|
|
59
|
+
*/
|
|
60
|
+
get<T extends AnyKeyData = AnyKeyData>(kid: string): Promise<T | null>;
|
|
61
|
+
/**
|
|
62
|
+
* Get a secret key by ID.
|
|
63
|
+
*
|
|
64
|
+
* @param kid - Key identifier
|
|
65
|
+
* @returns Secret key data or null if not found or wrong type
|
|
66
|
+
*/
|
|
67
|
+
getSecret(kid: string): Promise<SecretKeyData | null>;
|
|
68
|
+
/**
|
|
69
|
+
* Get an asymmetric key by ID.
|
|
70
|
+
*
|
|
71
|
+
* @param kid - Key identifier
|
|
72
|
+
* @returns Asymmetric key data or null if not found or wrong type
|
|
73
|
+
*/
|
|
74
|
+
getAsymmetric(kid: string): Promise<AsymmetricKeyData | null>;
|
|
75
|
+
/**
|
|
76
|
+
* Store a key.
|
|
77
|
+
*
|
|
78
|
+
* @param key - Key data to store
|
|
79
|
+
*/
|
|
80
|
+
set(key: AnyKeyData): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Delete a key.
|
|
83
|
+
*
|
|
84
|
+
* @param kid - Key identifier
|
|
85
|
+
* @returns true if key existed and was deleted
|
|
86
|
+
*/
|
|
87
|
+
delete(kid: string): Promise<boolean>;
|
|
88
|
+
/**
|
|
89
|
+
* Check if a key exists.
|
|
90
|
+
*
|
|
91
|
+
* @param kid - Key identifier
|
|
92
|
+
* @returns true if key exists
|
|
93
|
+
*/
|
|
94
|
+
has(kid: string): Promise<boolean>;
|
|
95
|
+
/**
|
|
96
|
+
* List all key IDs.
|
|
97
|
+
*
|
|
98
|
+
* @returns Array of key identifiers
|
|
99
|
+
*/
|
|
100
|
+
list(): Promise<string[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Get or create a secret key.
|
|
103
|
+
*
|
|
104
|
+
* If a key with the given ID exists and is a valid secret key,
|
|
105
|
+
* it is returned. Otherwise, a new secret key is generated.
|
|
106
|
+
*
|
|
107
|
+
* @param kid - Key identifier
|
|
108
|
+
* @param options - Options for key creation
|
|
109
|
+
* @returns Secret key data
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* const secret = await keys.getOrCreateSecret('encryption-key');
|
|
114
|
+
* const bytes = base64urlDecode(secret.secret);
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
getOrCreateSecret(kid: string, options?: CreateSecretOptions): Promise<SecretKeyData>;
|
|
118
|
+
/**
|
|
119
|
+
* Clear the in-memory cache.
|
|
120
|
+
*
|
|
121
|
+
* Useful when you want to force a reload from storage.
|
|
122
|
+
*/
|
|
123
|
+
clearCache(): void;
|
|
124
|
+
/**
|
|
125
|
+
* Clear cache for a specific key.
|
|
126
|
+
*
|
|
127
|
+
* @param kid - Key identifier
|
|
128
|
+
*/
|
|
129
|
+
clearCacheFor(kid: string): void;
|
|
130
|
+
/**
|
|
131
|
+
* Check if a key is in the cache.
|
|
132
|
+
*
|
|
133
|
+
* @param kid - Key identifier
|
|
134
|
+
* @returns true if key is cached
|
|
135
|
+
*/
|
|
136
|
+
isCached(kid: string): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Get the underlying storage adapter.
|
|
139
|
+
*
|
|
140
|
+
* Use with caution - direct storage access bypasses validation.
|
|
141
|
+
*/
|
|
142
|
+
getAdapter(): StorageAdapter;
|
|
143
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schemas for key persistence validation.
|
|
3
|
+
*
|
|
4
|
+
* @module @frontmcp/utils/key-persistence
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import type { AnyKeyData, SecretKeyData, AsymmetricKeyData } from './types';
|
|
8
|
+
/**
|
|
9
|
+
* Supported asymmetric algorithms.
|
|
10
|
+
*/
|
|
11
|
+
export declare const asymmetricAlgSchema: z.ZodEnum<{
|
|
12
|
+
RS256: "RS256";
|
|
13
|
+
RS384: "RS384";
|
|
14
|
+
RS512: "RS512";
|
|
15
|
+
ES256: "ES256";
|
|
16
|
+
ES384: "ES384";
|
|
17
|
+
ES512: "ES512";
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Zod schema for SecretKeyData.
|
|
21
|
+
*/
|
|
22
|
+
export declare const secretKeyDataSchema: z.ZodObject<{
|
|
23
|
+
kid: z.ZodString;
|
|
24
|
+
createdAt: z.ZodNumber;
|
|
25
|
+
version: z.ZodNumber;
|
|
26
|
+
type: z.ZodLiteral<"secret">;
|
|
27
|
+
secret: z.ZodString;
|
|
28
|
+
bytes: z.ZodNumber;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
/**
|
|
31
|
+
* Zod schema for AsymmetricKeyData.
|
|
32
|
+
*/
|
|
33
|
+
export declare const asymmetricKeyDataSchema: z.ZodObject<{
|
|
34
|
+
kid: z.ZodString;
|
|
35
|
+
createdAt: z.ZodNumber;
|
|
36
|
+
version: z.ZodNumber;
|
|
37
|
+
type: z.ZodLiteral<"asymmetric">;
|
|
38
|
+
alg: z.ZodEnum<{
|
|
39
|
+
RS256: "RS256";
|
|
40
|
+
RS384: "RS384";
|
|
41
|
+
RS512: "RS512";
|
|
42
|
+
ES256: "ES256";
|
|
43
|
+
ES384: "ES384";
|
|
44
|
+
ES512: "ES512";
|
|
45
|
+
}>;
|
|
46
|
+
privateKey: z.ZodObject<{
|
|
47
|
+
kty: z.ZodOptional<z.ZodString>;
|
|
48
|
+
use: z.ZodOptional<z.ZodString>;
|
|
49
|
+
alg: z.ZodOptional<z.ZodString>;
|
|
50
|
+
kid: z.ZodOptional<z.ZodString>;
|
|
51
|
+
n: z.ZodOptional<z.ZodString>;
|
|
52
|
+
e: z.ZodOptional<z.ZodString>;
|
|
53
|
+
d: z.ZodOptional<z.ZodString>;
|
|
54
|
+
p: z.ZodOptional<z.ZodString>;
|
|
55
|
+
q: z.ZodOptional<z.ZodString>;
|
|
56
|
+
dp: z.ZodOptional<z.ZodString>;
|
|
57
|
+
dq: z.ZodOptional<z.ZodString>;
|
|
58
|
+
qi: z.ZodOptional<z.ZodString>;
|
|
59
|
+
x: z.ZodOptional<z.ZodString>;
|
|
60
|
+
y: z.ZodOptional<z.ZodString>;
|
|
61
|
+
crv: z.ZodOptional<z.ZodString>;
|
|
62
|
+
}, z.core.$loose>;
|
|
63
|
+
publicJwk: z.ZodObject<{
|
|
64
|
+
keys: z.ZodArray<z.ZodObject<{
|
|
65
|
+
kty: z.ZodOptional<z.ZodString>;
|
|
66
|
+
use: z.ZodOptional<z.ZodString>;
|
|
67
|
+
alg: z.ZodOptional<z.ZodString>;
|
|
68
|
+
kid: z.ZodOptional<z.ZodString>;
|
|
69
|
+
n: z.ZodOptional<z.ZodString>;
|
|
70
|
+
e: z.ZodOptional<z.ZodString>;
|
|
71
|
+
d: z.ZodOptional<z.ZodString>;
|
|
72
|
+
p: z.ZodOptional<z.ZodString>;
|
|
73
|
+
q: z.ZodOptional<z.ZodString>;
|
|
74
|
+
dp: z.ZodOptional<z.ZodString>;
|
|
75
|
+
dq: z.ZodOptional<z.ZodString>;
|
|
76
|
+
qi: z.ZodOptional<z.ZodString>;
|
|
77
|
+
x: z.ZodOptional<z.ZodString>;
|
|
78
|
+
y: z.ZodOptional<z.ZodString>;
|
|
79
|
+
crv: z.ZodOptional<z.ZodString>;
|
|
80
|
+
}, z.core.$loose>>;
|
|
81
|
+
}, z.core.$strip>;
|
|
82
|
+
}, z.core.$strip>;
|
|
83
|
+
/**
|
|
84
|
+
* Zod schema for AnyKeyData (discriminated union).
|
|
85
|
+
*/
|
|
86
|
+
export declare const anyKeyDataSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
87
|
+
kid: z.ZodString;
|
|
88
|
+
createdAt: z.ZodNumber;
|
|
89
|
+
version: z.ZodNumber;
|
|
90
|
+
type: z.ZodLiteral<"secret">;
|
|
91
|
+
secret: z.ZodString;
|
|
92
|
+
bytes: z.ZodNumber;
|
|
93
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
94
|
+
kid: z.ZodString;
|
|
95
|
+
createdAt: z.ZodNumber;
|
|
96
|
+
version: z.ZodNumber;
|
|
97
|
+
type: z.ZodLiteral<"asymmetric">;
|
|
98
|
+
alg: z.ZodEnum<{
|
|
99
|
+
RS256: "RS256";
|
|
100
|
+
RS384: "RS384";
|
|
101
|
+
RS512: "RS512";
|
|
102
|
+
ES256: "ES256";
|
|
103
|
+
ES384: "ES384";
|
|
104
|
+
ES512: "ES512";
|
|
105
|
+
}>;
|
|
106
|
+
privateKey: z.ZodObject<{
|
|
107
|
+
kty: z.ZodOptional<z.ZodString>;
|
|
108
|
+
use: z.ZodOptional<z.ZodString>;
|
|
109
|
+
alg: z.ZodOptional<z.ZodString>;
|
|
110
|
+
kid: z.ZodOptional<z.ZodString>;
|
|
111
|
+
n: z.ZodOptional<z.ZodString>;
|
|
112
|
+
e: z.ZodOptional<z.ZodString>;
|
|
113
|
+
d: z.ZodOptional<z.ZodString>;
|
|
114
|
+
p: z.ZodOptional<z.ZodString>;
|
|
115
|
+
q: z.ZodOptional<z.ZodString>;
|
|
116
|
+
dp: z.ZodOptional<z.ZodString>;
|
|
117
|
+
dq: z.ZodOptional<z.ZodString>;
|
|
118
|
+
qi: z.ZodOptional<z.ZodString>;
|
|
119
|
+
x: z.ZodOptional<z.ZodString>;
|
|
120
|
+
y: z.ZodOptional<z.ZodString>;
|
|
121
|
+
crv: z.ZodOptional<z.ZodString>;
|
|
122
|
+
}, z.core.$loose>;
|
|
123
|
+
publicJwk: z.ZodObject<{
|
|
124
|
+
keys: z.ZodArray<z.ZodObject<{
|
|
125
|
+
kty: z.ZodOptional<z.ZodString>;
|
|
126
|
+
use: z.ZodOptional<z.ZodString>;
|
|
127
|
+
alg: z.ZodOptional<z.ZodString>;
|
|
128
|
+
kid: z.ZodOptional<z.ZodString>;
|
|
129
|
+
n: z.ZodOptional<z.ZodString>;
|
|
130
|
+
e: z.ZodOptional<z.ZodString>;
|
|
131
|
+
d: z.ZodOptional<z.ZodString>;
|
|
132
|
+
p: z.ZodOptional<z.ZodString>;
|
|
133
|
+
q: z.ZodOptional<z.ZodString>;
|
|
134
|
+
dp: z.ZodOptional<z.ZodString>;
|
|
135
|
+
dq: z.ZodOptional<z.ZodString>;
|
|
136
|
+
qi: z.ZodOptional<z.ZodString>;
|
|
137
|
+
x: z.ZodOptional<z.ZodString>;
|
|
138
|
+
y: z.ZodOptional<z.ZodString>;
|
|
139
|
+
crv: z.ZodOptional<z.ZodString>;
|
|
140
|
+
}, z.core.$loose>>;
|
|
141
|
+
}, z.core.$strip>;
|
|
142
|
+
}, z.core.$strip>], "type">;
|
|
143
|
+
/**
|
|
144
|
+
* Result of validating key data.
|
|
145
|
+
*/
|
|
146
|
+
export interface KeyValidationResult {
|
|
147
|
+
/** Whether the key data is valid */
|
|
148
|
+
valid: boolean;
|
|
149
|
+
/** Parsed key data if valid */
|
|
150
|
+
data?: AnyKeyData;
|
|
151
|
+
/** Error message if invalid */
|
|
152
|
+
error?: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Validate key data structure and timestamps.
|
|
156
|
+
*
|
|
157
|
+
* Checks:
|
|
158
|
+
* - Schema validation
|
|
159
|
+
* - createdAt is not in the future (with 1 minute drift allowance)
|
|
160
|
+
* - createdAt is not too old (100 years)
|
|
161
|
+
*
|
|
162
|
+
* @param data - Data to validate
|
|
163
|
+
* @returns Validation result
|
|
164
|
+
*/
|
|
165
|
+
export declare function validateKeyData(data: unknown): KeyValidationResult;
|
|
166
|
+
/**
|
|
167
|
+
* Parse and validate key data.
|
|
168
|
+
*
|
|
169
|
+
* @param data - Raw data to parse
|
|
170
|
+
* @returns Parsed key data or null if invalid
|
|
171
|
+
*/
|
|
172
|
+
export declare function parseKeyData(data: unknown): AnyKeyData | null;
|
|
173
|
+
/**
|
|
174
|
+
* Check if key data is a secret key.
|
|
175
|
+
*/
|
|
176
|
+
export declare function isSecretKeyData(data: AnyKeyData): data is SecretKeyData;
|
|
177
|
+
/**
|
|
178
|
+
* Check if key data is an asymmetric key.
|
|
179
|
+
*/
|
|
180
|
+
export declare function isAsymmetricKeyData(data: AnyKeyData): data is AsymmetricKeyData;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for unified key persistence.
|
|
3
|
+
*
|
|
4
|
+
* Supports both symmetric secrets and asymmetric key pairs
|
|
5
|
+
* with a unified storage interface.
|
|
6
|
+
*
|
|
7
|
+
* @module @frontmcp/utils/key-persistence
|
|
8
|
+
*/
|
|
9
|
+
import type { StorageAdapter } from '../../storage/types';
|
|
10
|
+
/**
|
|
11
|
+
* Base interface for all key data types.
|
|
12
|
+
*/
|
|
13
|
+
export interface BaseKeyData {
|
|
14
|
+
/** Unique key identifier */
|
|
15
|
+
kid: string;
|
|
16
|
+
/** Creation timestamp (ms since epoch) */
|
|
17
|
+
createdAt: number;
|
|
18
|
+
/** Schema version for migrations */
|
|
19
|
+
version: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Symmetric secret key data.
|
|
23
|
+
* Used for encryption, HMAC, and other symmetric operations.
|
|
24
|
+
*/
|
|
25
|
+
export interface SecretKeyData extends BaseKeyData {
|
|
26
|
+
type: 'secret';
|
|
27
|
+
/** Base64url-encoded secret bytes */
|
|
28
|
+
secret: string;
|
|
29
|
+
/** Number of bytes in the secret */
|
|
30
|
+
bytes: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Asymmetric key pair data (RSA or EC).
|
|
34
|
+
* Used for JWT signing and verification.
|
|
35
|
+
*/
|
|
36
|
+
export interface AsymmetricKeyData extends BaseKeyData {
|
|
37
|
+
type: 'asymmetric';
|
|
38
|
+
/** Algorithm identifier (RS256, RS384, RS512, ES256, ES384, ES512) */
|
|
39
|
+
alg: 'RS256' | 'RS384' | 'RS512' | 'ES256' | 'ES384' | 'ES512';
|
|
40
|
+
/** Private key in JWK format */
|
|
41
|
+
privateKey: JsonWebKey;
|
|
42
|
+
/** Public JWKS for verification (contains keys array) */
|
|
43
|
+
publicJwk: {
|
|
44
|
+
keys: JsonWebKey[];
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Union of all key types.
|
|
49
|
+
*/
|
|
50
|
+
export type AnyKeyData = SecretKeyData | AsymmetricKeyData;
|
|
51
|
+
/**
|
|
52
|
+
* Options for KeyPersistence constructor.
|
|
53
|
+
*/
|
|
54
|
+
export interface KeyPersistenceOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Storage adapter for key data.
|
|
57
|
+
* Can be MemoryStorageAdapter, FileSystemStorageAdapter, or any StorageAdapter.
|
|
58
|
+
*/
|
|
59
|
+
storage: StorageAdapter;
|
|
60
|
+
/**
|
|
61
|
+
* Whether to throw an error on invalid key data.
|
|
62
|
+
* If false, invalid data is silently ignored (returns null).
|
|
63
|
+
* @default false
|
|
64
|
+
*/
|
|
65
|
+
throwOnInvalid?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Enable in-memory caching for faster reads.
|
|
68
|
+
* @default true
|
|
69
|
+
*/
|
|
70
|
+
enableCache?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Options for creating a KeyPersistence instance.
|
|
74
|
+
*/
|
|
75
|
+
export interface CreateKeyPersistenceOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Storage type.
|
|
78
|
+
* - 'auto': Auto-detect based on environment (filesystem in Node.js, memory in browser)
|
|
79
|
+
* - 'memory': Always use memory (keys lost on restart)
|
|
80
|
+
* - 'filesystem': Always use filesystem (Node.js only)
|
|
81
|
+
* @default 'auto'
|
|
82
|
+
*/
|
|
83
|
+
type?: 'auto' | 'memory' | 'filesystem';
|
|
84
|
+
/**
|
|
85
|
+
* Base directory for filesystem storage.
|
|
86
|
+
* Only used when type is 'filesystem' or 'auto' in Node.js.
|
|
87
|
+
* @default '.frontmcp/keys'
|
|
88
|
+
*/
|
|
89
|
+
baseDir?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Whether to throw on invalid key data.
|
|
92
|
+
* @default false
|
|
93
|
+
*/
|
|
94
|
+
throwOnInvalid?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Enable in-memory caching.
|
|
97
|
+
* @default true
|
|
98
|
+
*/
|
|
99
|
+
enableCache?: boolean;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Options for creating a secret key.
|
|
103
|
+
*/
|
|
104
|
+
export interface CreateSecretOptions {
|
|
105
|
+
/**
|
|
106
|
+
* Number of bytes to generate.
|
|
107
|
+
* @default 32 (256 bits)
|
|
108
|
+
*/
|
|
109
|
+
bytes?: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Options for creating an asymmetric key.
|
|
113
|
+
*/
|
|
114
|
+
export interface CreateAsymmetricOptions {
|
|
115
|
+
/**
|
|
116
|
+
* Algorithm to use.
|
|
117
|
+
* @default 'RS256'
|
|
118
|
+
*/
|
|
119
|
+
alg?: AsymmetricKeyData['alg'];
|
|
120
|
+
}
|