@frontmcp/utils 1.10.16 → 1.11.0-rc.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/README.md +1 -0
- package/browser/async-context/browser-async-context.mjs +177 -0
- package/browser/async-context/index.mjs +15 -0
- package/browser/async-context/overlap-error.mjs +10 -0
- package/browser/class-brand/class-brand.mjs +27 -0
- package/browser/class-brand/index.mjs +6 -0
- package/browser/content/content.mjs +151 -0
- package/browser/content/index.mjs +8 -0
- package/browser/crypto/browser.mjs +103 -0
- package/browser/crypto/encrypted-blob.mjs +119 -0
- package/browser/crypto/hmac-signing.mjs +70 -0
- package/browser/crypto/index.mjs +281 -0
- package/browser/crypto/jwt-alg.mjs +40 -0
- package/browser/crypto/key-persistence/factory.mjs +134 -0
- package/browser/crypto/key-persistence/index.mjs +26 -0
- package/browser/crypto/key-persistence/key-persistence.mjs +185 -0
- package/browser/crypto/key-persistence/schemas.mjs +82 -0
- package/browser/crypto/pkce/index.mjs +25 -0
- package/browser/crypto/pkce/pkce.mjs +59 -0
- package/browser/crypto/runtime.mjs +17 -0
- package/browser/crypto/secret-persistence/index.mjs +29 -0
- package/browser/crypto/secret-persistence/persistence.mjs +181 -0
- package/browser/crypto/secret-persistence/schema.mjs +40 -0
- package/browser/crypto/web-rsa.mjs +36 -0
- package/browser/env/browser-env.mjs +56 -0
- package/browser/env/browser-runtime-context.mjs +85 -0
- package/browser/env/build-target.mjs +25 -0
- package/browser/env/edge-scope.mjs +17 -0
- package/browser/env/index.mjs +47 -0
- package/browser/env/provider.mjs +46 -0
- package/browser/escape/escape.mjs +43 -0
- package/browser/escape/index.mjs +9 -0
- package/browser/event-emitter/browser-event-emitter.mjs +49 -0
- package/browser/event-emitter/index.mjs +5 -0
- package/browser/fs/fs.mjs +227 -0
- package/browser/fs/index.mjs +65 -0
- package/browser/http/cookie.mjs +85 -0
- package/browser/http/http.mjs +18 -0
- package/browser/http/index.mjs +11 -0
- package/browser/index.mjs +526 -0
- package/browser/llm/index.mjs +5 -0
- package/browser/llm/llm-tool-handlers.mjs +55 -0
- package/browser/machine-id/index.mjs +7 -0
- package/browser/machine-id/machine-id.mjs +108 -0
- package/browser/naming/index.mjs +10 -0
- package/browser/naming/naming.mjs +68 -0
- package/browser/net/index.mjs +10 -0
- package/browser/net/ip-address.mjs +128 -0
- package/browser/net/redirect.mjs +7 -0
- package/browser/path/browser-path.mjs +47 -0
- package/browser/path/index.mjs +24 -0
- package/browser/path/path.mjs +13 -0
- package/browser/process/child-process.mjs +23 -0
- package/browser/process/index.mjs +15 -0
- package/browser/process/owned-process.mjs +175 -0
- package/browser/process/process-tree.mjs +149 -0
- package/browser/process/process-watch.mjs +48 -0
- package/browser/redaction/index.mjs +6 -0
- package/browser/redaction/redaction.mjs +74 -0
- package/browser/regex/index.mjs +3 -0
- package/browser/regex/patterns.mjs +167 -0
- package/browser/regex/safe-regex.mjs +93 -0
- package/browser/serialization/index.mjs +5 -0
- package/browser/serialization/serialization.mjs +22 -0
- package/browser/stdio-bridge/errors.mjs +27 -0
- package/browser/stdio-bridge/index.mjs +24 -0
- package/browser/stdio-bridge/list-changed.mjs +21 -0
- package/browser/stdio-bridge/state-machine.mjs +211 -0
- package/browser/stdio-bridge/stdio-framer.mjs +91 -0
- package/browser/stdio-bridge/upstream-client.mjs +189 -0
- package/browser/storage/adapters/base.mjs +139 -0
- package/browser/storage/adapters/cloudflare-kv.mjs +149 -0
- package/browser/storage/adapters/filesystem.mjs +279 -0
- package/browser/storage/adapters/index.mjs +23 -0
- package/browser/storage/adapters/indexeddb.mjs +198 -0
- package/browser/storage/adapters/localstorage.mjs +222 -0
- package/browser/storage/adapters/memory.mjs +350 -0
- package/browser/storage/adapters/redis.mjs +497 -0
- package/browser/storage/adapters/upstash.mjs +299 -0
- package/browser/storage/adapters/vercel-kv.mjs +226 -0
- package/browser/storage/change-feed.mjs +189 -0
- package/browser/storage/encrypted-typed-storage.mjs +329 -0
- package/browser/storage/errors.mjs +92 -0
- package/browser/storage/factory.mjs +155 -0
- package/browser/storage/index.mjs +96 -0
- package/browser/storage/namespace.mjs +186 -0
- package/browser/storage/redis-error-listener.mjs +32 -0
- package/browser/storage/redis-url.mjs +66 -0
- package/browser/storage/typed-storage.mjs +171 -0
- package/browser/storage/utils/compare-and-delete.mjs +9 -0
- package/browser/storage/utils/index.mjs +24 -0
- package/browser/storage/utils/pattern.mjs +68 -0
- package/browser/storage/utils/ttl.mjs +48 -0
- package/browser/uri/index.mjs +19 -0
- package/browser/uri/uri-template.mjs +86 -0
- package/browser/uri/uri-validation.mjs +18 -0
- package/crypto/browser.d.ts +1 -13
- package/crypto/browser.js +1 -1
- package/crypto/node.js +1 -1
- package/crypto/web-rsa.d.ts +13 -0
- package/env/browser-runtime-context.js +1 -1
- package/env/runtime-context.js +1 -1
- package/esm/crypto/browser.mjs +1 -1
- package/esm/crypto/node.mjs +1 -1
- package/esm/env/browser-runtime-context.mjs +1 -1
- package/esm/env/runtime-context.mjs +1 -1
- package/esm/index.mjs +1 -1
- package/fs/fs.d.ts +20 -4
- package/fs/index.d.ts +1 -1
- package/index.d.ts +9 -3
- package/index.js +1 -1
- package/package.json +24 -8
- package/process/child-process.d.ts +2 -0
- package/process/index.d.ts +6 -0
- package/process/owned-process.d.ts +59 -0
- package/process/process-tree.d.ts +78 -0
- package/process/process-watch.d.ts +41 -0
- package/redaction/index.d.ts +2 -0
- package/redaction/redaction.d.ts +24 -0
- package/stdio-bridge/errors.d.ts +23 -0
- package/stdio-bridge/index.d.ts +9 -0
- package/stdio-bridge/list-changed.d.ts +3 -0
- package/stdio-bridge/state-machine.d.ts +61 -0
- package/stdio-bridge/stdio-framer.d.ts +49 -0
- package/stdio-bridge/types.d.ts +10 -0
- package/stdio-bridge/upstream-client.d.ts +66 -0
- package/storage/adapters/base.d.ts +2 -0
- package/storage/adapters/memory.d.ts +1 -0
- package/storage/adapters/redis.d.ts +3 -0
- package/storage/change-feed.d.ts +47 -0
- package/storage/factory.d.ts +6 -0
- package/storage/index.d.ts +3 -1
- package/storage/namespace.d.ts +2 -0
- package/storage/types.d.ts +46 -1
- package/storage/utils/compare-and-delete.d.ts +2 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// libs/utils/src/crypto/index.ts
|
|
2
|
+
import * as platformCrypto from "./browser.mjs";
|
|
3
|
+
import { isNode } from "./runtime.mjs";
|
|
4
|
+
import { rsaVerifyBrowser } from "./web-rsa.mjs";
|
|
5
|
+
import { isRsaPssAlg, jwtAlgToNodeAlg, jwtAlgToWebCryptoAlg } from "./jwt-alg.mjs";
|
|
6
|
+
import { isNode as isNode2, isBrowser, assertNode } from "./runtime.mjs";
|
|
7
|
+
import {
|
|
8
|
+
EncryptedBlobError,
|
|
9
|
+
encryptValue,
|
|
10
|
+
decryptValue,
|
|
11
|
+
tryDecryptValue,
|
|
12
|
+
serializeBlob,
|
|
13
|
+
deserializeBlob,
|
|
14
|
+
tryDeserializeBlob,
|
|
15
|
+
isValidEncryptedBlob,
|
|
16
|
+
encryptAndSerialize,
|
|
17
|
+
deserializeAndDecrypt,
|
|
18
|
+
tryDeserializeAndDecrypt
|
|
19
|
+
} from "./encrypted-blob.mjs";
|
|
20
|
+
import {
|
|
21
|
+
MIN_CODE_VERIFIER_LENGTH,
|
|
22
|
+
MAX_CODE_VERIFIER_LENGTH,
|
|
23
|
+
DEFAULT_CODE_VERIFIER_LENGTH,
|
|
24
|
+
PkceError,
|
|
25
|
+
generateCodeVerifier,
|
|
26
|
+
generateCodeChallenge,
|
|
27
|
+
verifyCodeChallenge,
|
|
28
|
+
generatePkcePair,
|
|
29
|
+
isValidCodeVerifier,
|
|
30
|
+
isValidCodeChallenge
|
|
31
|
+
} from "./pkce/index.mjs";
|
|
32
|
+
import {
|
|
33
|
+
secretDataSchema,
|
|
34
|
+
validateSecretData,
|
|
35
|
+
parseSecretData,
|
|
36
|
+
isSecretPersistenceEnabled,
|
|
37
|
+
resolveSecretPath,
|
|
38
|
+
loadSecret,
|
|
39
|
+
saveSecret,
|
|
40
|
+
deleteSecret,
|
|
41
|
+
generateSecret,
|
|
42
|
+
createSecretData,
|
|
43
|
+
getOrCreateSecret,
|
|
44
|
+
clearCachedSecret,
|
|
45
|
+
isSecretCached
|
|
46
|
+
} from "./secret-persistence/index.mjs";
|
|
47
|
+
import {
|
|
48
|
+
signData,
|
|
49
|
+
verifyData,
|
|
50
|
+
isSignedData,
|
|
51
|
+
verifyOrParseData
|
|
52
|
+
} from "./hmac-signing.mjs";
|
|
53
|
+
import {
|
|
54
|
+
asymmetricAlgSchema,
|
|
55
|
+
secretKeyDataSchema,
|
|
56
|
+
asymmetricKeyDataSchema,
|
|
57
|
+
anyKeyDataSchema,
|
|
58
|
+
validateKeyData,
|
|
59
|
+
parseKeyData,
|
|
60
|
+
isSecretKeyData,
|
|
61
|
+
isAsymmetricKeyData,
|
|
62
|
+
KeyPersistence,
|
|
63
|
+
createKeyPersistence,
|
|
64
|
+
createKeyPersistenceWithStorage
|
|
65
|
+
} from "./key-persistence/index.mjs";
|
|
66
|
+
function getCrypto() {
|
|
67
|
+
return platformCrypto.cryptoProvider;
|
|
68
|
+
}
|
|
69
|
+
function rsaVerify2(jwtAlg, data, publicJwk, signature) {
|
|
70
|
+
if (isNode()) {
|
|
71
|
+
return Promise.resolve(platformCrypto.rsaVerify(jwtAlg, data, publicJwk, signature));
|
|
72
|
+
}
|
|
73
|
+
return rsaVerifyBrowser(jwtAlg, data, publicJwk, signature);
|
|
74
|
+
}
|
|
75
|
+
function rsaSignBase64Url2(jwtAlg, data, privateJwk) {
|
|
76
|
+
if (!isNode()) {
|
|
77
|
+
throw new Error("rsaSignBase64Url is only available in Node.js runtimes");
|
|
78
|
+
}
|
|
79
|
+
return platformCrypto.rsaSignBase64Url(jwtAlg, data, privateJwk);
|
|
80
|
+
}
|
|
81
|
+
function rsaVerifySync2(jwtAlg, data, publicJwk, signature) {
|
|
82
|
+
if (!isNode()) {
|
|
83
|
+
throw new Error("rsaVerifySync is only available in Node.js runtimes; use the async rsaVerify in browsers");
|
|
84
|
+
}
|
|
85
|
+
return platformCrypto.rsaVerifySync(jwtAlg, data, publicJwk, signature);
|
|
86
|
+
}
|
|
87
|
+
function pemToPublicJwk2(pem) {
|
|
88
|
+
if (!isNode()) {
|
|
89
|
+
throw new Error("pemToPublicJwk is only available in Node.js runtimes");
|
|
90
|
+
}
|
|
91
|
+
return platformCrypto.pemToPublicJwk(pem);
|
|
92
|
+
}
|
|
93
|
+
function generateRsaKeyPair2(modulusLength, alg) {
|
|
94
|
+
if (!isNode()) {
|
|
95
|
+
throw new Error("generateRsaKeyPair is only available in Node.js runtimes");
|
|
96
|
+
}
|
|
97
|
+
return platformCrypto.generateRsaKeyPair(modulusLength, alg);
|
|
98
|
+
}
|
|
99
|
+
function randomUUID() {
|
|
100
|
+
return getCrypto().randomUUID();
|
|
101
|
+
}
|
|
102
|
+
function randomBytes(length) {
|
|
103
|
+
if (!Number.isInteger(length) || length <= 0) {
|
|
104
|
+
throw new Error(`randomBytes length must be a positive integer, got ${length}`);
|
|
105
|
+
}
|
|
106
|
+
return getCrypto().randomBytes(length);
|
|
107
|
+
}
|
|
108
|
+
function sha256(data) {
|
|
109
|
+
return getCrypto().sha256(data);
|
|
110
|
+
}
|
|
111
|
+
function sha256Hex(data) {
|
|
112
|
+
return getCrypto().sha256Hex(data);
|
|
113
|
+
}
|
|
114
|
+
function hmacSha256(key, data) {
|
|
115
|
+
return getCrypto().hmacSha256(key, data);
|
|
116
|
+
}
|
|
117
|
+
var HKDF_SHA256_MAX_LENGTH = 255 * 32;
|
|
118
|
+
function hkdfSha256(ikm, salt, info, length) {
|
|
119
|
+
if (!Number.isInteger(length) || length <= 0) {
|
|
120
|
+
throw new Error(`HKDF length must be a positive integer, got ${length}`);
|
|
121
|
+
}
|
|
122
|
+
if (length > HKDF_SHA256_MAX_LENGTH) {
|
|
123
|
+
throw new Error(`HKDF-SHA256 length cannot exceed ${HKDF_SHA256_MAX_LENGTH} bytes, got ${length}`);
|
|
124
|
+
}
|
|
125
|
+
return getCrypto().hkdfSha256(ikm, salt, info, length);
|
|
126
|
+
}
|
|
127
|
+
function encryptAesGcm(key, plaintext, iv) {
|
|
128
|
+
if (key.length !== 32) {
|
|
129
|
+
throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
|
|
130
|
+
}
|
|
131
|
+
if (iv.length !== 12) {
|
|
132
|
+
throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
|
|
133
|
+
}
|
|
134
|
+
return getCrypto().encryptAesGcm(key, plaintext, iv);
|
|
135
|
+
}
|
|
136
|
+
function decryptAesGcm(key, ciphertext, iv, tag) {
|
|
137
|
+
if (key.length !== 32) {
|
|
138
|
+
throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`);
|
|
139
|
+
}
|
|
140
|
+
if (iv.length !== 12) {
|
|
141
|
+
throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`);
|
|
142
|
+
}
|
|
143
|
+
if (tag.length !== 16) {
|
|
144
|
+
throw new Error(`AES-GCM requires a 16-byte authentication tag, got ${tag.length} bytes`);
|
|
145
|
+
}
|
|
146
|
+
return getCrypto().decryptAesGcm(key, ciphertext, iv, tag);
|
|
147
|
+
}
|
|
148
|
+
function timingSafeEqual(a, b) {
|
|
149
|
+
if (a.length !== b.length) {
|
|
150
|
+
throw new Error(`timingSafeEqual requires equal-length arrays, got ${a.length} and ${b.length} bytes`);
|
|
151
|
+
}
|
|
152
|
+
return getCrypto().timingSafeEqual(a, b);
|
|
153
|
+
}
|
|
154
|
+
function bytesToHex(data) {
|
|
155
|
+
return Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
156
|
+
}
|
|
157
|
+
function base64urlEncode(data) {
|
|
158
|
+
let base64;
|
|
159
|
+
if (typeof Buffer !== "undefined") {
|
|
160
|
+
base64 = Buffer.from(data).toString("base64");
|
|
161
|
+
} else {
|
|
162
|
+
const binString = Array.from(data, (byte) => String.fromCodePoint(byte)).join("");
|
|
163
|
+
base64 = btoa(binString);
|
|
164
|
+
}
|
|
165
|
+
const result = base64.replace(/\+/g, "-").replace(/\//g, "_");
|
|
166
|
+
let end = result.length;
|
|
167
|
+
while (end > 0 && result[end - 1] === "=") {
|
|
168
|
+
end--;
|
|
169
|
+
}
|
|
170
|
+
return result.slice(0, end);
|
|
171
|
+
}
|
|
172
|
+
function base64urlDecode(data) {
|
|
173
|
+
let base64 = data.replace(/-/g, "+").replace(/_/g, "/");
|
|
174
|
+
const pad = base64.length % 4;
|
|
175
|
+
if (pad) {
|
|
176
|
+
base64 += "=".repeat(4 - pad);
|
|
177
|
+
}
|
|
178
|
+
if (typeof Buffer !== "undefined") {
|
|
179
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
180
|
+
} else {
|
|
181
|
+
const binString = atob(base64);
|
|
182
|
+
return Uint8Array.from(binString, (c) => c.codePointAt(0) ?? 0);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function base64Encode(data) {
|
|
186
|
+
if (typeof Buffer !== "undefined") {
|
|
187
|
+
return Buffer.from(data).toString("base64");
|
|
188
|
+
} else {
|
|
189
|
+
const binString = Array.from(data, (byte) => String.fromCodePoint(byte)).join("");
|
|
190
|
+
return btoa(binString);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function base64Decode(data) {
|
|
194
|
+
if (typeof Buffer !== "undefined") {
|
|
195
|
+
return new Uint8Array(Buffer.from(data, "base64"));
|
|
196
|
+
} else {
|
|
197
|
+
const binString = atob(data);
|
|
198
|
+
return Uint8Array.from(binString, (c) => c.codePointAt(0) ?? 0);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function sha256Base64url(data) {
|
|
202
|
+
return base64urlEncode(sha256(data));
|
|
203
|
+
}
|
|
204
|
+
export {
|
|
205
|
+
DEFAULT_CODE_VERIFIER_LENGTH,
|
|
206
|
+
EncryptedBlobError,
|
|
207
|
+
KeyPersistence,
|
|
208
|
+
MAX_CODE_VERIFIER_LENGTH,
|
|
209
|
+
MIN_CODE_VERIFIER_LENGTH,
|
|
210
|
+
PkceError,
|
|
211
|
+
anyKeyDataSchema,
|
|
212
|
+
assertNode,
|
|
213
|
+
asymmetricAlgSchema,
|
|
214
|
+
asymmetricKeyDataSchema,
|
|
215
|
+
base64Decode,
|
|
216
|
+
base64Encode,
|
|
217
|
+
base64urlDecode,
|
|
218
|
+
base64urlEncode,
|
|
219
|
+
bytesToHex,
|
|
220
|
+
clearCachedSecret,
|
|
221
|
+
createKeyPersistence,
|
|
222
|
+
createKeyPersistenceWithStorage,
|
|
223
|
+
createSecretData,
|
|
224
|
+
decryptAesGcm,
|
|
225
|
+
decryptValue,
|
|
226
|
+
deleteSecret,
|
|
227
|
+
deserializeAndDecrypt,
|
|
228
|
+
deserializeBlob,
|
|
229
|
+
encryptAesGcm,
|
|
230
|
+
encryptAndSerialize,
|
|
231
|
+
encryptValue,
|
|
232
|
+
generateCodeChallenge,
|
|
233
|
+
generateCodeVerifier,
|
|
234
|
+
generatePkcePair,
|
|
235
|
+
generateRsaKeyPair2 as generateRsaKeyPair,
|
|
236
|
+
generateSecret,
|
|
237
|
+
getCrypto,
|
|
238
|
+
getOrCreateSecret,
|
|
239
|
+
hkdfSha256,
|
|
240
|
+
hmacSha256,
|
|
241
|
+
isAsymmetricKeyData,
|
|
242
|
+
isBrowser,
|
|
243
|
+
isNode2 as isNode,
|
|
244
|
+
isRsaPssAlg,
|
|
245
|
+
isSecretCached,
|
|
246
|
+
isSecretKeyData,
|
|
247
|
+
isSecretPersistenceEnabled,
|
|
248
|
+
isSignedData,
|
|
249
|
+
isValidCodeChallenge,
|
|
250
|
+
isValidCodeVerifier,
|
|
251
|
+
isValidEncryptedBlob,
|
|
252
|
+
jwtAlgToNodeAlg,
|
|
253
|
+
jwtAlgToWebCryptoAlg,
|
|
254
|
+
loadSecret,
|
|
255
|
+
parseKeyData,
|
|
256
|
+
parseSecretData,
|
|
257
|
+
pemToPublicJwk2 as pemToPublicJwk,
|
|
258
|
+
randomBytes,
|
|
259
|
+
randomUUID,
|
|
260
|
+
resolveSecretPath,
|
|
261
|
+
rsaSignBase64Url2 as rsaSignBase64Url,
|
|
262
|
+
rsaVerify2 as rsaVerify,
|
|
263
|
+
rsaVerifySync2 as rsaVerifySync,
|
|
264
|
+
saveSecret,
|
|
265
|
+
secretDataSchema,
|
|
266
|
+
secretKeyDataSchema,
|
|
267
|
+
serializeBlob,
|
|
268
|
+
sha256,
|
|
269
|
+
sha256Base64url,
|
|
270
|
+
sha256Hex,
|
|
271
|
+
signData,
|
|
272
|
+
timingSafeEqual,
|
|
273
|
+
tryDecryptValue,
|
|
274
|
+
tryDeserializeAndDecrypt,
|
|
275
|
+
tryDeserializeBlob,
|
|
276
|
+
validateKeyData,
|
|
277
|
+
validateSecretData,
|
|
278
|
+
verifyCodeChallenge,
|
|
279
|
+
verifyData,
|
|
280
|
+
verifyOrParseData
|
|
281
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// libs/utils/src/crypto/jwt-alg.ts
|
|
2
|
+
var JWT_ALG_TO_NODE_DIGEST = {
|
|
3
|
+
RS256: "RSA-SHA256",
|
|
4
|
+
RS384: "RSA-SHA384",
|
|
5
|
+
RS512: "RSA-SHA512",
|
|
6
|
+
// For RSA-PSS, Node's crypto.sign/verify uses the digest algorithm + explicit PSS padding options.
|
|
7
|
+
PS256: "RSA-SHA256",
|
|
8
|
+
PS384: "RSA-SHA384",
|
|
9
|
+
PS512: "RSA-SHA512"
|
|
10
|
+
};
|
|
11
|
+
function jwtAlgToNodeAlg(jwtAlg) {
|
|
12
|
+
const nodeAlg = JWT_ALG_TO_NODE_DIGEST[jwtAlg];
|
|
13
|
+
if (!nodeAlg) {
|
|
14
|
+
throw new Error(`Unsupported JWT algorithm: ${jwtAlg}`);
|
|
15
|
+
}
|
|
16
|
+
return nodeAlg;
|
|
17
|
+
}
|
|
18
|
+
function isRsaPssAlg(jwtAlg) {
|
|
19
|
+
return jwtAlg.startsWith("PS");
|
|
20
|
+
}
|
|
21
|
+
var JWT_ALG_TO_WEB_CRYPTO = {
|
|
22
|
+
RS256: "SHA-256",
|
|
23
|
+
RS384: "SHA-384",
|
|
24
|
+
RS512: "SHA-512",
|
|
25
|
+
PS256: "SHA-256",
|
|
26
|
+
PS384: "SHA-384",
|
|
27
|
+
PS512: "SHA-512"
|
|
28
|
+
};
|
|
29
|
+
function jwtAlgToWebCryptoAlg(jwtAlg) {
|
|
30
|
+
const webAlg = JWT_ALG_TO_WEB_CRYPTO[jwtAlg];
|
|
31
|
+
if (!webAlg) {
|
|
32
|
+
throw new Error(`Unsupported JWT algorithm for WebCrypto: ${jwtAlg}`);
|
|
33
|
+
}
|
|
34
|
+
return webAlg;
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
isRsaPssAlg,
|
|
38
|
+
jwtAlgToNodeAlg,
|
|
39
|
+
jwtAlgToWebCryptoAlg
|
|
40
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// libs/utils/src/crypto/key-persistence/factory.ts
|
|
2
|
+
import { isNode, isBrowser } from "../runtime.mjs";
|
|
3
|
+
import { cryptoProvider } from "../browser.mjs";
|
|
4
|
+
import { MemoryStorageAdapter } from "../../storage/adapters/memory.mjs";
|
|
5
|
+
import { KeyPersistence } from "./key-persistence.mjs";
|
|
6
|
+
import { randomBytes, base64urlEncode, base64urlDecode } from "../index.mjs";
|
|
7
|
+
var DEFAULT_BASE_DIR = ".frontmcp/keys";
|
|
8
|
+
var LS_HKDF_IKM = new TextEncoder().encode("frontmcp:localstorage:v1");
|
|
9
|
+
var INSTALL_SALT_KEY = "__frontmcp_internal__:install_salt";
|
|
10
|
+
var LEGACY_SALT_KEY = "frontmcp:_install_salt";
|
|
11
|
+
function getOrCreateInstallationSalt() {
|
|
12
|
+
if (typeof localStorage === "undefined") {
|
|
13
|
+
throw new Error("localStorage unavailable: cannot derive installation salt");
|
|
14
|
+
}
|
|
15
|
+
const existing = localStorage.getItem(INSTALL_SALT_KEY);
|
|
16
|
+
if (existing) {
|
|
17
|
+
try {
|
|
18
|
+
return base64urlDecode(existing);
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const legacy = localStorage.getItem(LEGACY_SALT_KEY);
|
|
23
|
+
if (legacy) {
|
|
24
|
+
try {
|
|
25
|
+
const salt2 = base64urlDecode(legacy);
|
|
26
|
+
localStorage.setItem(INSTALL_SALT_KEY, base64urlEncode(salt2));
|
|
27
|
+
localStorage.removeItem(LEGACY_SALT_KEY);
|
|
28
|
+
return salt2;
|
|
29
|
+
} catch {
|
|
30
|
+
localStorage.removeItem(LEGACY_SALT_KEY);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const salt = randomBytes(32);
|
|
34
|
+
localStorage.setItem(INSTALL_SALT_KEY, base64urlEncode(salt));
|
|
35
|
+
return salt;
|
|
36
|
+
}
|
|
37
|
+
function deriveLocalStorageKey(userKey) {
|
|
38
|
+
if (userKey) return userKey;
|
|
39
|
+
const salt = getOrCreateInstallationSalt();
|
|
40
|
+
const origin = typeof location !== "undefined" ? location.origin : "unknown";
|
|
41
|
+
const info = new TextEncoder().encode(origin);
|
|
42
|
+
return cryptoProvider.hkdfSha256(LS_HKDF_IKM, salt, info, 32);
|
|
43
|
+
}
|
|
44
|
+
async function createKeyPersistence(options) {
|
|
45
|
+
const type = options?.type ?? "auto";
|
|
46
|
+
const baseDir = options?.baseDir ?? DEFAULT_BASE_DIR;
|
|
47
|
+
let adapter;
|
|
48
|
+
if (type === "memory") {
|
|
49
|
+
adapter = new MemoryStorageAdapter();
|
|
50
|
+
} else if (type === "filesystem") {
|
|
51
|
+
const { FileSystemStorageAdapter } = await import("../../storage/adapters/filesystem.mjs");
|
|
52
|
+
adapter = new FileSystemStorageAdapter({ baseDir });
|
|
53
|
+
} else if (type === "indexeddb") {
|
|
54
|
+
const { IndexedDBStorageAdapter } = await import("../../storage/adapters/indexeddb.mjs");
|
|
55
|
+
adapter = new IndexedDBStorageAdapter({ prefix: "frontmcp:keys:" });
|
|
56
|
+
} else if (type === "localstorage") {
|
|
57
|
+
const { LocalStorageAdapter } = await import("../../storage/adapters/localstorage.mjs");
|
|
58
|
+
adapter = new LocalStorageAdapter({
|
|
59
|
+
prefix: "frontmcp:keys:",
|
|
60
|
+
encryptionKey: deriveLocalStorageKey(options?.encryptionKey)
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
let connected = false;
|
|
64
|
+
if (isNode()) {
|
|
65
|
+
try {
|
|
66
|
+
const { FileSystemStorageAdapter } = await import("../../storage/adapters/filesystem.mjs");
|
|
67
|
+
adapter = new FileSystemStorageAdapter({ baseDir });
|
|
68
|
+
await adapter.connect();
|
|
69
|
+
connected = true;
|
|
70
|
+
} catch {
|
|
71
|
+
adapter = new MemoryStorageAdapter();
|
|
72
|
+
}
|
|
73
|
+
} else if (isBrowser() && typeof indexedDB !== "undefined") {
|
|
74
|
+
try {
|
|
75
|
+
const { IndexedDBStorageAdapter } = await import("../../storage/adapters/indexeddb.mjs");
|
|
76
|
+
adapter = new IndexedDBStorageAdapter({ prefix: "frontmcp:keys:" });
|
|
77
|
+
await adapter.connect();
|
|
78
|
+
connected = true;
|
|
79
|
+
} catch {
|
|
80
|
+
if (typeof localStorage !== "undefined") {
|
|
81
|
+
try {
|
|
82
|
+
const { LocalStorageAdapter } = await import("../../storage/adapters/localstorage.mjs");
|
|
83
|
+
adapter = new LocalStorageAdapter({
|
|
84
|
+
prefix: "frontmcp:keys:",
|
|
85
|
+
encryptionKey: deriveLocalStorageKey(options?.encryptionKey)
|
|
86
|
+
});
|
|
87
|
+
await adapter.connect();
|
|
88
|
+
connected = true;
|
|
89
|
+
} catch {
|
|
90
|
+
adapter = new MemoryStorageAdapter();
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
adapter = new MemoryStorageAdapter();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else if (isBrowser() && typeof localStorage !== "undefined") {
|
|
97
|
+
try {
|
|
98
|
+
const { LocalStorageAdapter } = await import("../../storage/adapters/localstorage.mjs");
|
|
99
|
+
adapter = new LocalStorageAdapter({
|
|
100
|
+
prefix: "frontmcp:keys:",
|
|
101
|
+
encryptionKey: deriveLocalStorageKey(options?.encryptionKey)
|
|
102
|
+
});
|
|
103
|
+
await adapter.connect();
|
|
104
|
+
connected = true;
|
|
105
|
+
} catch {
|
|
106
|
+
adapter = new MemoryStorageAdapter();
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
adapter = new MemoryStorageAdapter();
|
|
110
|
+
}
|
|
111
|
+
if (!connected) {
|
|
112
|
+
await adapter.connect();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (type !== "auto") {
|
|
116
|
+
await adapter.connect();
|
|
117
|
+
}
|
|
118
|
+
return new KeyPersistence({
|
|
119
|
+
storage: adapter,
|
|
120
|
+
throwOnInvalid: options?.throwOnInvalid ?? false,
|
|
121
|
+
enableCache: options?.enableCache ?? true
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function createKeyPersistenceWithStorage(storage, options) {
|
|
125
|
+
return new KeyPersistence({
|
|
126
|
+
storage,
|
|
127
|
+
throwOnInvalid: options?.throwOnInvalid ?? false,
|
|
128
|
+
enableCache: options?.enableCache ?? true
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
createKeyPersistence,
|
|
133
|
+
createKeyPersistenceWithStorage
|
|
134
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// libs/utils/src/crypto/key-persistence/index.ts
|
|
2
|
+
import {
|
|
3
|
+
asymmetricAlgSchema,
|
|
4
|
+
secretKeyDataSchema,
|
|
5
|
+
asymmetricKeyDataSchema,
|
|
6
|
+
anyKeyDataSchema,
|
|
7
|
+
validateKeyData,
|
|
8
|
+
parseKeyData,
|
|
9
|
+
isSecretKeyData,
|
|
10
|
+
isAsymmetricKeyData
|
|
11
|
+
} from "./schemas.mjs";
|
|
12
|
+
import { KeyPersistence } from "./key-persistence.mjs";
|
|
13
|
+
import { createKeyPersistence, createKeyPersistenceWithStorage } from "./factory.mjs";
|
|
14
|
+
export {
|
|
15
|
+
KeyPersistence,
|
|
16
|
+
anyKeyDataSchema,
|
|
17
|
+
asymmetricAlgSchema,
|
|
18
|
+
asymmetricKeyDataSchema,
|
|
19
|
+
createKeyPersistence,
|
|
20
|
+
createKeyPersistenceWithStorage,
|
|
21
|
+
isAsymmetricKeyData,
|
|
22
|
+
isSecretKeyData,
|
|
23
|
+
parseKeyData,
|
|
24
|
+
secretKeyDataSchema,
|
|
25
|
+
validateKeyData
|
|
26
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// libs/utils/src/crypto/key-persistence/key-persistence.ts
|
|
2
|
+
import { randomBytes, base64urlEncode } from "../index.mjs";
|
|
3
|
+
import { validateKeyData, isSecretKeyData, isAsymmetricKeyData } from "./schemas.mjs";
|
|
4
|
+
var KeyPersistence = class {
|
|
5
|
+
storage;
|
|
6
|
+
cache = /* @__PURE__ */ new Map();
|
|
7
|
+
enableCache;
|
|
8
|
+
throwOnInvalid;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.storage = options.storage;
|
|
11
|
+
this.enableCache = options.enableCache ?? true;
|
|
12
|
+
this.throwOnInvalid = options.throwOnInvalid ?? false;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get a key by ID.
|
|
16
|
+
*
|
|
17
|
+
* @param kid - Key identifier
|
|
18
|
+
* @returns Key data or null if not found
|
|
19
|
+
*/
|
|
20
|
+
async get(kid) {
|
|
21
|
+
if (this.enableCache) {
|
|
22
|
+
const cached = this.cache.get(kid);
|
|
23
|
+
if (cached) return cached;
|
|
24
|
+
}
|
|
25
|
+
const raw = await this.storage.get(kid);
|
|
26
|
+
if (!raw) return null;
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(raw);
|
|
30
|
+
} catch {
|
|
31
|
+
if (this.throwOnInvalid) {
|
|
32
|
+
throw new Error(`Invalid JSON for key "${kid}"`);
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const validation = validateKeyData(parsed);
|
|
37
|
+
if (!validation.valid) {
|
|
38
|
+
if (this.throwOnInvalid) {
|
|
39
|
+
throw new Error(`Invalid key data for "${kid}": ${validation.error}`);
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const key = validation.data;
|
|
44
|
+
if (this.enableCache) {
|
|
45
|
+
this.cache.set(kid, key);
|
|
46
|
+
}
|
|
47
|
+
return key;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get a secret key by ID.
|
|
51
|
+
*
|
|
52
|
+
* @param kid - Key identifier
|
|
53
|
+
* @returns Secret key data or null if not found or wrong type
|
|
54
|
+
*/
|
|
55
|
+
async getSecret(kid) {
|
|
56
|
+
const key = await this.get(kid);
|
|
57
|
+
if (!key || !isSecretKeyData(key)) return null;
|
|
58
|
+
return key;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get an asymmetric key by ID.
|
|
62
|
+
*
|
|
63
|
+
* @param kid - Key identifier
|
|
64
|
+
* @returns Asymmetric key data or null if not found or wrong type
|
|
65
|
+
*/
|
|
66
|
+
async getAsymmetric(kid) {
|
|
67
|
+
const key = await this.get(kid);
|
|
68
|
+
if (!key || !isAsymmetricKeyData(key)) return null;
|
|
69
|
+
return key;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Store a key.
|
|
73
|
+
*
|
|
74
|
+
* @param key - Key data to store
|
|
75
|
+
*/
|
|
76
|
+
async set(key) {
|
|
77
|
+
const validation = validateKeyData(key);
|
|
78
|
+
if (!validation.valid) {
|
|
79
|
+
throw new Error(`Invalid key data: ${validation.error}`);
|
|
80
|
+
}
|
|
81
|
+
await this.storage.set(key.kid, JSON.stringify(key, null, 2));
|
|
82
|
+
if (this.enableCache) {
|
|
83
|
+
this.cache.set(key.kid, key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Delete a key.
|
|
88
|
+
*
|
|
89
|
+
* @param kid - Key identifier
|
|
90
|
+
* @returns true if key existed and was deleted
|
|
91
|
+
*/
|
|
92
|
+
async delete(kid) {
|
|
93
|
+
this.cache.delete(kid);
|
|
94
|
+
return this.storage.delete(kid);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if a key exists.
|
|
98
|
+
*
|
|
99
|
+
* @param kid - Key identifier
|
|
100
|
+
* @returns true if key exists
|
|
101
|
+
*/
|
|
102
|
+
async has(kid) {
|
|
103
|
+
if (this.enableCache && this.cache.has(kid)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return this.storage.exists(kid);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* List all key IDs.
|
|
110
|
+
*
|
|
111
|
+
* @returns Array of key identifiers
|
|
112
|
+
*/
|
|
113
|
+
async list() {
|
|
114
|
+
return this.storage.keys();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get or create a secret key.
|
|
118
|
+
*
|
|
119
|
+
* If a key with the given ID exists and is a valid secret key,
|
|
120
|
+
* it is returned. Otherwise, a new secret key is generated.
|
|
121
|
+
*
|
|
122
|
+
* @param kid - Key identifier
|
|
123
|
+
* @param options - Options for key creation
|
|
124
|
+
* @returns Secret key data
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const secret = await keys.getOrCreateSecret('encryption-key');
|
|
129
|
+
* const bytes = base64urlDecode(secret.secret);
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
async getOrCreateSecret(kid, options) {
|
|
133
|
+
const existing = await this.getSecret(kid);
|
|
134
|
+
if (existing) {
|
|
135
|
+
return existing;
|
|
136
|
+
}
|
|
137
|
+
const bytes = options?.bytes ?? 32;
|
|
138
|
+
const secret = {
|
|
139
|
+
type: "secret",
|
|
140
|
+
kid,
|
|
141
|
+
secret: base64urlEncode(randomBytes(bytes)),
|
|
142
|
+
bytes,
|
|
143
|
+
createdAt: Date.now(),
|
|
144
|
+
version: 1
|
|
145
|
+
};
|
|
146
|
+
await this.set(secret);
|
|
147
|
+
return secret;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Clear the in-memory cache.
|
|
151
|
+
*
|
|
152
|
+
* Useful when you want to force a reload from storage.
|
|
153
|
+
*/
|
|
154
|
+
clearCache() {
|
|
155
|
+
this.cache.clear();
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Clear cache for a specific key.
|
|
159
|
+
*
|
|
160
|
+
* @param kid - Key identifier
|
|
161
|
+
*/
|
|
162
|
+
clearCacheFor(kid) {
|
|
163
|
+
this.cache.delete(kid);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Check if a key is in the cache.
|
|
167
|
+
*
|
|
168
|
+
* @param kid - Key identifier
|
|
169
|
+
* @returns true if key is cached
|
|
170
|
+
*/
|
|
171
|
+
isCached(kid) {
|
|
172
|
+
return this.cache.has(kid);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the underlying storage adapter.
|
|
176
|
+
*
|
|
177
|
+
* Use with caution - direct storage access bypasses validation.
|
|
178
|
+
*/
|
|
179
|
+
getAdapter() {
|
|
180
|
+
return this.storage;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
export {
|
|
184
|
+
KeyPersistence
|
|
185
|
+
};
|