@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,82 @@
|
|
|
1
|
+
// libs/utils/src/crypto/key-persistence/schemas.ts
|
|
2
|
+
import { internalZ as z } from "@frontmcp/lazy-zod";
|
|
3
|
+
var MAX_CLOCK_DRIFT_MS = 6e4;
|
|
4
|
+
var MAX_KEY_AGE_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
5
|
+
var asymmetricAlgSchema = z.enum(["RS256", "RS384", "RS512", "ES256", "ES384", "ES512"]);
|
|
6
|
+
var baseKeyDataSchema = z.object({
|
|
7
|
+
kid: z.string().min(1),
|
|
8
|
+
createdAt: z.number().positive().int(),
|
|
9
|
+
version: z.number().positive().int()
|
|
10
|
+
});
|
|
11
|
+
var jsonWebKeySchema = z.object({
|
|
12
|
+
kty: z.string().optional(),
|
|
13
|
+
use: z.string().optional(),
|
|
14
|
+
alg: z.string().optional(),
|
|
15
|
+
kid: z.string().optional(),
|
|
16
|
+
n: z.string().optional(),
|
|
17
|
+
e: z.string().optional(),
|
|
18
|
+
d: z.string().optional(),
|
|
19
|
+
p: z.string().optional(),
|
|
20
|
+
q: z.string().optional(),
|
|
21
|
+
dp: z.string().optional(),
|
|
22
|
+
dq: z.string().optional(),
|
|
23
|
+
qi: z.string().optional(),
|
|
24
|
+
x: z.string().optional(),
|
|
25
|
+
y: z.string().optional(),
|
|
26
|
+
crv: z.string().optional()
|
|
27
|
+
}).passthrough();
|
|
28
|
+
var secretKeyDataSchema = baseKeyDataSchema.extend({
|
|
29
|
+
type: z.literal("secret"),
|
|
30
|
+
secret: z.string().min(1),
|
|
31
|
+
bytes: z.number().positive().int()
|
|
32
|
+
});
|
|
33
|
+
var asymmetricKeyDataSchema = baseKeyDataSchema.extend({
|
|
34
|
+
type: z.literal("asymmetric"),
|
|
35
|
+
alg: asymmetricAlgSchema,
|
|
36
|
+
privateKey: jsonWebKeySchema,
|
|
37
|
+
publicJwk: z.object({
|
|
38
|
+
keys: z.array(jsonWebKeySchema).min(1)
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
var anyKeyDataSchema = z.discriminatedUnion("type", [secretKeyDataSchema, asymmetricKeyDataSchema]);
|
|
42
|
+
function validateKeyData(data) {
|
|
43
|
+
const result = anyKeyDataSchema.safeParse(data);
|
|
44
|
+
if (!result.success) {
|
|
45
|
+
return {
|
|
46
|
+
valid: false,
|
|
47
|
+
error: result.error.issues[0]?.message ?? "Invalid key structure"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const parsed = result.data;
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
if (parsed.createdAt > now + MAX_CLOCK_DRIFT_MS) {
|
|
53
|
+
return { valid: false, error: "createdAt is in the future" };
|
|
54
|
+
}
|
|
55
|
+
if (parsed.createdAt < now - MAX_KEY_AGE_MS) {
|
|
56
|
+
return { valid: false, error: "createdAt is too old" };
|
|
57
|
+
}
|
|
58
|
+
return { valid: true, data: parsed };
|
|
59
|
+
}
|
|
60
|
+
function parseKeyData(data) {
|
|
61
|
+
const validation = validateKeyData(data);
|
|
62
|
+
if (!validation.valid) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return validation.data;
|
|
66
|
+
}
|
|
67
|
+
function isSecretKeyData(data) {
|
|
68
|
+
return data.type === "secret";
|
|
69
|
+
}
|
|
70
|
+
function isAsymmetricKeyData(data) {
|
|
71
|
+
return data.type === "asymmetric";
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
anyKeyDataSchema,
|
|
75
|
+
asymmetricAlgSchema,
|
|
76
|
+
asymmetricKeyDataSchema,
|
|
77
|
+
isAsymmetricKeyData,
|
|
78
|
+
isSecretKeyData,
|
|
79
|
+
parseKeyData,
|
|
80
|
+
secretKeyDataSchema,
|
|
81
|
+
validateKeyData
|
|
82
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// libs/utils/src/crypto/pkce/index.ts
|
|
2
|
+
import {
|
|
3
|
+
MIN_CODE_VERIFIER_LENGTH,
|
|
4
|
+
MAX_CODE_VERIFIER_LENGTH,
|
|
5
|
+
DEFAULT_CODE_VERIFIER_LENGTH,
|
|
6
|
+
PkceError,
|
|
7
|
+
generateCodeVerifier,
|
|
8
|
+
generateCodeChallenge,
|
|
9
|
+
verifyCodeChallenge,
|
|
10
|
+
generatePkcePair,
|
|
11
|
+
isValidCodeVerifier,
|
|
12
|
+
isValidCodeChallenge
|
|
13
|
+
} from "./pkce.mjs";
|
|
14
|
+
export {
|
|
15
|
+
DEFAULT_CODE_VERIFIER_LENGTH,
|
|
16
|
+
MAX_CODE_VERIFIER_LENGTH,
|
|
17
|
+
MIN_CODE_VERIFIER_LENGTH,
|
|
18
|
+
PkceError,
|
|
19
|
+
generateCodeChallenge,
|
|
20
|
+
generateCodeVerifier,
|
|
21
|
+
generatePkcePair,
|
|
22
|
+
isValidCodeChallenge,
|
|
23
|
+
isValidCodeVerifier,
|
|
24
|
+
verifyCodeChallenge
|
|
25
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// libs/utils/src/crypto/pkce/pkce.ts
|
|
2
|
+
import { randomBytes, sha256, base64urlEncode } from "../index.mjs";
|
|
3
|
+
var MIN_CODE_VERIFIER_LENGTH = 43;
|
|
4
|
+
var MAX_CODE_VERIFIER_LENGTH = 128;
|
|
5
|
+
var DEFAULT_CODE_VERIFIER_LENGTH = 64;
|
|
6
|
+
var PkceError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "PkceError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
function generateCodeVerifier(length = DEFAULT_CODE_VERIFIER_LENGTH) {
|
|
13
|
+
if (length < MIN_CODE_VERIFIER_LENGTH || length > MAX_CODE_VERIFIER_LENGTH) {
|
|
14
|
+
throw new PkceError(
|
|
15
|
+
`Code verifier length must be between ${MIN_CODE_VERIFIER_LENGTH} and ${MAX_CODE_VERIFIER_LENGTH} characters (got ${length})`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
const bytesNeeded = Math.ceil(length * 6 / 8);
|
|
19
|
+
const bytes = randomBytes(bytesNeeded);
|
|
20
|
+
return base64urlEncode(bytes).slice(0, length);
|
|
21
|
+
}
|
|
22
|
+
function generateCodeChallenge(codeVerifier) {
|
|
23
|
+
const verifierBytes = new TextEncoder().encode(codeVerifier);
|
|
24
|
+
const hash = sha256(verifierBytes);
|
|
25
|
+
return base64urlEncode(hash);
|
|
26
|
+
}
|
|
27
|
+
function verifyCodeChallenge(codeVerifier, codeChallenge) {
|
|
28
|
+
const computedChallenge = generateCodeChallenge(codeVerifier);
|
|
29
|
+
return computedChallenge === codeChallenge;
|
|
30
|
+
}
|
|
31
|
+
function generatePkcePair(length = DEFAULT_CODE_VERIFIER_LENGTH) {
|
|
32
|
+
const codeVerifier = generateCodeVerifier(length);
|
|
33
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
34
|
+
return { codeVerifier, codeChallenge };
|
|
35
|
+
}
|
|
36
|
+
function isValidCodeVerifier(value) {
|
|
37
|
+
if (value.length < MIN_CODE_VERIFIER_LENGTH || value.length > MAX_CODE_VERIFIER_LENGTH) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return /^[A-Za-z0-9\-._~]+$/.test(value);
|
|
41
|
+
}
|
|
42
|
+
function isValidCodeChallenge(value) {
|
|
43
|
+
if (value.length !== 43) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return /^[A-Za-z0-9\-_]+$/.test(value);
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
DEFAULT_CODE_VERIFIER_LENGTH,
|
|
50
|
+
MAX_CODE_VERIFIER_LENGTH,
|
|
51
|
+
MIN_CODE_VERIFIER_LENGTH,
|
|
52
|
+
PkceError,
|
|
53
|
+
generateCodeChallenge,
|
|
54
|
+
generateCodeVerifier,
|
|
55
|
+
generatePkcePair,
|
|
56
|
+
isValidCodeChallenge,
|
|
57
|
+
isValidCodeVerifier,
|
|
58
|
+
verifyCodeChallenge
|
|
59
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// libs/utils/src/crypto/runtime.ts
|
|
2
|
+
function isNode() {
|
|
3
|
+
return typeof process !== "undefined" && !!process.versions?.node;
|
|
4
|
+
}
|
|
5
|
+
function isBrowser() {
|
|
6
|
+
return typeof globalThis !== "undefined" && typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function";
|
|
7
|
+
}
|
|
8
|
+
function assertNode(feature) {
|
|
9
|
+
if (!isNode()) {
|
|
10
|
+
throw new Error(`${feature} is not supported in the browser. Requires Node.js.`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
assertNode,
|
|
15
|
+
isBrowser,
|
|
16
|
+
isNode
|
|
17
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// libs/utils/src/crypto/secret-persistence/index.ts
|
|
2
|
+
import { secretDataSchema, validateSecretData, parseSecretData } from "./schema.mjs";
|
|
3
|
+
import {
|
|
4
|
+
isSecretPersistenceEnabled,
|
|
5
|
+
resolveSecretPath,
|
|
6
|
+
loadSecret,
|
|
7
|
+
saveSecret,
|
|
8
|
+
deleteSecret,
|
|
9
|
+
generateSecret,
|
|
10
|
+
createSecretData,
|
|
11
|
+
getOrCreateSecret,
|
|
12
|
+
clearCachedSecret,
|
|
13
|
+
isSecretCached
|
|
14
|
+
} from "./persistence.mjs";
|
|
15
|
+
export {
|
|
16
|
+
clearCachedSecret,
|
|
17
|
+
createSecretData,
|
|
18
|
+
deleteSecret,
|
|
19
|
+
generateSecret,
|
|
20
|
+
getOrCreateSecret,
|
|
21
|
+
isSecretCached,
|
|
22
|
+
isSecretPersistenceEnabled,
|
|
23
|
+
loadSecret,
|
|
24
|
+
parseSecretData,
|
|
25
|
+
resolveSecretPath,
|
|
26
|
+
saveSecret,
|
|
27
|
+
secretDataSchema,
|
|
28
|
+
validateSecretData
|
|
29
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// libs/utils/src/crypto/secret-persistence/persistence.ts
|
|
12
|
+
import { readFile, writeFile, mkdir, rename, unlink } from "../../fs/index.mjs";
|
|
13
|
+
import { randomBytes, base64urlEncode } from "../index.mjs";
|
|
14
|
+
import { validateSecretData } from "./schema.mjs";
|
|
15
|
+
import { getEnv, getCwd } from "../../env/browser-env.mjs";
|
|
16
|
+
function getPath() {
|
|
17
|
+
return __require("path");
|
|
18
|
+
}
|
|
19
|
+
var DEFAULT_SECRET_BYTES = 32;
|
|
20
|
+
var DEFAULT_SECRET_DIR = ".frontmcp";
|
|
21
|
+
function isSecretPersistenceEnabled(options) {
|
|
22
|
+
const isProduction = getEnv("NODE_ENV") === "production";
|
|
23
|
+
if (isProduction) {
|
|
24
|
+
return options?.forceEnable === true;
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
function resolveSecretPath(options) {
|
|
29
|
+
const p = getPath();
|
|
30
|
+
if (options?.secretPath) {
|
|
31
|
+
if (p.isAbsolute(options.secretPath)) {
|
|
32
|
+
return options.secretPath;
|
|
33
|
+
}
|
|
34
|
+
return p.resolve(getCwd(), options.secretPath);
|
|
35
|
+
}
|
|
36
|
+
const name = options?.name ?? "default";
|
|
37
|
+
const defaultPath = `${DEFAULT_SECRET_DIR}/${name}-secret.json`;
|
|
38
|
+
return p.resolve(getCwd(), defaultPath);
|
|
39
|
+
}
|
|
40
|
+
var noop = () => {
|
|
41
|
+
};
|
|
42
|
+
function getLogger(options) {
|
|
43
|
+
if (options?.enableLogging === false) {
|
|
44
|
+
return { warn: noop, error: noop };
|
|
45
|
+
}
|
|
46
|
+
return options?.logger ?? { warn: console.warn, error: console.error };
|
|
47
|
+
}
|
|
48
|
+
function getLogPrefix(options) {
|
|
49
|
+
const name = options?.name ?? "Secret";
|
|
50
|
+
return `[${name}Persistence]`;
|
|
51
|
+
}
|
|
52
|
+
async function loadSecret(options) {
|
|
53
|
+
if (!isSecretPersistenceEnabled(options)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const secretPath = resolveSecretPath(options);
|
|
57
|
+
const logger = getLogger(options);
|
|
58
|
+
const prefix = getLogPrefix(options);
|
|
59
|
+
try {
|
|
60
|
+
const content = await readFile(secretPath);
|
|
61
|
+
const data = JSON.parse(content);
|
|
62
|
+
const validation = validateSecretData(data);
|
|
63
|
+
if (!validation.valid) {
|
|
64
|
+
logger.warn(`${prefix} Invalid secret file format at ${secretPath}: ${validation.error}, will regenerate`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return data;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error.code === "ENOENT") {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
logger.warn(`${prefix} Failed to load secret from ${secretPath}: ${error.message}`);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function saveSecret(secretData, options) {
|
|
77
|
+
if (!isSecretPersistenceEnabled(options)) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
const secretPath = resolveSecretPath(options);
|
|
81
|
+
const dir = getPath().dirname(secretPath);
|
|
82
|
+
const tempPath = `${secretPath}.tmp.${Date.now()}.${base64urlEncode(randomBytes(8))}`;
|
|
83
|
+
const logger = getLogger(options);
|
|
84
|
+
const prefix = getLogPrefix(options);
|
|
85
|
+
try {
|
|
86
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
87
|
+
const content = JSON.stringify(secretData, null, 2);
|
|
88
|
+
await writeFile(tempPath, content, { mode: 384 });
|
|
89
|
+
await rename(tempPath, secretPath);
|
|
90
|
+
return true;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
logger.error(`${prefix} Failed to save secret to ${secretPath}: ${error.message}`);
|
|
93
|
+
try {
|
|
94
|
+
await unlink(tempPath);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function deleteSecret(options) {
|
|
101
|
+
const secretPath = resolveSecretPath(options);
|
|
102
|
+
const logger = getLogger(options);
|
|
103
|
+
const prefix = getLogPrefix(options);
|
|
104
|
+
try {
|
|
105
|
+
await unlink(secretPath);
|
|
106
|
+
return true;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.code === "ENOENT") {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
logger.warn(`${prefix} Failed to delete secret at ${secretPath}: ${error.message}`);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function generateSecret(bytes = DEFAULT_SECRET_BYTES) {
|
|
116
|
+
return base64urlEncode(randomBytes(bytes));
|
|
117
|
+
}
|
|
118
|
+
function createSecretData(options) {
|
|
119
|
+
const secretBytes = options?.secretBytes ?? DEFAULT_SECRET_BYTES;
|
|
120
|
+
return {
|
|
121
|
+
secret: generateSecret(secretBytes),
|
|
122
|
+
createdAt: Date.now(),
|
|
123
|
+
version: 1
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
var secretCache = /* @__PURE__ */ new Map();
|
|
127
|
+
var generationPromises = /* @__PURE__ */ new Map();
|
|
128
|
+
async function getOrCreateSecret(options) {
|
|
129
|
+
const cacheKey = resolveSecretPath(options);
|
|
130
|
+
const cached = secretCache.get(cacheKey);
|
|
131
|
+
if (cached) {
|
|
132
|
+
return cached;
|
|
133
|
+
}
|
|
134
|
+
const existing = generationPromises.get(cacheKey);
|
|
135
|
+
if (existing) {
|
|
136
|
+
return existing;
|
|
137
|
+
}
|
|
138
|
+
const promise = (async () => {
|
|
139
|
+
try {
|
|
140
|
+
const loaded = await loadSecret(options);
|
|
141
|
+
if (loaded) {
|
|
142
|
+
secretCache.set(cacheKey, loaded.secret);
|
|
143
|
+
return loaded.secret;
|
|
144
|
+
}
|
|
145
|
+
const secretData = createSecretData(options);
|
|
146
|
+
if (isSecretPersistenceEnabled(options)) {
|
|
147
|
+
await saveSecret(secretData, options);
|
|
148
|
+
}
|
|
149
|
+
secretCache.set(cacheKey, secretData.secret);
|
|
150
|
+
return secretData.secret;
|
|
151
|
+
} finally {
|
|
152
|
+
generationPromises.delete(cacheKey);
|
|
153
|
+
}
|
|
154
|
+
})();
|
|
155
|
+
generationPromises.set(cacheKey, promise);
|
|
156
|
+
return promise;
|
|
157
|
+
}
|
|
158
|
+
function clearCachedSecret(options) {
|
|
159
|
+
if (options) {
|
|
160
|
+
const cacheKey = resolveSecretPath(options);
|
|
161
|
+
secretCache.delete(cacheKey);
|
|
162
|
+
} else {
|
|
163
|
+
secretCache.clear();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function isSecretCached(options) {
|
|
167
|
+
const cacheKey = resolveSecretPath(options);
|
|
168
|
+
return secretCache.has(cacheKey);
|
|
169
|
+
}
|
|
170
|
+
export {
|
|
171
|
+
clearCachedSecret,
|
|
172
|
+
createSecretData,
|
|
173
|
+
deleteSecret,
|
|
174
|
+
generateSecret,
|
|
175
|
+
getOrCreateSecret,
|
|
176
|
+
isSecretCached,
|
|
177
|
+
isSecretPersistenceEnabled,
|
|
178
|
+
loadSecret,
|
|
179
|
+
resolveSecretPath,
|
|
180
|
+
saveSecret
|
|
181
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// libs/utils/src/crypto/secret-persistence/schema.ts
|
|
2
|
+
import { internalZ as z } from "@frontmcp/lazy-zod";
|
|
3
|
+
var secretDataSchema = z.object({
|
|
4
|
+
secret: z.string().base64url().length(43),
|
|
5
|
+
// base64url of 32 bytes is exactly 43 chars
|
|
6
|
+
createdAt: z.number().positive().int(),
|
|
7
|
+
version: z.number().positive().int()
|
|
8
|
+
}).strict();
|
|
9
|
+
var MAX_CLOCK_DRIFT_MS = 6e4;
|
|
10
|
+
var MAX_SECRET_AGE_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
11
|
+
function validateSecretData(data) {
|
|
12
|
+
const result = secretDataSchema.safeParse(data);
|
|
13
|
+
if (!result.success) {
|
|
14
|
+
return {
|
|
15
|
+
valid: false,
|
|
16
|
+
error: result.error.issues[0]?.message ?? "Invalid secret structure"
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const parsed = result.data;
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
if (parsed.createdAt > now + MAX_CLOCK_DRIFT_MS) {
|
|
22
|
+
return { valid: false, error: "createdAt is in the future" };
|
|
23
|
+
}
|
|
24
|
+
if (parsed.createdAt < now - MAX_SECRET_AGE_MS) {
|
|
25
|
+
return { valid: false, error: "createdAt is too old" };
|
|
26
|
+
}
|
|
27
|
+
return { valid: true };
|
|
28
|
+
}
|
|
29
|
+
function parseSecretData(data) {
|
|
30
|
+
const validation = validateSecretData(data);
|
|
31
|
+
if (!validation.valid) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
parseSecretData,
|
|
38
|
+
secretDataSchema,
|
|
39
|
+
validateSecretData
|
|
40
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// libs/utils/src/crypto/web-rsa.ts
|
|
2
|
+
import { isRsaPssAlg, jwtAlgToWebCryptoAlg } from "./jwt-alg.mjs";
|
|
3
|
+
async function rsaVerifyBrowser(jwtAlg, data, publicJwk, signature) {
|
|
4
|
+
if (typeof globalThis.crypto?.subtle === "undefined") {
|
|
5
|
+
throw new Error("WebCrypto API (crypto.subtle) is not available in this environment");
|
|
6
|
+
}
|
|
7
|
+
const webAlg = jwtAlgToWebCryptoAlg(jwtAlg);
|
|
8
|
+
const isPss = isRsaPssAlg(jwtAlg);
|
|
9
|
+
const algorithm = {
|
|
10
|
+
name: isPss ? "RSA-PSS" : "RSASSA-PKCS1-v1_5",
|
|
11
|
+
hash: { name: webAlg }
|
|
12
|
+
};
|
|
13
|
+
const key = await globalThis.crypto.subtle.importKey("jwk", publicJwk, algorithm, false, ["verify"]);
|
|
14
|
+
const verifyAlgorithm = isPss ? { name: "RSA-PSS", saltLength: getSaltLength(jwtAlg) } : { name: "RSASSA-PKCS1-v1_5" };
|
|
15
|
+
const sigBuf = new Uint8Array(signature).buffer;
|
|
16
|
+
const dataBuf = new Uint8Array(data).buffer;
|
|
17
|
+
return globalThis.crypto.subtle.verify(verifyAlgorithm, key, sigBuf, dataBuf);
|
|
18
|
+
}
|
|
19
|
+
function getSaltLength(jwtAlg) {
|
|
20
|
+
switch (jwtAlg) {
|
|
21
|
+
case "PS256":
|
|
22
|
+
return 32;
|
|
23
|
+
// SHA-256 digest length
|
|
24
|
+
case "PS384":
|
|
25
|
+
return 48;
|
|
26
|
+
// SHA-384 digest length
|
|
27
|
+
case "PS512":
|
|
28
|
+
return 64;
|
|
29
|
+
// SHA-512 digest length
|
|
30
|
+
default:
|
|
31
|
+
return 32;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
rsaVerifyBrowser
|
|
36
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// libs/utils/src/env/browser-env.ts
|
|
2
|
+
import { hasEdgeGlobalScope } from "./edge-scope.mjs";
|
|
3
|
+
var NODE_ENV_KEY = "NODE_ENV";
|
|
4
|
+
function bundlerDefinedNodeEnv() {
|
|
5
|
+
try {
|
|
6
|
+
return process.env["NODE_ENV"] || void 0;
|
|
7
|
+
} catch {
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function getEnv(_key, defaultValue) {
|
|
12
|
+
return defaultValue;
|
|
13
|
+
}
|
|
14
|
+
function getCwd() {
|
|
15
|
+
return "/";
|
|
16
|
+
}
|
|
17
|
+
function getNodeEnv() {
|
|
18
|
+
const shim = globalThis.process;
|
|
19
|
+
return shim?.env?.[NODE_ENV_KEY] || bundlerDefinedNodeEnv();
|
|
20
|
+
}
|
|
21
|
+
function isProduction() {
|
|
22
|
+
return getNodeEnv() === "production";
|
|
23
|
+
}
|
|
24
|
+
function isDevelopment() {
|
|
25
|
+
return getNodeEnv() === "development";
|
|
26
|
+
}
|
|
27
|
+
function getEnvFlag(_key) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
function isDebug() {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
function setEnv(_key, _value) {
|
|
34
|
+
}
|
|
35
|
+
function isEdgeRuntime() {
|
|
36
|
+
return hasEdgeGlobalScope();
|
|
37
|
+
}
|
|
38
|
+
function isServerless() {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
function supportsAnsi() {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
getCwd,
|
|
46
|
+
getEnv,
|
|
47
|
+
getEnvFlag,
|
|
48
|
+
getNodeEnv,
|
|
49
|
+
isDebug,
|
|
50
|
+
isDevelopment,
|
|
51
|
+
isEdgeRuntime,
|
|
52
|
+
isProduction,
|
|
53
|
+
isServerless,
|
|
54
|
+
setEnv,
|
|
55
|
+
supportsAnsi
|
|
56
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// libs/utils/src/env/browser-runtime-context.ts
|
|
2
|
+
import { internalZ as z } from "@frontmcp/lazy-zod";
|
|
3
|
+
import { getNodeEnv } from "./browser-env.mjs";
|
|
4
|
+
var entryAvailabilitySchema = z.object({
|
|
5
|
+
os: z.array(z.string().min(1)).optional(),
|
|
6
|
+
platform: z.array(z.string().min(1)).optional(),
|
|
7
|
+
runtime: z.array(z.string().min(1)).optional(),
|
|
8
|
+
deployment: z.array(z.string().min(1)).optional(),
|
|
9
|
+
provider: z.array(z.string().min(1)).optional(),
|
|
10
|
+
target: z.array(z.string().min(1)).optional(),
|
|
11
|
+
surface: z.array(z.enum(["mcp", "cli", "http-trigger", "job", "agent", "webmcp"])).optional(),
|
|
12
|
+
env: z.array(z.string().min(1)).optional()
|
|
13
|
+
}).strict();
|
|
14
|
+
var PROCESS_AXES = [
|
|
15
|
+
{ key: "os", ctxKey: "os" },
|
|
16
|
+
{ key: "platform", ctxKey: "platform" },
|
|
17
|
+
{ key: "runtime", ctxKey: "runtime" },
|
|
18
|
+
{ key: "deployment", ctxKey: "deployment" },
|
|
19
|
+
{ key: "provider", ctxKey: "provider" },
|
|
20
|
+
{ key: "target", ctxKey: "target" },
|
|
21
|
+
{ key: "env", ctxKey: "env" }
|
|
22
|
+
];
|
|
23
|
+
function isEntryAvailable(availability, ctx, callCtx) {
|
|
24
|
+
if (!availability) return true;
|
|
25
|
+
for (const { key, ctxKey } of PROCESS_AXES) {
|
|
26
|
+
const allowed = availability[key];
|
|
27
|
+
if (allowed === void 0) continue;
|
|
28
|
+
if (allowed.length === 0) return false;
|
|
29
|
+
if (!allowed.includes(ctx[ctxKey])) return false;
|
|
30
|
+
}
|
|
31
|
+
if (availability.surface !== void 0) {
|
|
32
|
+
if (availability.surface.length === 0) return false;
|
|
33
|
+
if (callCtx?.surface !== void 0 && !availability.surface.includes(callCtx.surface)) return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
function checkEntryAvailability(availability, ctx, callCtx) {
|
|
38
|
+
if (!availability) return { available: true, missingAxes: [] };
|
|
39
|
+
const missing = [];
|
|
40
|
+
for (const { key, ctxKey } of PROCESS_AXES) {
|
|
41
|
+
const allowed = availability[key];
|
|
42
|
+
if (allowed === void 0) continue;
|
|
43
|
+
if (allowed.length === 0 || !allowed.includes(ctx[ctxKey])) missing.push(key);
|
|
44
|
+
}
|
|
45
|
+
if (availability.surface !== void 0) {
|
|
46
|
+
if (availability.surface.length === 0) missing.push("surface");
|
|
47
|
+
else if (callCtx?.surface !== void 0 && !availability.surface.includes(callCtx.surface)) missing.push("surface");
|
|
48
|
+
}
|
|
49
|
+
return { available: missing.length === 0, missingAxes: missing };
|
|
50
|
+
}
|
|
51
|
+
function detectRuntimeContext() {
|
|
52
|
+
return {
|
|
53
|
+
os: "browser",
|
|
54
|
+
platform: "browser",
|
|
55
|
+
runtime: "browser",
|
|
56
|
+
deployment: "standalone",
|
|
57
|
+
provider: "bare",
|
|
58
|
+
target: "browser",
|
|
59
|
+
env: getNodeEnv() || "development"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
var cached;
|
|
63
|
+
function getRuntimeContext() {
|
|
64
|
+
if (!cached) {
|
|
65
|
+
const detected = detectRuntimeContext();
|
|
66
|
+
Object.defineProperty(detected, "env", {
|
|
67
|
+
get: () => getNodeEnv() || "development",
|
|
68
|
+
enumerable: true,
|
|
69
|
+
configurable: true
|
|
70
|
+
});
|
|
71
|
+
cached = detected;
|
|
72
|
+
}
|
|
73
|
+
return cached;
|
|
74
|
+
}
|
|
75
|
+
function resetRuntimeContext() {
|
|
76
|
+
cached = void 0;
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
checkEntryAvailability,
|
|
80
|
+
detectRuntimeContext,
|
|
81
|
+
entryAvailabilitySchema,
|
|
82
|
+
getRuntimeContext,
|
|
83
|
+
isEntryAvailable,
|
|
84
|
+
resetRuntimeContext
|
|
85
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// libs/utils/src/env/build-target.ts
|
|
2
|
+
var cached;
|
|
3
|
+
function getBuildTarget() {
|
|
4
|
+
if (cached) return cached;
|
|
5
|
+
cached = resolveBuildTarget();
|
|
6
|
+
return cached;
|
|
7
|
+
}
|
|
8
|
+
function resolveBuildTarget() {
|
|
9
|
+
const globalAny = globalThis;
|
|
10
|
+
const fromGlobal = globalAny.FRONTMCP_BUILD_TARGET;
|
|
11
|
+
if (typeof fromGlobal === "string" && isKnownTarget(fromGlobal)) return fromGlobal;
|
|
12
|
+
const fromEnv = typeof process !== "undefined" ? process.env?.["FRONTMCP_BUILD_TARGET"] : void 0;
|
|
13
|
+
if (typeof fromEnv === "string" && isKnownTarget(fromEnv)) return fromEnv;
|
|
14
|
+
return "unknown";
|
|
15
|
+
}
|
|
16
|
+
function isKnownTarget(value) {
|
|
17
|
+
return value === "unknown" || value === "node" || value === "distributed" || value === "cli" || value === "vercel" || value === "lambda" || value === "cloudflare" || value === "browser" || value === "sdk" || value === "mcpb";
|
|
18
|
+
}
|
|
19
|
+
function resetBuildTargetCacheForTesting() {
|
|
20
|
+
cached = void 0;
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
getBuildTarget,
|
|
24
|
+
resetBuildTargetCacheForTesting
|
|
25
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// libs/utils/src/env/edge-scope.ts
|
|
2
|
+
function hasEdgeGlobalScope() {
|
|
3
|
+
if (typeof globalThis === "undefined") return false;
|
|
4
|
+
if ("EdgeRuntime" in globalThis) return true;
|
|
5
|
+
if (!("caches" in globalThis) || "window" in globalThis) return false;
|
|
6
|
+
return !isBrowserWorkerScope();
|
|
7
|
+
}
|
|
8
|
+
function isBrowserWorkerScope() {
|
|
9
|
+
const userAgent = typeof navigator === "undefined" ? void 0 : navigator.userAgent;
|
|
10
|
+
if (userAgent === void 0 || userAgent === "Cloudflare-Workers") return false;
|
|
11
|
+
return ["DedicatedWorkerGlobalScope", "SharedWorkerGlobalScope", "ServiceWorkerGlobalScope"].some(
|
|
12
|
+
(scopeName) => scopeName in globalThis
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
hasEdgeGlobalScope
|
|
17
|
+
};
|