toolcraft 0.0.315 → 0.0.317
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/composition.json +3 -3
- package/dist/composition.json +3 -3
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/README.md +32 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +9 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +2 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.d.ts +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.js +45 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +71 -9
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/resource-bound-store.d.ts +9 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/resource-bound-store.js +108 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/types.d.ts +13 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +1 -1
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/dist/index.d.ts +13 -1
- package/node_modules/tiny-mcp-client/dist/index.js +1364 -1159
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
|
@@ -3395,1296 +3395,1444 @@ var SubscriptionManager = class {
|
|
|
3395
3395
|
}
|
|
3396
3396
|
};
|
|
3397
3397
|
|
|
3398
|
-
// ../mcp-oauth/dist/client/
|
|
3399
|
-
|
|
3400
|
-
if (scope === void 0)
|
|
3401
|
-
return void 0;
|
|
3402
|
-
if (typeof scope !== "string" || [...scope].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126 || char === '"' || char === "\\"))
|
|
3403
|
-
throw new Error("Invalid OAuth scope syntax");
|
|
3404
|
-
const normalized = [...new Set(scope.split(" ").filter(Boolean))].sort().join(" ");
|
|
3405
|
-
return normalized || void 0;
|
|
3406
|
-
}
|
|
3398
|
+
// ../mcp-oauth/dist/client/loopback-authorization.js
|
|
3399
|
+
import http from "node:http";
|
|
3407
3400
|
|
|
3408
|
-
// ../mcp-oauth/dist/client/
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3401
|
+
// ../mcp-oauth/dist/client/authorization-state.js
|
|
3402
|
+
import crypto from "node:crypto";
|
|
3403
|
+
function createAuthorizationState(input) {
|
|
3404
|
+
const payload = {
|
|
3405
|
+
v: 1,
|
|
3406
|
+
n: crypto.randomBytes(16).toString("base64url"),
|
|
3407
|
+
i: input.issuer,
|
|
3408
|
+
r: input.requireIssuer
|
|
3409
|
+
};
|
|
3410
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
3415
3411
|
}
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
const invalid = () => new Error("Invalid OAuth client registration metadata");
|
|
3420
|
-
let nodes = 0;
|
|
3421
|
-
function copy(input, depth) {
|
|
3422
|
-
if (++nodes > 2e4 || depth > 64)
|
|
3423
|
-
throw invalid();
|
|
3424
|
-
if (input === null || typeof input === "boolean" || typeof input === "string")
|
|
3425
|
-
return input;
|
|
3426
|
-
if (typeof input === "number" && Number.isFinite(input))
|
|
3427
|
-
return input;
|
|
3428
|
-
if (typeof input !== "object" || input === null)
|
|
3429
|
-
throw invalid();
|
|
3430
|
-
const descriptors = Object.getOwnPropertyDescriptors(input);
|
|
3431
|
-
if (Array.isArray(input)) {
|
|
3432
|
-
const length = descriptors.length?.value;
|
|
3433
|
-
if (length > 2e4)
|
|
3434
|
-
throw invalid();
|
|
3435
|
-
const result2 = [];
|
|
3436
|
-
for (let index = 0; index < length; index++) {
|
|
3437
|
-
const descriptor = descriptors[String(index)];
|
|
3438
|
-
if (descriptor === void 0 || !Object.hasOwn(descriptor, "value"))
|
|
3439
|
-
throw invalid();
|
|
3440
|
-
result2.push(copy(descriptor.value, depth + 1));
|
|
3441
|
-
}
|
|
3442
|
-
return result2;
|
|
3443
|
-
}
|
|
3444
|
-
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
|
|
3445
|
-
throw invalid();
|
|
3446
|
-
return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key2, descriptor]) => {
|
|
3447
|
-
if (!Object.hasOwn(descriptor, "value"))
|
|
3448
|
-
throw invalid();
|
|
3449
|
-
return [key2, copy(descriptor.value, depth + 1)];
|
|
3450
|
-
}));
|
|
3451
|
-
}
|
|
3452
|
-
let result;
|
|
3453
|
-
try {
|
|
3454
|
-
result = copy(value, 0);
|
|
3455
|
-
} catch {
|
|
3456
|
-
throw invalid();
|
|
3457
|
-
}
|
|
3458
|
-
if (typeof result !== "object" || result === null || Array.isArray(result))
|
|
3459
|
-
throw invalid();
|
|
3460
|
-
const record2 = result;
|
|
3461
|
-
if (!Object.hasOwn(record2, "client_id") || typeof record2.client_id !== "string" || record2.client_id.trim() === "")
|
|
3462
|
-
throw new Error("OAuth client registration response missing client_id");
|
|
3463
|
-
for (const key2 of [
|
|
3464
|
-
"client_id",
|
|
3465
|
-
"client_secret",
|
|
3466
|
-
"token_endpoint_auth_method",
|
|
3467
|
-
"application_type",
|
|
3468
|
-
"client_name",
|
|
3469
|
-
"client_uri",
|
|
3470
|
-
"logo_uri",
|
|
3471
|
-
"scope",
|
|
3472
|
-
"tos_uri",
|
|
3473
|
-
"policy_uri",
|
|
3474
|
-
"jwks_uri",
|
|
3475
|
-
"software_id",
|
|
3476
|
-
"software_version",
|
|
3477
|
-
"software_statement",
|
|
3478
|
-
"registration_access_token",
|
|
3479
|
-
"registration_client_uri",
|
|
3480
|
-
"issuer"
|
|
3481
|
-
]) {
|
|
3482
|
-
if (Object.hasOwn(record2, key2) && record2[key2] !== null && typeof record2[key2] !== "string")
|
|
3483
|
-
throw invalid();
|
|
3484
|
-
}
|
|
3485
|
-
if (typeof record2.client_secret === "string" && record2.client_secret.trim() === "")
|
|
3486
|
-
throw invalid();
|
|
3487
|
-
for (const key2 of ["redirect_uris", "grant_types", "response_types", "contacts"]) {
|
|
3488
|
-
const entry = record2[key2];
|
|
3489
|
-
if (Object.hasOwn(record2, key2) && entry !== null && (!Array.isArray(entry) || entry.some((item) => typeof item !== "string")))
|
|
3490
|
-
throw invalid();
|
|
3491
|
-
}
|
|
3492
|
-
for (const key2 of ["client_id_issued_at", "client_secret_expires_at"]) {
|
|
3493
|
-
const entry = record2[key2];
|
|
3494
|
-
if (Object.hasOwn(record2, key2) && entry !== null && (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 0))
|
|
3495
|
-
throw invalid();
|
|
3412
|
+
function parseAuthorizationState(value) {
|
|
3413
|
+
if (value === null || value.length === 0) {
|
|
3414
|
+
return null;
|
|
3496
3415
|
}
|
|
3497
3416
|
try {
|
|
3498
|
-
|
|
3417
|
+
const decoded = Buffer.from(value, "base64url").toString("utf8");
|
|
3418
|
+
const parsed = JSON.parse(decoded);
|
|
3419
|
+
if (!isObjectRecord(parsed)) {
|
|
3420
|
+
return null;
|
|
3421
|
+
}
|
|
3422
|
+
const version = getOwnEntry(parsed, "v");
|
|
3423
|
+
const nonce = getOwnEntry(parsed, "n");
|
|
3424
|
+
const issuer = getOwnEntry(parsed, "i");
|
|
3425
|
+
const requireIssuer = getOwnEntry(parsed, "r");
|
|
3426
|
+
if (version !== 1 || typeof nonce !== "string" || nonce.length === 0 || typeof issuer !== "string" || issuer.length === 0 || typeof requireIssuer !== "boolean") {
|
|
3427
|
+
return null;
|
|
3428
|
+
}
|
|
3429
|
+
return {
|
|
3430
|
+
issuer,
|
|
3431
|
+
requireIssuer
|
|
3432
|
+
};
|
|
3499
3433
|
} catch {
|
|
3500
|
-
throw invalid();
|
|
3501
|
-
}
|
|
3502
|
-
if (Buffer.byteLength(JSON.stringify(record2), "utf8") > 64 * 1024)
|
|
3503
|
-
throw invalid();
|
|
3504
|
-
return record2;
|
|
3505
|
-
}
|
|
3506
|
-
function normalizeStoredOAuthClient(value) {
|
|
3507
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
3508
|
-
return null;
|
|
3509
|
-
const record2 = value;
|
|
3510
|
-
const clientId = Object.hasOwn(record2, "clientId") ? record2.clientId : void 0;
|
|
3511
|
-
const clientSecret = Object.hasOwn(record2, "clientSecret") ? record2.clientSecret : void 0;
|
|
3512
|
-
if (typeof clientId !== "string" || clientId.trim() === "" || clientSecret !== void 0 && (typeof clientSecret !== "string" || clientSecret.trim() === ""))
|
|
3513
3434
|
return null;
|
|
3514
|
-
const client = { clientId: clientId.trim(), ...clientSecret === void 0 ? {} : { clientSecret: clientSecret.trim() } };
|
|
3515
|
-
const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record2, "tokenEndpointAuthMethod") ? record2.tokenEndpointAuthMethod : void 0);
|
|
3516
|
-
if (Object.hasOwn(record2, "registration") && record2.registration !== void 0) {
|
|
3517
|
-
const registration = parseOAuthClientRegistration(record2.registration);
|
|
3518
|
-
const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : void 0;
|
|
3519
|
-
if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
|
|
3520
|
-
throw new Error("OAuth client registration does not match the client identity");
|
|
3521
|
-
client.registration = registration;
|
|
3522
|
-
const registrationMethod = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(registration, "token_endpoint_auth_method") ? registration.token_endpoint_auth_method : void 0);
|
|
3523
|
-
if (method !== void 0 && registrationMethod !== void 0 && method !== registrationMethod)
|
|
3524
|
-
throw new Error("OAuth token endpoint authentication conflicts with the client registration");
|
|
3525
|
-
if (registrationMethod !== void 0)
|
|
3526
|
-
client.tokenEndpointAuthMethod = registrationMethod;
|
|
3527
3435
|
}
|
|
3528
|
-
if (method !== void 0)
|
|
3529
|
-
client.tokenEndpointAuthMethod = method;
|
|
3530
|
-
return client;
|
|
3531
3436
|
}
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
// ../auth-store/dist/encrypted-file-store.js
|
|
3538
|
-
import { createCipheriv, createDecipheriv, randomBytes, randomUUID as randomUUID2, scrypt } from "node:crypto";
|
|
3539
|
-
import { promises as fs } from "node:fs";
|
|
3540
|
-
import { homedir, hostname, userInfo } from "node:os";
|
|
3541
|
-
import path2 from "node:path";
|
|
3542
|
-
|
|
3543
|
-
// ../auth-store/dist/error-codes.js
|
|
3544
|
-
function hasOwnErrorCode(error, code) {
|
|
3545
|
-
return error instanceof Error && Object.prototype.hasOwnProperty.call(error, "code") && error.code === code;
|
|
3437
|
+
function isObjectRecord(value) {
|
|
3438
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3439
|
+
}
|
|
3440
|
+
function getOwnEntry(record2, key2) {
|
|
3441
|
+
return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
|
|
3546
3442
|
}
|
|
3547
3443
|
|
|
3548
|
-
// ../
|
|
3549
|
-
|
|
3550
|
-
import path from "node:path";
|
|
3551
|
-
async function withSecretStoreFileLock(fs2, lockDirectory, operation, options = {}) {
|
|
3552
|
-
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
3553
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2147483647)
|
|
3554
|
-
throw new Error("Invalid secret-store transaction lock timeout");
|
|
3444
|
+
// ../mcp-oauth/dist/client/loopback-authorization.js
|
|
3445
|
+
async function createLoopbackAuthorizationSession(options = {}) {
|
|
3555
3446
|
options.signal?.throwIfAborted();
|
|
3556
|
-
const
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
const
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3447
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
3448
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
|
|
3449
|
+
throw new Error("OAuth authorization timeoutMs must be a positive supported timer interval");
|
|
3450
|
+
const target = loopbackTarget(options);
|
|
3451
|
+
const server = options.createServer ? options.createServer() : http.createServer();
|
|
3452
|
+
const controller = new AbortController();
|
|
3453
|
+
let closed = false;
|
|
3454
|
+
let used = false;
|
|
3455
|
+
const callerAbort = () => controller.abort(options.signal?.reason);
|
|
3456
|
+
const teardown = () => {
|
|
3457
|
+
if (closed)
|
|
3458
|
+
return;
|
|
3459
|
+
closed = true;
|
|
3460
|
+
clearTimeout(timer);
|
|
3461
|
+
options.signal?.removeEventListener("abort", callerAbort);
|
|
3462
|
+
server.closeAllConnections?.();
|
|
3463
|
+
server.close();
|
|
3464
|
+
};
|
|
3465
|
+
const timer = setTimeout(() => controller.abort(new Error("OAuth authorization timed out")), timeoutMs);
|
|
3466
|
+
timer.unref?.();
|
|
3467
|
+
controller.signal.addEventListener("abort", teardown, { once: true });
|
|
3468
|
+
options.signal?.addEventListener("abort", callerAbort, { once: true });
|
|
3469
|
+
if (options.signal?.aborted)
|
|
3470
|
+
callerAbort();
|
|
3471
|
+
let port;
|
|
3567
3472
|
try {
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3473
|
+
port = await startServer(server, target.port, target.host, controller.signal);
|
|
3474
|
+
} catch (error) {
|
|
3475
|
+
controller.abort(error);
|
|
3476
|
+
throw error;
|
|
3477
|
+
}
|
|
3478
|
+
const redirectUri = options.redirectUri ?? `http://127.0.0.1:${port}${target.callbackPath}`;
|
|
3479
|
+
return {
|
|
3480
|
+
redirectUri,
|
|
3481
|
+
async waitForCode(authorizationUrl) {
|
|
3482
|
+
controller.signal.throwIfAborted();
|
|
3483
|
+
if (used)
|
|
3484
|
+
throw new Error("OAuth authorization session has already been used");
|
|
3485
|
+
used = true;
|
|
3486
|
+
try {
|
|
3487
|
+
return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
|
|
3488
|
+
} finally {
|
|
3489
|
+
clearTimeout(timer);
|
|
3490
|
+
}
|
|
3491
|
+
},
|
|
3492
|
+
close() {
|
|
3493
|
+
controller.abort(new Error("OAuth authorization session closed"));
|
|
3574
3494
|
}
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3495
|
+
};
|
|
3496
|
+
}
|
|
3497
|
+
function loopbackTarget(options) {
|
|
3498
|
+
if (options.redirectUri !== void 0) {
|
|
3499
|
+
let url;
|
|
3580
3500
|
try {
|
|
3581
|
-
|
|
3582
|
-
} catch (
|
|
3583
|
-
|
|
3584
|
-
temporaryCreated = false;
|
|
3585
|
-
throw error;
|
|
3586
|
-
}
|
|
3587
|
-
await fs2.rename(temporaryPath, claimPath);
|
|
3588
|
-
temporaryCreated = false;
|
|
3589
|
-
for (; ; ) {
|
|
3590
|
-
options.signal?.throwIfAborted();
|
|
3591
|
-
const peers = await readClaims(fs2, directory, name);
|
|
3592
|
-
if (!peers.some((peer) => peer.ticket === null || peer.ticket < ticket || peer.ticket === ticket && peer.name < name))
|
|
3593
|
-
break;
|
|
3594
|
-
const remaining = deadline - performance.now();
|
|
3595
|
-
if (remaining <= 0)
|
|
3596
|
-
throw new Error("Timed out waiting for secret-store transaction lock");
|
|
3597
|
-
await new Promise((resolve, reject) => {
|
|
3598
|
-
const abort = () => {
|
|
3599
|
-
clearTimeout(timer);
|
|
3600
|
-
options.signal?.removeEventListener("abort", abort);
|
|
3601
|
-
reject(options.signal?.reason);
|
|
3602
|
-
};
|
|
3603
|
-
const timer = setTimeout(() => {
|
|
3604
|
-
options.signal?.removeEventListener("abort", abort);
|
|
3605
|
-
resolve();
|
|
3606
|
-
}, Math.min(10, remaining));
|
|
3607
|
-
options.signal?.addEventListener("abort", abort, { once: true });
|
|
3608
|
-
if (options.signal?.aborted)
|
|
3609
|
-
abort();
|
|
3610
|
-
});
|
|
3501
|
+
url = new URL(options.redirectUri);
|
|
3502
|
+
} catch (cause) {
|
|
3503
|
+
throw new Error("Invalid OAuth loopback redirect URI", { cause });
|
|
3611
3504
|
}
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3505
|
+
const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
|
|
3506
|
+
if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password || url.hash || url.port === "0" || forbiddenQuery.some((name) => url.searchParams.has(name)) || [...options.redirectUri].some((char) => char.codePointAt(0) <= 32) || options.callbackPath !== void 0 && options.callbackPath !== url.pathname)
|
|
3507
|
+
throw new Error("Invalid OAuth loopback redirect URI");
|
|
3508
|
+
return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
|
|
3616
3509
|
}
|
|
3617
|
-
const
|
|
3618
|
-
|
|
3510
|
+
const callbackPath = options.callbackPath ?? "/callback";
|
|
3511
|
+
const parsed = new URL(callbackPath, "http://127.0.0.1");
|
|
3512
|
+
if (!callbackPath.startsWith("/") || parsed.origin !== "http://127.0.0.1" || parsed.pathname !== callbackPath || parsed.search || parsed.hash)
|
|
3513
|
+
throw new Error("Invalid OAuth loopback callback path");
|
|
3514
|
+
return { port: 0, host: "127.0.0.1", callbackPath };
|
|
3515
|
+
}
|
|
3516
|
+
async function startServer(server, port, host, signal) {
|
|
3517
|
+
signal.throwIfAborted();
|
|
3518
|
+
return new Promise((resolve, reject) => {
|
|
3519
|
+
const cleanup = () => {
|
|
3520
|
+
server.off("error", handleError);
|
|
3521
|
+
signal.removeEventListener("abort", aborted);
|
|
3522
|
+
};
|
|
3523
|
+
const handleError = (error) => {
|
|
3524
|
+
cleanup();
|
|
3525
|
+
reject(error);
|
|
3526
|
+
};
|
|
3527
|
+
const aborted = () => {
|
|
3528
|
+
cleanup();
|
|
3529
|
+
reject(signal.reason);
|
|
3530
|
+
};
|
|
3531
|
+
server.once("error", handleError);
|
|
3532
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
3619
3533
|
try {
|
|
3620
|
-
|
|
3534
|
+
server.listen(port, host, () => {
|
|
3535
|
+
cleanup();
|
|
3536
|
+
if (signal.aborted) {
|
|
3537
|
+
server.close();
|
|
3538
|
+
reject(signal.reason);
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
3541
|
+
const address = server.address();
|
|
3542
|
+
if (address === null || typeof address === "string") {
|
|
3543
|
+
reject(new Error("OAuth listener has no TCP address"));
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
resolve(address.port);
|
|
3547
|
+
});
|
|
3621
3548
|
} catch (error) {
|
|
3622
|
-
|
|
3623
|
-
|
|
3549
|
+
cleanup();
|
|
3550
|
+
reject(error);
|
|
3624
3551
|
}
|
|
3625
|
-
}
|
|
3626
|
-
if (cleanup.length)
|
|
3627
|
-
throw new AggregateError([..."error" in outcome ? [outcome.error] : [], ...cleanup], "Secret-store transaction lock cleanup failed");
|
|
3628
|
-
if ("error" in outcome)
|
|
3629
|
-
throw outcome.error;
|
|
3630
|
-
return outcome.result;
|
|
3552
|
+
});
|
|
3631
3553
|
}
|
|
3632
|
-
|
|
3554
|
+
function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath, signal) {
|
|
3555
|
+
signal.throwIfAborted();
|
|
3556
|
+
const expectedAuthorization = readExpectedAuthorizationCallback(authorizationUrl);
|
|
3557
|
+
return new Promise((resolve, reject) => {
|
|
3558
|
+
let settled = false;
|
|
3559
|
+
const settle = (fn) => {
|
|
3560
|
+
if (settled)
|
|
3561
|
+
return;
|
|
3562
|
+
settled = true;
|
|
3563
|
+
server.off("request", request);
|
|
3564
|
+
signal.removeEventListener("abort", aborted);
|
|
3565
|
+
fn();
|
|
3566
|
+
};
|
|
3567
|
+
const aborted = () => settle(() => reject(signal.reason));
|
|
3568
|
+
const request = (req, res) => {
|
|
3569
|
+
let url;
|
|
3570
|
+
try {
|
|
3571
|
+
url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
3572
|
+
} catch {
|
|
3573
|
+
res.writeHead(400);
|
|
3574
|
+
res.end("Invalid callback URL");
|
|
3575
|
+
return;
|
|
3576
|
+
}
|
|
3577
|
+
if (url.pathname !== callbackPath) {
|
|
3578
|
+
res.writeHead(404);
|
|
3579
|
+
res.end("Not found");
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
const callbackParameters = {
|
|
3583
|
+
code: url.searchParams.get("code"),
|
|
3584
|
+
error: url.searchParams.get("error"),
|
|
3585
|
+
errorDescription: url.searchParams.get("error_description"),
|
|
3586
|
+
state: url.searchParams.get("state"),
|
|
3587
|
+
iss: url.searchParams.get("iss")
|
|
3588
|
+
};
|
|
3589
|
+
try {
|
|
3590
|
+
validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
|
|
3591
|
+
if (callbackParameters.error !== null)
|
|
3592
|
+
throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
|
|
3593
|
+
const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
|
|
3594
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
3595
|
+
res.end(buildSuccessPage(options.landingPage));
|
|
3596
|
+
settle(() => resolve(code));
|
|
3597
|
+
} catch (error) {
|
|
3598
|
+
res.writeHead(400);
|
|
3599
|
+
res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
|
|
3600
|
+
settle(() => reject(error));
|
|
3601
|
+
}
|
|
3602
|
+
};
|
|
3603
|
+
server.on("request", request);
|
|
3604
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
3605
|
+
if (options.readLine !== void 0) {
|
|
3606
|
+
void Promise.resolve().then(() => settled ? void 0 : options.readLine()).then((input) => {
|
|
3607
|
+
if (settled)
|
|
3608
|
+
return;
|
|
3609
|
+
const callbackParameters = extractCallbackParametersFromInput(input);
|
|
3610
|
+
if (callbackParameters === null)
|
|
3611
|
+
throw new Error("OAuth callback missing authorization code");
|
|
3612
|
+
validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
|
|
3613
|
+
if (callbackParameters.error !== null)
|
|
3614
|
+
throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
|
|
3615
|
+
const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
|
|
3616
|
+
settle(() => resolve(code));
|
|
3617
|
+
}).catch((error) => settle(() => reject(error)));
|
|
3618
|
+
}
|
|
3619
|
+
if (options.openBrowser !== void 0) {
|
|
3620
|
+
void Promise.resolve().then(() => settled ? void 0 : options.openBrowser(authorizationUrl)).catch((error) => settle(() => reject(error)));
|
|
3621
|
+
}
|
|
3622
|
+
});
|
|
3623
|
+
}
|
|
3624
|
+
function extractCallbackParametersFromInput(input) {
|
|
3625
|
+
const trimmed = input.replaceAll("\r", "").replaceAll("\n", "").trim();
|
|
3626
|
+
if (trimmed.length === 0) {
|
|
3627
|
+
return null;
|
|
3628
|
+
}
|
|
3633
3629
|
try {
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3630
|
+
const url = new URL(trimmed);
|
|
3631
|
+
return {
|
|
3632
|
+
code: url.searchParams.get("code"),
|
|
3633
|
+
error: url.searchParams.get("error"),
|
|
3634
|
+
errorDescription: url.searchParams.get("error_description"),
|
|
3635
|
+
state: url.searchParams.get("state"),
|
|
3636
|
+
iss: url.searchParams.get("iss")
|
|
3637
|
+
};
|
|
3638
|
+
} catch {
|
|
3639
|
+
return {
|
|
3640
|
+
code: trimmed,
|
|
3641
|
+
error: null,
|
|
3642
|
+
errorDescription: null,
|
|
3643
|
+
state: null,
|
|
3644
|
+
iss: null
|
|
3645
|
+
};
|
|
3639
3646
|
}
|
|
3640
3647
|
}
|
|
3641
|
-
|
|
3642
|
-
const
|
|
3643
|
-
const
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3648
|
+
function readExpectedAuthorizationCallback(authorizationUrl) {
|
|
3649
|
+
const url = new URL(authorizationUrl);
|
|
3650
|
+
const state = url.searchParams.get("state");
|
|
3651
|
+
const parsedState = parseAuthorizationState(state);
|
|
3652
|
+
return {
|
|
3653
|
+
state,
|
|
3654
|
+
issuer: parsedState?.issuer ?? null,
|
|
3655
|
+
requireIssuer: parsedState?.requireIssuer ?? false
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
function validateAuthorizationCallbackParameters(callback, expected) {
|
|
3659
|
+
validateAuthorizationCallbackBinding(callback, expected);
|
|
3660
|
+
if (callback.code === null || callback.code.length === 0) {
|
|
3661
|
+
throw new Error("OAuth callback missing authorization code");
|
|
3649
3662
|
}
|
|
3663
|
+
return callback.code;
|
|
3650
3664
|
}
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
continue;
|
|
3656
|
-
const pidText = name.slice(0, name.indexOf("-"));
|
|
3657
|
-
const pid = Number(pidText);
|
|
3658
|
-
if (!Number.isSafeInteger(pid) || pid < 1 || String(pid) !== pidText)
|
|
3659
|
-
throw new Error("Malformed secret-store transaction lock owner");
|
|
3660
|
-
const target = path.join(directory, name);
|
|
3661
|
-
await assertNoSymbolicLink(fs2, target);
|
|
3662
|
-
let alive = true;
|
|
3663
|
-
try {
|
|
3664
|
-
process.kill(pid, 0);
|
|
3665
|
-
} catch (error) {
|
|
3666
|
-
alive = !hasOwnErrorCode(error, "ESRCH");
|
|
3667
|
-
}
|
|
3668
|
-
if (!alive) {
|
|
3669
|
-
try {
|
|
3670
|
-
await fs2.unlink(target);
|
|
3671
|
-
} catch (error) {
|
|
3672
|
-
if (!hasOwnErrorCode(error, "ENOENT"))
|
|
3673
|
-
throw error;
|
|
3674
|
-
}
|
|
3675
|
-
continue;
|
|
3676
|
-
}
|
|
3677
|
-
let ticket = null;
|
|
3678
|
-
let raw;
|
|
3679
|
-
try {
|
|
3680
|
-
raw = await fs2.readFile(target, "utf8");
|
|
3681
|
-
} catch (error) {
|
|
3682
|
-
if (hasOwnErrorCode(error, "ENOENT"))
|
|
3683
|
-
continue;
|
|
3684
|
-
throw error;
|
|
3665
|
+
function validateAuthorizationCallbackBinding(callback, expected) {
|
|
3666
|
+
if (expected.state !== null) {
|
|
3667
|
+
if (callback.state === null || callback.state.length === 0) {
|
|
3668
|
+
throw new Error("OAuth callback missing state");
|
|
3685
3669
|
}
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
if (value !== null && typeof value === "object" && "ticket" in value && typeof value.ticket === "number" && Number.isSafeInteger(value.ticket) && value.ticket > 0)
|
|
3689
|
-
ticket = value.ticket;
|
|
3690
|
-
} catch {
|
|
3670
|
+
if (callback.state !== expected.state) {
|
|
3671
|
+
throw new Error("OAuth callback state mismatch");
|
|
3691
3672
|
}
|
|
3692
|
-
claims.push({ name, ticket });
|
|
3693
3673
|
}
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
// ../auth-store/dist/encrypted-file-store.js
|
|
3698
|
-
var derivedKeyCache = /* @__PURE__ */ new Map();
|
|
3699
|
-
var ENCRYPTION_ALGORITHM = "aes-256-gcm";
|
|
3700
|
-
var ENCRYPTION_VERSION = 1;
|
|
3701
|
-
var ENCRYPTION_KEY_BYTES = 32;
|
|
3702
|
-
var ENCRYPTION_IV_BYTES = 12;
|
|
3703
|
-
var ENCRYPTION_AUTH_TAG_BYTES = 16;
|
|
3704
|
-
var ENCRYPTION_FILE_MODE = 384;
|
|
3705
|
-
var EncryptedFileStore = class {
|
|
3706
|
-
fs;
|
|
3707
|
-
filePath;
|
|
3708
|
-
symbolicLinkCheckStartPath;
|
|
3709
|
-
salt;
|
|
3710
|
-
getMachineIdentity;
|
|
3711
|
-
getRandomBytes;
|
|
3712
|
-
keyPromise = null;
|
|
3713
|
-
throwOnInvalidDocument;
|
|
3714
|
-
constructor(input) {
|
|
3715
|
-
this.fs = input.fs ?? fs;
|
|
3716
|
-
this.salt = input.salt;
|
|
3717
|
-
if (input.filePath === void 0) {
|
|
3718
|
-
const homeDirectory = (input.getHomeDirectory ?? homedir)();
|
|
3719
|
-
const defaultDirectory = input.defaultDirectory ?? ".auth-store";
|
|
3720
|
-
const defaultFileName = input.defaultFileName ?? "credentials.enc";
|
|
3721
|
-
assertSafeDefaultDirectory(defaultDirectory);
|
|
3722
|
-
assertSafeDefaultFileName(defaultFileName);
|
|
3723
|
-
this.filePath = path2.join(homeDirectory, defaultDirectory, defaultFileName);
|
|
3724
|
-
this.symbolicLinkCheckStartPath = resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory);
|
|
3725
|
-
} else {
|
|
3726
|
-
this.filePath = input.filePath;
|
|
3727
|
-
this.symbolicLinkCheckStartPath = null;
|
|
3728
|
-
}
|
|
3729
|
-
this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
|
|
3730
|
-
this.getRandomBytes = input.getRandomBytes ?? randomBytes;
|
|
3731
|
-
this.throwOnInvalidDocument = input.throwOnInvalidDocument ?? false;
|
|
3732
|
-
}
|
|
3733
|
-
async get() {
|
|
3734
|
-
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
3735
|
-
let rawDocument;
|
|
3736
|
-
try {
|
|
3737
|
-
rawDocument = await this.fs.readFile(this.filePath, "utf8");
|
|
3738
|
-
} catch (error) {
|
|
3739
|
-
if (isNotFoundError(error)) {
|
|
3740
|
-
return null;
|
|
3741
|
-
}
|
|
3742
|
-
throw error;
|
|
3743
|
-
}
|
|
3744
|
-
const document = parseEncryptedDocument(rawDocument);
|
|
3745
|
-
if (!document) {
|
|
3746
|
-
if (this.throwOnInvalidDocument)
|
|
3747
|
-
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
3748
|
-
return null;
|
|
3749
|
-
}
|
|
3750
|
-
const key2 = await this.getEncryptionKey();
|
|
3751
|
-
try {
|
|
3752
|
-
const iv = Buffer.from(document.iv, "base64");
|
|
3753
|
-
const authTag = Buffer.from(document.authTag, "base64");
|
|
3754
|
-
const ciphertext = Buffer.from(document.ciphertext, "base64");
|
|
3755
|
-
if (iv.byteLength !== ENCRYPTION_IV_BYTES || authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
|
|
3756
|
-
if (this.throwOnInvalidDocument)
|
|
3757
|
-
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
3758
|
-
return null;
|
|
3759
|
-
}
|
|
3760
|
-
const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key2, iv);
|
|
3761
|
-
decipher.setAuthTag(authTag);
|
|
3762
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
3763
|
-
return plaintext.toString("utf8");
|
|
3764
|
-
} catch {
|
|
3765
|
-
if (this.throwOnInvalidDocument)
|
|
3766
|
-
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
3767
|
-
return null;
|
|
3768
|
-
}
|
|
3769
|
-
}
|
|
3770
|
-
async withLock(operation, options = {}) {
|
|
3771
|
-
await this.assertCredentialPathHasNoSymbolicLinks(`${this.filePath}.lock`);
|
|
3772
|
-
if (this.fs.readdir === void 0)
|
|
3773
|
-
throw new Error("Secret-store transaction locks require filesystem readdir support");
|
|
3774
|
-
return withSecretStoreFileLock(this.fs, `${this.filePath}.lock`, operation, options);
|
|
3775
|
-
}
|
|
3776
|
-
async set(value) {
|
|
3777
|
-
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
3778
|
-
const key2 = await this.getEncryptionKey();
|
|
3779
|
-
const iv = this.getRandomBytes(ENCRYPTION_IV_BYTES);
|
|
3780
|
-
const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key2, iv);
|
|
3781
|
-
const ciphertext = Buffer.concat([
|
|
3782
|
-
cipher.update(value, "utf8"),
|
|
3783
|
-
cipher.final()
|
|
3784
|
-
]);
|
|
3785
|
-
const authTag = cipher.getAuthTag();
|
|
3786
|
-
const document = {
|
|
3787
|
-
version: ENCRYPTION_VERSION,
|
|
3788
|
-
iv: iv.toString("base64"),
|
|
3789
|
-
authTag: authTag.toString("base64"),
|
|
3790
|
-
ciphertext: ciphertext.toString("base64")
|
|
3791
|
-
};
|
|
3792
|
-
await this.fs.mkdir(path2.dirname(this.filePath), { recursive: true });
|
|
3793
|
-
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
3794
|
-
const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
3795
|
-
let temporaryCreated = false;
|
|
3796
|
-
try {
|
|
3797
|
-
await this.assertCredentialPathHasNoSymbolicLinks(temporaryPath);
|
|
3798
|
-
await this.fs.writeFile(temporaryPath, JSON.stringify(document), {
|
|
3799
|
-
encoding: "utf8",
|
|
3800
|
-
flag: "wx",
|
|
3801
|
-
mode: ENCRYPTION_FILE_MODE
|
|
3802
|
-
});
|
|
3803
|
-
temporaryCreated = true;
|
|
3804
|
-
await this.fs.chmod(temporaryPath, ENCRYPTION_FILE_MODE);
|
|
3805
|
-
await this.fs.rename(temporaryPath, this.filePath);
|
|
3806
|
-
} catch (error) {
|
|
3807
|
-
if (temporaryCreated || !isAlreadyExistsError(error)) {
|
|
3808
|
-
await removeIfPresent(this.fs, temporaryPath).catch(() => void 0);
|
|
3809
|
-
}
|
|
3810
|
-
throw error;
|
|
3674
|
+
if (expected.requireIssuer) {
|
|
3675
|
+
if (callback.iss === null || callback.iss.length === 0) {
|
|
3676
|
+
throw new Error("OAuth callback missing issuer");
|
|
3811
3677
|
}
|
|
3812
3678
|
}
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
try {
|
|
3816
|
-
await this.fs.unlink(this.filePath);
|
|
3817
|
-
} catch (error) {
|
|
3818
|
-
if (!isNotFoundError(error)) {
|
|
3819
|
-
throw error;
|
|
3820
|
-
}
|
|
3821
|
-
}
|
|
3679
|
+
if (callback.iss !== null && callback.iss.length > 0 && expected.issuer !== null && callback.iss !== expected.issuer) {
|
|
3680
|
+
throw new Error("OAuth callback issuer mismatch");
|
|
3822
3681
|
}
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3682
|
+
}
|
|
3683
|
+
function createAuthorizationError(error, description) {
|
|
3684
|
+
return new Error(`OAuth authorization failed: ${error} \u2014 ${description}`);
|
|
3685
|
+
}
|
|
3686
|
+
function escapeHtml(text) {
|
|
3687
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3688
|
+
}
|
|
3689
|
+
function buildSuccessPage(landingPage) {
|
|
3690
|
+
const title = landingPage?.title ?? "Connected";
|
|
3691
|
+
const body = landingPage?.body ?? "You can close this tab and return to your terminal.";
|
|
3692
|
+
return [
|
|
3693
|
+
"<!DOCTYPE html>",
|
|
3694
|
+
`<html><head><meta charset=utf-8><title>${escapeHtml(title)}</title></head>`,
|
|
3695
|
+
'<body style="font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0">',
|
|
3696
|
+
'<div style="text-align:center">',
|
|
3697
|
+
`<h1>${escapeHtml(title)}</h1>`,
|
|
3698
|
+
`<p style="color:#666">${escapeHtml(body)}</p>`,
|
|
3699
|
+
"</div></body></html>"
|
|
3700
|
+
].join("");
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
// ../mcp-oauth/dist/client/scope.js
|
|
3704
|
+
function normalizeOAuthScope(scope) {
|
|
3705
|
+
if (scope === void 0)
|
|
3706
|
+
return void 0;
|
|
3707
|
+
if (typeof scope !== "string" || [...scope].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126 || char === '"' || char === "\\"))
|
|
3708
|
+
throw new Error("Invalid OAuth scope syntax");
|
|
3709
|
+
const normalized = [...new Set(scope.split(" ").filter(Boolean))].sort().join(" ");
|
|
3710
|
+
return normalized || void 0;
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
// ../mcp-oauth/dist/client/token-auth-method.js
|
|
3714
|
+
function normalizeOAuthTokenEndpointAuthMethod(value) {
|
|
3715
|
+
if (value === void 0 || value === null)
|
|
3716
|
+
return void 0;
|
|
3717
|
+
if (value !== "none" && value !== "client_secret_post" && value !== "client_secret_basic")
|
|
3718
|
+
throw new Error("Unsupported OAuth token endpoint authentication method");
|
|
3719
|
+
return value;
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
// ../mcp-oauth/dist/client/client-registration.js
|
|
3723
|
+
function parseOAuthClientRegistration(value) {
|
|
3724
|
+
const invalid = () => new Error("Invalid OAuth client registration metadata");
|
|
3725
|
+
let nodes = 0;
|
|
3726
|
+
function copy(input, depth) {
|
|
3727
|
+
if (++nodes > 2e4 || depth > 64)
|
|
3728
|
+
throw invalid();
|
|
3729
|
+
if (input === null || typeof input === "boolean" || typeof input === "string")
|
|
3730
|
+
return input;
|
|
3731
|
+
if (typeof input === "number" && Number.isFinite(input))
|
|
3732
|
+
return input;
|
|
3733
|
+
if (typeof input !== "object" || input === null)
|
|
3734
|
+
throw invalid();
|
|
3735
|
+
const descriptors = Object.getOwnPropertyDescriptors(input);
|
|
3736
|
+
if (Array.isArray(input)) {
|
|
3737
|
+
const length = descriptors.length?.value;
|
|
3738
|
+
if (length > 2e4)
|
|
3739
|
+
throw invalid();
|
|
3740
|
+
const result2 = [];
|
|
3741
|
+
for (let index = 0; index < length; index++) {
|
|
3742
|
+
const descriptor = descriptors[String(index)];
|
|
3743
|
+
if (descriptor === void 0 || !Object.hasOwn(descriptor, "value"))
|
|
3744
|
+
throw invalid();
|
|
3745
|
+
result2.push(copy(descriptor.value, depth + 1));
|
|
3837
3746
|
}
|
|
3747
|
+
return result2;
|
|
3838
3748
|
}
|
|
3749
|
+
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
|
|
3750
|
+
throw invalid();
|
|
3751
|
+
return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key2, descriptor]) => {
|
|
3752
|
+
if (!Object.hasOwn(descriptor, "value"))
|
|
3753
|
+
throw invalid();
|
|
3754
|
+
return [key2, copy(descriptor.value, depth + 1)];
|
|
3755
|
+
}));
|
|
3839
3756
|
}
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
}
|
|
3846
|
-
throw error;
|
|
3847
|
-
});
|
|
3848
|
-
this.keyPromise = retryableKeyPromise;
|
|
3849
|
-
}
|
|
3850
|
-
return this.keyPromise;
|
|
3851
|
-
}
|
|
3852
|
-
};
|
|
3853
|
-
function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
|
|
3854
|
-
const [firstSegment] = defaultDirectory.split(/[\\/]+/).filter(Boolean);
|
|
3855
|
-
return path2.resolve(homeDirectory, firstSegment ?? ".");
|
|
3856
|
-
}
|
|
3857
|
-
function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
|
|
3858
|
-
if (symbolicLinkCheckStartPath === null) {
|
|
3859
|
-
return getExplicitProtectedCredentialPaths(resolvedPath);
|
|
3860
|
-
}
|
|
3861
|
-
const resolvedStartPath = path2.resolve(symbolicLinkCheckStartPath);
|
|
3862
|
-
if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
|
|
3863
|
-
return [path2.dirname(resolvedPath), resolvedPath];
|
|
3757
|
+
let result;
|
|
3758
|
+
try {
|
|
3759
|
+
result = copy(value, 0);
|
|
3760
|
+
} catch {
|
|
3761
|
+
throw invalid();
|
|
3864
3762
|
}
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3763
|
+
if (typeof result !== "object" || result === null || Array.isArray(result))
|
|
3764
|
+
throw invalid();
|
|
3765
|
+
const record2 = result;
|
|
3766
|
+
if (!Object.hasOwn(record2, "client_id") || typeof record2.client_id !== "string" || record2.client_id.trim() === "")
|
|
3767
|
+
throw new Error("OAuth client registration response missing client_id");
|
|
3768
|
+
for (const key2 of [
|
|
3769
|
+
"client_id",
|
|
3770
|
+
"client_secret",
|
|
3771
|
+
"token_endpoint_auth_method",
|
|
3772
|
+
"application_type",
|
|
3773
|
+
"client_name",
|
|
3774
|
+
"client_uri",
|
|
3775
|
+
"logo_uri",
|
|
3776
|
+
"scope",
|
|
3777
|
+
"tos_uri",
|
|
3778
|
+
"policy_uri",
|
|
3779
|
+
"jwks_uri",
|
|
3780
|
+
"software_id",
|
|
3781
|
+
"software_version",
|
|
3782
|
+
"software_statement",
|
|
3783
|
+
"registration_access_token",
|
|
3784
|
+
"registration_client_uri",
|
|
3785
|
+
"issuer"
|
|
3786
|
+
]) {
|
|
3787
|
+
if (Object.hasOwn(record2, key2) && record2[key2] !== null && typeof record2[key2] !== "string")
|
|
3788
|
+
throw invalid();
|
|
3870
3789
|
}
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3790
|
+
if (typeof record2.client_secret === "string" && record2.client_secret.trim() === "")
|
|
3791
|
+
throw invalid();
|
|
3792
|
+
for (const key2 of ["redirect_uris", "grant_types", "response_types", "contacts"]) {
|
|
3793
|
+
const entry = record2[key2];
|
|
3794
|
+
if (Object.hasOwn(record2, key2) && entry !== null && (!Array.isArray(entry) || entry.some((item) => typeof item !== "string")))
|
|
3795
|
+
throw invalid();
|
|
3876
3796
|
}
|
|
3877
|
-
for (const
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3797
|
+
for (const key2 of ["client_id_issued_at", "client_secret_expires_at"]) {
|
|
3798
|
+
const entry = record2[key2];
|
|
3799
|
+
if (Object.hasOwn(record2, key2) && entry !== null && (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 0))
|
|
3800
|
+
throw invalid();
|
|
3881
3801
|
}
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
throw
|
|
3802
|
+
try {
|
|
3803
|
+
normalizeOAuthScope(Object.hasOwn(record2, "scope") && record2.scope !== null ? record2.scope : void 0);
|
|
3804
|
+
} catch {
|
|
3805
|
+
throw invalid();
|
|
3886
3806
|
}
|
|
3807
|
+
if (Buffer.byteLength(JSON.stringify(record2), "utf8") > 64 * 1024)
|
|
3808
|
+
throw invalid();
|
|
3809
|
+
return record2;
|
|
3887
3810
|
}
|
|
3888
|
-
function
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
const
|
|
3893
|
-
const
|
|
3894
|
-
if (
|
|
3895
|
-
return
|
|
3896
|
-
}
|
|
3897
|
-
const
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3811
|
+
function normalizeStoredOAuthClient(value) {
|
|
3812
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
3813
|
+
return null;
|
|
3814
|
+
const record2 = value;
|
|
3815
|
+
const clientId = Object.hasOwn(record2, "clientId") ? record2.clientId : void 0;
|
|
3816
|
+
const clientSecret = Object.hasOwn(record2, "clientSecret") ? record2.clientSecret : void 0;
|
|
3817
|
+
if (typeof clientId !== "string" || clientId.trim() === "" || clientSecret !== void 0 && (typeof clientSecret !== "string" || clientSecret.trim() === ""))
|
|
3818
|
+
return null;
|
|
3819
|
+
const client = { clientId: clientId.trim(), ...clientSecret === void 0 ? {} : { clientSecret: clientSecret.trim() } };
|
|
3820
|
+
const requestedRedirectUri = Object.hasOwn(record2, "requestedRedirectUri") ? record2.requestedRedirectUri : void 0;
|
|
3821
|
+
if (requestedRedirectUri !== void 0) {
|
|
3822
|
+
try {
|
|
3823
|
+
if (typeof requestedRedirectUri !== "string")
|
|
3824
|
+
throw new Error("Invalid redirect identity");
|
|
3825
|
+
loopbackTarget({ redirectUri: requestedRedirectUri });
|
|
3826
|
+
} catch {
|
|
3827
|
+
throw new Error("Invalid stored OAuth registration redirect identity");
|
|
3903
3828
|
}
|
|
3904
|
-
|
|
3829
|
+
client.requestedRedirectUri = requestedRedirectUri;
|
|
3905
3830
|
}
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
if (
|
|
3917
|
-
|
|
3918
|
-
}
|
|
3831
|
+
const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record2, "tokenEndpointAuthMethod") ? record2.tokenEndpointAuthMethod : void 0);
|
|
3832
|
+
if (Object.hasOwn(record2, "registration") && record2.registration !== void 0) {
|
|
3833
|
+
const registration = parseOAuthClientRegistration(record2.registration);
|
|
3834
|
+
const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : void 0;
|
|
3835
|
+
if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
|
|
3836
|
+
throw new Error("OAuth client registration does not match the client identity");
|
|
3837
|
+
client.registration = registration;
|
|
3838
|
+
const registrationMethod = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(registration, "token_endpoint_auth_method") ? registration.token_endpoint_auth_method : void 0);
|
|
3839
|
+
if (method !== void 0 && registrationMethod !== void 0 && method !== registrationMethod)
|
|
3840
|
+
throw new Error("OAuth token endpoint authentication conflicts with the client registration");
|
|
3841
|
+
if (registrationMethod !== void 0)
|
|
3842
|
+
client.tokenEndpointAuthMethod = registrationMethod;
|
|
3919
3843
|
}
|
|
3844
|
+
if (method !== void 0)
|
|
3845
|
+
client.tokenEndpointAuthMethod = method;
|
|
3846
|
+
return client;
|
|
3920
3847
|
}
|
|
3921
|
-
function
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
reject(error);
|
|
3939
|
-
return;
|
|
3940
|
-
}
|
|
3941
|
-
resolve(Buffer.from(derivedKey));
|
|
3942
|
-
});
|
|
3943
|
-
});
|
|
3944
|
-
derivedKeyCache.set(cacheKey, keyPromise);
|
|
3945
|
-
return keyPromise.catch((error) => {
|
|
3946
|
-
if (derivedKeyCache.get(cacheKey) === keyPromise) {
|
|
3947
|
-
derivedKeyCache.delete(cacheKey);
|
|
3848
|
+
function registrationMatchesRedirect(client, requestedUri, fresh = false) {
|
|
3849
|
+
if (client.requestedRedirectUri !== void 0)
|
|
3850
|
+
return client.requestedRedirectUri === requestedUri;
|
|
3851
|
+
const redirects = client.registration?.redirect_uris;
|
|
3852
|
+
if (redirects === void 0 || redirects === null || redirects.length === 0)
|
|
3853
|
+
return true;
|
|
3854
|
+
return redirects.some((returnedUri) => {
|
|
3855
|
+
if (returnedUri === requestedUri)
|
|
3856
|
+
return true;
|
|
3857
|
+
if (!fresh)
|
|
3858
|
+
return false;
|
|
3859
|
+
let returned, requested;
|
|
3860
|
+
try {
|
|
3861
|
+
returned = new URL(returnedUri);
|
|
3862
|
+
requested = new URL(requestedUri);
|
|
3863
|
+
} catch {
|
|
3864
|
+
return false;
|
|
3948
3865
|
}
|
|
3949
|
-
|
|
3866
|
+
if (requested.protocol !== "http:" || returned.protocol !== "http:" || !["127.0.0.1", "[::1]", "localhost"].includes(requested.hostname))
|
|
3867
|
+
return false;
|
|
3868
|
+
const sameHost = returned.hostname === requested.hostname;
|
|
3869
|
+
const normalizedIpv4 = requested.hostname === "127.0.0.1" && returned.hostname === "localhost" && returned.port === "";
|
|
3870
|
+
if (!sameHost && !normalizedIpv4)
|
|
3871
|
+
return false;
|
|
3872
|
+
returned.hostname = requested.hostname;
|
|
3873
|
+
returned.port = requested.port;
|
|
3874
|
+
return returned.href === requested.href;
|
|
3950
3875
|
});
|
|
3951
3876
|
}
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
return null;
|
|
3967
|
-
}
|
|
3968
|
-
return {
|
|
3969
|
-
version,
|
|
3970
|
-
iv,
|
|
3971
|
-
authTag,
|
|
3972
|
-
ciphertext
|
|
3973
|
-
};
|
|
3974
|
-
} catch {
|
|
3975
|
-
return null;
|
|
3976
|
-
}
|
|
3977
|
-
}
|
|
3978
|
-
function isRecord2(value) {
|
|
3979
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3980
|
-
}
|
|
3981
|
-
function getOwnEntry(record2, key2) {
|
|
3982
|
-
return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
|
|
3983
|
-
}
|
|
3984
|
-
function isNotFoundError(error) {
|
|
3985
|
-
return hasOwnErrorCode(error, "ENOENT");
|
|
3986
|
-
}
|
|
3987
|
-
function isAlreadyExistsError(error) {
|
|
3988
|
-
return hasOwnErrorCode(error, "EEXIST");
|
|
3877
|
+
|
|
3878
|
+
// ../mcp-oauth/dist/client/auth-store-session-store.js
|
|
3879
|
+
import crypto2 from "node:crypto";
|
|
3880
|
+
import path4 from "node:path";
|
|
3881
|
+
|
|
3882
|
+
// ../auth-store/dist/encrypted-file-store.js
|
|
3883
|
+
import { createCipheriv, createDecipheriv, randomBytes, randomUUID as randomUUID2, scrypt } from "node:crypto";
|
|
3884
|
+
import { promises as fs } from "node:fs";
|
|
3885
|
+
import { homedir, hostname, userInfo } from "node:os";
|
|
3886
|
+
import path2 from "node:path";
|
|
3887
|
+
|
|
3888
|
+
// ../auth-store/dist/error-codes.js
|
|
3889
|
+
function hasOwnErrorCode(error, code) {
|
|
3890
|
+
return error instanceof Error && Object.prototype.hasOwnProperty.call(error, "code") && error.code === code;
|
|
3989
3891
|
}
|
|
3990
3892
|
|
|
3991
|
-
// ../auth-store/dist/
|
|
3992
|
-
import {
|
|
3993
|
-
import
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
}
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
}
|
|
4018
|
-
async get() {
|
|
4019
|
-
const result = await this.executeSecurityCommand(["find-generic-password", "-s", this.service, "-a", this.account, "-w"], "read secret from macOS Keychain");
|
|
4020
|
-
if (getCommandExitCode(result) === 0) {
|
|
4021
|
-
return stripTrailingLineBreak(getCommandOutput(result, "stdout"));
|
|
4022
|
-
}
|
|
4023
|
-
if (isKeychainEntryNotFound(result)) {
|
|
4024
|
-
return null;
|
|
4025
|
-
}
|
|
4026
|
-
throw createSecurityCliFailure("read secret from macOS Keychain", result);
|
|
4027
|
-
}
|
|
4028
|
-
async withLock(operation, options = {}) {
|
|
4029
|
-
const identity = createHash("sha256").update(JSON.stringify([this.service, this.account])).digest("hex");
|
|
4030
|
-
return withSecretStoreFileLock(this.lockFs, path3.join(this.lockDirectory, identity), operation, options);
|
|
4031
|
-
}
|
|
4032
|
-
async set(value) {
|
|
4033
|
-
if (value.includes("\n") || value.includes("\r")) {
|
|
4034
|
-
throw new Error("Keychain secrets cannot contain line breaks");
|
|
4035
|
-
}
|
|
4036
|
-
const result = await this.executeSecurityCommand([
|
|
4037
|
-
"add-generic-password",
|
|
4038
|
-
"-s",
|
|
4039
|
-
this.service,
|
|
4040
|
-
"-a",
|
|
4041
|
-
this.account,
|
|
4042
|
-
"-U",
|
|
4043
|
-
"-w",
|
|
4044
|
-
value
|
|
4045
|
-
], "store secret in macOS Keychain");
|
|
4046
|
-
if (getCommandExitCode(result) !== 0) {
|
|
4047
|
-
throw createSecurityCliFailure("store secret in macOS Keychain", result);
|
|
4048
|
-
}
|
|
4049
|
-
}
|
|
4050
|
-
async delete() {
|
|
4051
|
-
const result = await this.executeSecurityCommand(["delete-generic-password", "-s", this.service, "-a", this.account], "delete secret from macOS Keychain");
|
|
4052
|
-
if (getCommandExitCode(result) === 0 || isKeychainEntryNotFound(result)) {
|
|
4053
|
-
return;
|
|
3893
|
+
// ../auth-store/dist/transaction-lock.js
|
|
3894
|
+
import { randomUUID } from "node:crypto";
|
|
3895
|
+
import path from "node:path";
|
|
3896
|
+
async function withSecretStoreFileLock(fs2, lockDirectory, operation, options = {}) {
|
|
3897
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
3898
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2147483647)
|
|
3899
|
+
throw new Error("Invalid secret-store transaction lock timeout");
|
|
3900
|
+
options.signal?.throwIfAborted();
|
|
3901
|
+
const deadline = performance.now() + timeoutMs;
|
|
3902
|
+
const directory = path.resolve(lockDirectory);
|
|
3903
|
+
await assertLockDirectoryPath(fs2, directory);
|
|
3904
|
+
await fs2.mkdir(directory, { recursive: true, mode: 448 });
|
|
3905
|
+
await assertLockDirectoryPath(fs2, directory);
|
|
3906
|
+
const name = `${process.pid}-${randomUUID()}.claim`;
|
|
3907
|
+
const claimPath = path.join(directory, name);
|
|
3908
|
+
const temporaryPath = `${claimPath}.tmp`;
|
|
3909
|
+
let claimed = true;
|
|
3910
|
+
let temporaryCreated = false;
|
|
3911
|
+
let outcome;
|
|
3912
|
+
try {
|
|
3913
|
+
try {
|
|
3914
|
+
await fs2.writeFile(claimPath, JSON.stringify({ ticket: null }), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
3915
|
+
} catch (error) {
|
|
3916
|
+
if (hasOwnErrorCode(error, "EEXIST"))
|
|
3917
|
+
claimed = false;
|
|
3918
|
+
throw error;
|
|
4054
3919
|
}
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
3920
|
+
const existing = await readClaims(fs2, directory, name);
|
|
3921
|
+
const ticket = existing.reduce((max, claim) => Math.max(max, claim.ticket ?? 0), 0) + 1;
|
|
3922
|
+
if (!Number.isSafeInteger(ticket))
|
|
3923
|
+
throw new Error("Secret-store transaction lock ticket overflow");
|
|
3924
|
+
temporaryCreated = true;
|
|
4058
3925
|
try {
|
|
4059
|
-
|
|
4060
|
-
return await this.runCommand(SECURITY_CLI, args);
|
|
4061
|
-
}
|
|
4062
|
-
return await this.runCommand(SECURITY_CLI, args, options);
|
|
3926
|
+
await fs2.writeFile(temporaryPath, JSON.stringify({ ticket }), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
4063
3927
|
} catch (error) {
|
|
4064
|
-
|
|
4065
|
-
|
|
3928
|
+
if (hasOwnErrorCode(error, "EEXIST"))
|
|
3929
|
+
temporaryCreated = false;
|
|
3930
|
+
throw error;
|
|
4066
3931
|
}
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
});
|
|
4091
|
-
child.stderr?.setEncoding("utf8");
|
|
4092
|
-
child.stderr?.on("data", (chunk) => {
|
|
4093
|
-
stderr += chunk.toString();
|
|
4094
|
-
});
|
|
4095
|
-
if (options?.stdin !== void 0) {
|
|
4096
|
-
child.stdin?.once("error", (error) => {
|
|
4097
|
-
stdinErrorMessage = error instanceof Error ? error.message : String(error);
|
|
3932
|
+
await fs2.rename(temporaryPath, claimPath);
|
|
3933
|
+
temporaryCreated = false;
|
|
3934
|
+
for (; ; ) {
|
|
3935
|
+
options.signal?.throwIfAborted();
|
|
3936
|
+
const peers = await readClaims(fs2, directory, name);
|
|
3937
|
+
if (!peers.some((peer) => peer.ticket === null || peer.ticket < ticket || peer.ticket === ticket && peer.name < name))
|
|
3938
|
+
break;
|
|
3939
|
+
const remaining = deadline - performance.now();
|
|
3940
|
+
if (remaining <= 0)
|
|
3941
|
+
throw new Error("Timed out waiting for secret-store transaction lock");
|
|
3942
|
+
await new Promise((resolve, reject) => {
|
|
3943
|
+
const abort = () => {
|
|
3944
|
+
clearTimeout(timer);
|
|
3945
|
+
options.signal?.removeEventListener("abort", abort);
|
|
3946
|
+
reject(options.signal?.reason);
|
|
3947
|
+
};
|
|
3948
|
+
const timer = setTimeout(() => {
|
|
3949
|
+
options.signal?.removeEventListener("abort", abort);
|
|
3950
|
+
resolve();
|
|
3951
|
+
}, Math.min(10, remaining));
|
|
3952
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
3953
|
+
if (options.signal?.aborted)
|
|
3954
|
+
abort();
|
|
4098
3955
|
});
|
|
4099
|
-
child.stdin?.end(options.stdin);
|
|
4100
3956
|
}
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
resolve({
|
|
4106
|
-
stdout,
|
|
4107
|
-
stderr,
|
|
4108
|
-
exitCode: 127
|
|
4109
|
-
});
|
|
4110
|
-
});
|
|
4111
|
-
child.on("close", (code) => {
|
|
4112
|
-
appendStdinError();
|
|
4113
|
-
resolve({
|
|
4114
|
-
stdout,
|
|
4115
|
-
stderr,
|
|
4116
|
-
exitCode: code ?? 1
|
|
4117
|
-
});
|
|
4118
|
-
});
|
|
4119
|
-
});
|
|
4120
|
-
}
|
|
4121
|
-
function stripTrailingLineBreak(value) {
|
|
4122
|
-
if (value.endsWith("\r\n")) {
|
|
4123
|
-
return value.slice(0, -2);
|
|
3957
|
+
options.signal?.throwIfAborted();
|
|
3958
|
+
outcome = { result: await operation() };
|
|
3959
|
+
} catch (error) {
|
|
3960
|
+
outcome = { error };
|
|
4124
3961
|
}
|
|
4125
|
-
|
|
4126
|
-
|
|
3962
|
+
const cleanup = [];
|
|
3963
|
+
for (const target of [...temporaryCreated ? [temporaryPath] : [], ...claimed ? [claimPath] : []]) {
|
|
3964
|
+
try {
|
|
3965
|
+
await fs2.unlink(target);
|
|
3966
|
+
} catch (error) {
|
|
3967
|
+
if (!hasOwnErrorCode(error, "ENOENT"))
|
|
3968
|
+
cleanup.push(error);
|
|
3969
|
+
}
|
|
4127
3970
|
}
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
3971
|
+
if (cleanup.length)
|
|
3972
|
+
throw new AggregateError([..."error" in outcome ? [outcome.error] : [], ...cleanup], "Secret-store transaction lock cleanup failed");
|
|
3973
|
+
if ("error" in outcome)
|
|
3974
|
+
throw outcome.error;
|
|
3975
|
+
return outcome.result;
|
|
4132
3976
|
}
|
|
4133
|
-
function
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
3977
|
+
async function assertNoSymbolicLink(fs2, target) {
|
|
3978
|
+
try {
|
|
3979
|
+
if ((await fs2.lstat(target)).isSymbolicLink())
|
|
3980
|
+
throw new Error("Refusing secret-store transaction lock through symbolic link");
|
|
3981
|
+
} catch (error) {
|
|
3982
|
+
if (!hasOwnErrorCode(error, "ENOENT"))
|
|
3983
|
+
throw error;
|
|
4138
3984
|
}
|
|
4139
|
-
return new Error(`Failed to ${operation}: security exited with code ${exitCode}`);
|
|
4140
|
-
}
|
|
4141
|
-
function getCommandExitCode(result) {
|
|
4142
|
-
const value = getOwnEntry2(result, "exitCode");
|
|
4143
|
-
return typeof value === "number" && Number.isInteger(value) ? value : 1;
|
|
4144
|
-
}
|
|
4145
|
-
function getCommandOutput(result, key2) {
|
|
4146
|
-
const value = getOwnEntry2(result, key2);
|
|
4147
|
-
return typeof value === "string" ? value : "";
|
|
4148
3985
|
}
|
|
4149
|
-
function
|
|
4150
|
-
|
|
3986
|
+
async function assertLockDirectoryPath(fs2, directory) {
|
|
3987
|
+
const root = path.parse(directory).root;
|
|
3988
|
+
const segments = directory.slice(root.length).split(path.sep).filter(Boolean);
|
|
3989
|
+
let current = root;
|
|
3990
|
+
for (const [index, segment] of segments.entries()) {
|
|
3991
|
+
current = path.join(current, segment);
|
|
3992
|
+
if (index > 0 || segments.length === 1)
|
|
3993
|
+
await assertNoSymbolicLink(fs2, current);
|
|
3994
|
+
}
|
|
4151
3995
|
}
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
3996
|
+
async function readClaims(fs2, directory, ownName) {
|
|
3997
|
+
const claims = [];
|
|
3998
|
+
for (const name of await fs2.readdir(directory)) {
|
|
3999
|
+
if (name === ownName || !name.endsWith(".claim"))
|
|
4000
|
+
continue;
|
|
4001
|
+
const pidText = name.slice(0, name.indexOf("-"));
|
|
4002
|
+
const pid = Number(pidText);
|
|
4003
|
+
if (!Number.isSafeInteger(pid) || pid < 1 || String(pid) !== pidText)
|
|
4004
|
+
throw new Error("Malformed secret-store transaction lock owner");
|
|
4005
|
+
const target = path.join(directory, name);
|
|
4006
|
+
await assertNoSymbolicLink(fs2, target);
|
|
4007
|
+
let alive = true;
|
|
4008
|
+
try {
|
|
4009
|
+
process.kill(pid, 0);
|
|
4010
|
+
} catch (error) {
|
|
4011
|
+
alive = !hasOwnErrorCode(error, "ESRCH");
|
|
4160
4012
|
}
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4013
|
+
if (!alive) {
|
|
4014
|
+
try {
|
|
4015
|
+
await fs2.unlink(target);
|
|
4016
|
+
} catch (error) {
|
|
4017
|
+
if (!hasOwnErrorCode(error, "ENOENT"))
|
|
4018
|
+
throw error;
|
|
4019
|
+
}
|
|
4020
|
+
continue;
|
|
4166
4021
|
}
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
if (backend === "keychain") {
|
|
4184
|
-
return "keychain";
|
|
4185
|
-
}
|
|
4186
|
-
if (backend === void 0 || backend === "file") {
|
|
4187
|
-
return "file";
|
|
4022
|
+
let ticket = null;
|
|
4023
|
+
let raw;
|
|
4024
|
+
try {
|
|
4025
|
+
raw = await fs2.readFile(target, "utf8");
|
|
4026
|
+
} catch (error) {
|
|
4027
|
+
if (hasOwnErrorCode(error, "ENOENT"))
|
|
4028
|
+
continue;
|
|
4029
|
+
throw error;
|
|
4030
|
+
}
|
|
4031
|
+
try {
|
|
4032
|
+
const value = JSON.parse(raw);
|
|
4033
|
+
if (value !== null && typeof value === "object" && "ticket" in value && typeof value.ticket === "number" && Number.isSafeInteger(value.ticket) && value.ticket > 0)
|
|
4034
|
+
ticket = value.ticket;
|
|
4035
|
+
} catch {
|
|
4036
|
+
}
|
|
4037
|
+
claims.push({ name, ticket });
|
|
4188
4038
|
}
|
|
4189
|
-
|
|
4190
|
-
}
|
|
4191
|
-
function getOwnEnvValue(env, key2) {
|
|
4192
|
-
return env !== void 0 && Object.prototype.hasOwnProperty.call(env, key2) ? env[key2] : void 0;
|
|
4039
|
+
return claims;
|
|
4193
4040
|
}
|
|
4194
4041
|
|
|
4195
|
-
// ../
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4042
|
+
// ../auth-store/dist/encrypted-file-store.js
|
|
4043
|
+
var derivedKeyCache = /* @__PURE__ */ new Map();
|
|
4044
|
+
var ENCRYPTION_ALGORITHM = "aes-256-gcm";
|
|
4045
|
+
var ENCRYPTION_VERSION = 1;
|
|
4046
|
+
var ENCRYPTION_KEY_BYTES = 32;
|
|
4047
|
+
var ENCRYPTION_IV_BYTES = 12;
|
|
4048
|
+
var ENCRYPTION_AUTH_TAG_BYTES = 16;
|
|
4049
|
+
var ENCRYPTION_FILE_MODE = 384;
|
|
4050
|
+
var EncryptedFileStore = class {
|
|
4051
|
+
fs;
|
|
4052
|
+
filePath;
|
|
4053
|
+
symbolicLinkCheckStartPath;
|
|
4054
|
+
salt;
|
|
4055
|
+
getMachineIdentity;
|
|
4056
|
+
getRandomBytes;
|
|
4057
|
+
keyPromise = null;
|
|
4058
|
+
throwOnInvalidDocument;
|
|
4059
|
+
constructor(input) {
|
|
4060
|
+
this.fs = input.fs ?? fs;
|
|
4061
|
+
this.salt = input.salt;
|
|
4062
|
+
if (input.filePath === void 0) {
|
|
4063
|
+
const homeDirectory = (input.getHomeDirectory ?? homedir)();
|
|
4064
|
+
const defaultDirectory = input.defaultDirectory ?? ".auth-store";
|
|
4065
|
+
const defaultFileName = input.defaultFileName ?? "credentials.enc";
|
|
4066
|
+
assertSafeDefaultDirectory(defaultDirectory);
|
|
4067
|
+
assertSafeDefaultFileName(defaultFileName);
|
|
4068
|
+
this.filePath = path2.join(homeDirectory, defaultDirectory, defaultFileName);
|
|
4069
|
+
this.symbolicLinkCheckStartPath = resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory);
|
|
4070
|
+
} else {
|
|
4071
|
+
this.filePath = input.filePath;
|
|
4072
|
+
this.symbolicLinkCheckStartPath = null;
|
|
4073
|
+
}
|
|
4074
|
+
this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
|
|
4075
|
+
this.getRandomBytes = input.getRandomBytes ?? randomBytes;
|
|
4076
|
+
this.throwOnInvalidDocument = input.throwOnInvalidDocument ?? false;
|
|
4202
4077
|
}
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
var DEFAULT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth";
|
|
4211
|
-
var DEFAULT_CLIENT_FILE_SALT = "poe-code:mcp-oauth:clients:v1";
|
|
4212
|
-
var DEFAULT_CLIENT_FILE_DIRECTORY = ".poe-code/mcp-oauth/clients";
|
|
4213
|
-
var DEFAULT_CLIENT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth-clients";
|
|
4214
|
-
var MAX_JS_DATE_MS = 864e13;
|
|
4215
|
-
function createAuthStoreSessionStore(options = {}, namespace) {
|
|
4216
|
-
assertPersistenceNamespace(namespace);
|
|
4217
|
-
return {
|
|
4218
|
-
async withLock(resource, operation, lockOptions) {
|
|
4219
|
-
const store = createResourceSecretStore(resource, options, namespace);
|
|
4220
|
-
if (store.withLock === void 0)
|
|
4221
|
-
throw new Error("OAuth secret-store backend does not support transaction locks");
|
|
4222
|
-
return store.withLock(operation, lockOptions);
|
|
4223
|
-
},
|
|
4224
|
-
async load(resource) {
|
|
4225
|
-
const store = createResourceSecretStore(resource, options, namespace);
|
|
4226
|
-
const value = await store.get();
|
|
4227
|
-
if (value === null) {
|
|
4078
|
+
async get() {
|
|
4079
|
+
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
4080
|
+
let rawDocument;
|
|
4081
|
+
try {
|
|
4082
|
+
rawDocument = await this.fs.readFile(this.filePath, "utf8");
|
|
4083
|
+
} catch (error) {
|
|
4084
|
+
if (isNotFoundError(error)) {
|
|
4228
4085
|
return null;
|
|
4229
4086
|
}
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4087
|
+
throw error;
|
|
4088
|
+
}
|
|
4089
|
+
const document = parseEncryptedDocument(rawDocument);
|
|
4090
|
+
if (!document) {
|
|
4091
|
+
if (this.throwOnInvalidDocument)
|
|
4092
|
+
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
4093
|
+
return null;
|
|
4094
|
+
}
|
|
4095
|
+
const key2 = await this.getEncryptionKey();
|
|
4096
|
+
try {
|
|
4097
|
+
const iv = Buffer.from(document.iv, "base64");
|
|
4098
|
+
const authTag = Buffer.from(document.authTag, "base64");
|
|
4099
|
+
const ciphertext = Buffer.from(document.ciphertext, "base64");
|
|
4100
|
+
if (iv.byteLength !== ENCRYPTION_IV_BYTES || authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
|
|
4101
|
+
if (this.throwOnInvalidDocument)
|
|
4102
|
+
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
4103
|
+
return null;
|
|
4235
4104
|
}
|
|
4236
|
-
|
|
4237
|
-
|
|
4105
|
+
const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key2, iv);
|
|
4106
|
+
decipher.setAuthTag(authTag);
|
|
4107
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
4108
|
+
return plaintext.toString("utf8");
|
|
4109
|
+
} catch {
|
|
4110
|
+
if (this.throwOnInvalidDocument)
|
|
4111
|
+
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
4112
|
+
return null;
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
async withLock(operation, options = {}) {
|
|
4116
|
+
await this.assertCredentialPathHasNoSymbolicLinks(`${this.filePath}.lock`);
|
|
4117
|
+
if (this.fs.readdir === void 0)
|
|
4118
|
+
throw new Error("Secret-store transaction locks require filesystem readdir support");
|
|
4119
|
+
return withSecretStoreFileLock(this.fs, `${this.filePath}.lock`, operation, options);
|
|
4120
|
+
}
|
|
4121
|
+
async set(value) {
|
|
4122
|
+
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
4123
|
+
const key2 = await this.getEncryptionKey();
|
|
4124
|
+
const iv = this.getRandomBytes(ENCRYPTION_IV_BYTES);
|
|
4125
|
+
const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key2, iv);
|
|
4126
|
+
const ciphertext = Buffer.concat([
|
|
4127
|
+
cipher.update(value, "utf8"),
|
|
4128
|
+
cipher.final()
|
|
4129
|
+
]);
|
|
4130
|
+
const authTag = cipher.getAuthTag();
|
|
4131
|
+
const document = {
|
|
4132
|
+
version: ENCRYPTION_VERSION,
|
|
4133
|
+
iv: iv.toString("base64"),
|
|
4134
|
+
authTag: authTag.toString("base64"),
|
|
4135
|
+
ciphertext: ciphertext.toString("base64")
|
|
4136
|
+
};
|
|
4137
|
+
await this.fs.mkdir(path2.dirname(this.filePath), { recursive: true });
|
|
4138
|
+
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
4139
|
+
const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
4140
|
+
let temporaryCreated = false;
|
|
4141
|
+
try {
|
|
4142
|
+
await this.assertCredentialPathHasNoSymbolicLinks(temporaryPath);
|
|
4143
|
+
await this.fs.writeFile(temporaryPath, JSON.stringify(document), {
|
|
4144
|
+
encoding: "utf8",
|
|
4145
|
+
flag: "wx",
|
|
4146
|
+
mode: ENCRYPTION_FILE_MODE
|
|
4147
|
+
});
|
|
4148
|
+
temporaryCreated = true;
|
|
4149
|
+
await this.fs.chmod(temporaryPath, ENCRYPTION_FILE_MODE);
|
|
4150
|
+
await this.fs.rename(temporaryPath, this.filePath);
|
|
4151
|
+
} catch (error) {
|
|
4152
|
+
if (temporaryCreated || !isAlreadyExistsError(error)) {
|
|
4153
|
+
await removeIfPresent(this.fs, temporaryPath).catch(() => void 0);
|
|
4238
4154
|
}
|
|
4239
|
-
throw
|
|
4240
|
-
},
|
|
4241
|
-
async save(resource, session) {
|
|
4242
|
-
const store = createResourceSecretStore(resource, options, namespace);
|
|
4243
|
-
await store.set(JSON.stringify(session));
|
|
4244
|
-
},
|
|
4245
|
-
async clear(resource) {
|
|
4246
|
-
const store = createResourceSecretStore(resource, options, namespace);
|
|
4247
|
-
await store.delete();
|
|
4155
|
+
throw error;
|
|
4248
4156
|
}
|
|
4249
|
-
}
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
if (value === null) {
|
|
4258
|
-
return null;
|
|
4157
|
+
}
|
|
4158
|
+
async delete() {
|
|
4159
|
+
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
4160
|
+
try {
|
|
4161
|
+
await this.fs.unlink(this.filePath);
|
|
4162
|
+
} catch (error) {
|
|
4163
|
+
if (!isNotFoundError(error)) {
|
|
4164
|
+
throw error;
|
|
4259
4165
|
}
|
|
4260
|
-
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
async assertCredentialPathHasNoSymbolicLinks(targetPath) {
|
|
4169
|
+
const resolvedPath = path2.resolve(targetPath);
|
|
4170
|
+
const protectedPaths = getProtectedCredentialPaths(resolvedPath, this.symbolicLinkCheckStartPath);
|
|
4171
|
+
for (const currentPath of protectedPaths) {
|
|
4261
4172
|
try {
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4173
|
+
const stats = await this.fs.lstat(currentPath);
|
|
4174
|
+
if (stats.isSymbolicLink()) {
|
|
4175
|
+
throw new Error(`Refusing to use encrypted credential path through symbolic link: ${currentPath}`);
|
|
4176
|
+
}
|
|
4177
|
+
} catch (error) {
|
|
4178
|
+
if (isNotFoundError(error)) {
|
|
4179
|
+
return;
|
|
4180
|
+
}
|
|
4181
|
+
throw error;
|
|
4265
4182
|
}
|
|
4266
|
-
if (isObjectRecord(parsed) && typeof getOwnEntry3(parsed, "clientId") === "string")
|
|
4267
|
-
return parsed;
|
|
4268
|
-
throw new Error("Stored OAuth client must be a JSON object with clientId");
|
|
4269
|
-
},
|
|
4270
|
-
async save(issuer, client) {
|
|
4271
|
-
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
4272
|
-
await store.set(JSON.stringify(client));
|
|
4273
|
-
},
|
|
4274
|
-
async clear(issuer) {
|
|
4275
|
-
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
4276
|
-
await store.delete();
|
|
4277
4183
|
}
|
|
4278
|
-
};
|
|
4279
|
-
}
|
|
4280
|
-
function createNamedSecretStore(key2, options, defaults, namespace) {
|
|
4281
|
-
const hash = crypto.createHash("sha256").update(namespace === void 0 ? key2 : JSON.stringify([namespace, key2])).digest("hex");
|
|
4282
|
-
const configuredFilePath = options.fileStore?.filePath;
|
|
4283
|
-
const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
|
|
4284
|
-
const fileStore = {
|
|
4285
|
-
...options.fileStore,
|
|
4286
|
-
throwOnInvalidDocument: true,
|
|
4287
|
-
filePath: parsedFilePath === null ? void 0 : path4.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
|
|
4288
|
-
salt: options.fileStore?.salt ?? defaults.salt,
|
|
4289
|
-
defaultDirectory: options.fileStore?.defaultDirectory || defaults.directory,
|
|
4290
|
-
defaultFileName: parsedFilePath === null ? `${hash}.enc` : `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`
|
|
4291
|
-
};
|
|
4292
|
-
const keychainStore = {
|
|
4293
|
-
...options.keychainStore,
|
|
4294
|
-
service: options.keychainStore?.service ?? defaults.service,
|
|
4295
|
-
account: `${options.keychainStore?.account ?? defaults.accountPrefix}:${hash}`
|
|
4296
|
-
};
|
|
4297
|
-
return createSecretStore({ ...options, fileStore, keychainStore }).store;
|
|
4298
|
-
}
|
|
4299
|
-
function createResourceSecretStore(resource, options, namespace) {
|
|
4300
|
-
return createNamedSecretStore(canonicalizeResourceIndicator(resource), options, {
|
|
4301
|
-
salt: DEFAULT_FILE_SALT,
|
|
4302
|
-
directory: DEFAULT_FILE_DIRECTORY,
|
|
4303
|
-
service: DEFAULT_KEYCHAIN_SERVICE,
|
|
4304
|
-
accountPrefix: "provider"
|
|
4305
|
-
}, namespace);
|
|
4306
|
-
}
|
|
4307
|
-
function createIssuerSecretStore(issuer, options, namespace) {
|
|
4308
|
-
return createNamedSecretStore(issuer, options, {
|
|
4309
|
-
salt: DEFAULT_CLIENT_FILE_SALT,
|
|
4310
|
-
directory: DEFAULT_CLIENT_FILE_DIRECTORY,
|
|
4311
|
-
service: DEFAULT_CLIENT_KEYCHAIN_SERVICE,
|
|
4312
|
-
accountPrefix: "issuer"
|
|
4313
|
-
}, namespace);
|
|
4314
|
-
}
|
|
4315
|
-
function assertPersistenceNamespace(namespace) {
|
|
4316
|
-
if (namespace !== void 0 && (typeof namespace !== "string" || namespace.trim() === "" || Buffer.byteLength(namespace, "utf8") > 1024))
|
|
4317
|
-
throw new Error("OAuth persistence namespace must be a nonempty string within 1024 bytes");
|
|
4318
|
-
}
|
|
4319
|
-
function isObjectRecord(value) {
|
|
4320
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4321
|
-
}
|
|
4322
|
-
function getOwnEntry3(record2, key2) {
|
|
4323
|
-
return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
|
|
4324
|
-
}
|
|
4325
|
-
function getOwnString(record2, key2) {
|
|
4326
|
-
const value = getOwnEntry3(record2, key2);
|
|
4327
|
-
return typeof value === "string" ? value : void 0;
|
|
4328
|
-
}
|
|
4329
|
-
function isStoredOAuthSession(value) {
|
|
4330
|
-
if (!isObjectRecord(value)) {
|
|
4331
|
-
return false;
|
|
4332
4184
|
}
|
|
4333
|
-
|
|
4185
|
+
getEncryptionKey() {
|
|
4186
|
+
if (!this.keyPromise) {
|
|
4187
|
+
const retryableKeyPromise = deriveEncryptionKey(this.getMachineIdentity, this.salt).catch((error) => {
|
|
4188
|
+
if (this.keyPromise === retryableKeyPromise) {
|
|
4189
|
+
this.keyPromise = null;
|
|
4190
|
+
}
|
|
4191
|
+
throw error;
|
|
4192
|
+
});
|
|
4193
|
+
this.keyPromise = retryableKeyPromise;
|
|
4194
|
+
}
|
|
4195
|
+
return this.keyPromise;
|
|
4196
|
+
}
|
|
4197
|
+
};
|
|
4198
|
+
function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
|
|
4199
|
+
const [firstSegment] = defaultDirectory.split(/[\\/]+/).filter(Boolean);
|
|
4200
|
+
return path2.resolve(homeDirectory, firstSegment ?? ".");
|
|
4334
4201
|
}
|
|
4335
|
-
function
|
|
4336
|
-
if (
|
|
4337
|
-
return
|
|
4202
|
+
function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
|
|
4203
|
+
if (symbolicLinkCheckStartPath === null) {
|
|
4204
|
+
return getExplicitProtectedCredentialPaths(resolvedPath);
|
|
4205
|
+
}
|
|
4206
|
+
const resolvedStartPath = path2.resolve(symbolicLinkCheckStartPath);
|
|
4207
|
+
if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
|
|
4208
|
+
return [path2.dirname(resolvedPath), resolvedPath];
|
|
4209
|
+
}
|
|
4210
|
+
const protectedPaths = [resolvedStartPath];
|
|
4211
|
+
let currentPath = resolvedStartPath;
|
|
4212
|
+
for (const segment of path2.relative(resolvedStartPath, resolvedPath).split(path2.sep).filter(Boolean)) {
|
|
4213
|
+
currentPath = path2.join(currentPath, segment);
|
|
4214
|
+
protectedPaths.push(currentPath);
|
|
4338
4215
|
}
|
|
4339
|
-
return
|
|
4216
|
+
return protectedPaths;
|
|
4340
4217
|
}
|
|
4341
|
-
function
|
|
4342
|
-
if (
|
|
4343
|
-
|
|
4218
|
+
function assertSafeDefaultDirectory(defaultDirectory) {
|
|
4219
|
+
if (path2.isAbsolute(defaultDirectory) || path2.win32.isAbsolute(defaultDirectory)) {
|
|
4220
|
+
throw new Error("defaultDirectory must be a relative path inside the home directory");
|
|
4344
4221
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4222
|
+
for (const segment of splitPathSegments(defaultDirectory)) {
|
|
4223
|
+
if (segment === "..") {
|
|
4224
|
+
throw new Error("defaultDirectory must be a relative path inside the home directory");
|
|
4225
|
+
}
|
|
4347
4226
|
}
|
|
4348
|
-
|
|
4349
|
-
|
|
4227
|
+
}
|
|
4228
|
+
function assertSafeDefaultFileName(defaultFileName) {
|
|
4229
|
+
if (defaultFileName.trim().length === 0 || defaultFileName === "." || defaultFileName === ".." || splitPathSegments(defaultFileName).length !== 1) {
|
|
4230
|
+
throw new Error("defaultFileName must be a file name without path separators");
|
|
4350
4231
|
}
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4232
|
+
}
|
|
4233
|
+
function splitPathSegments(value) {
|
|
4234
|
+
return value.split("/").flatMap((segment) => segment.split("\\")).filter((segment) => segment.length > 0);
|
|
4235
|
+
}
|
|
4236
|
+
function getExplicitProtectedCredentialPaths(resolvedPath) {
|
|
4237
|
+
const parsed = path2.parse(resolvedPath);
|
|
4238
|
+
const segments = resolvedPath.slice(parsed.root.length).split(path2.sep).filter((segment) => segment.length > 0);
|
|
4239
|
+
if (segments.length <= 1) {
|
|
4240
|
+
return [resolvedPath];
|
|
4354
4241
|
}
|
|
4355
|
-
const
|
|
4356
|
-
|
|
4357
|
-
|
|
4242
|
+
const protectedPaths = [];
|
|
4243
|
+
let currentPath = parsed.root;
|
|
4244
|
+
for (const [index, segment] of segments.entries()) {
|
|
4245
|
+
currentPath = path2.join(currentPath, segment);
|
|
4246
|
+
if (index === 0) {
|
|
4247
|
+
continue;
|
|
4248
|
+
}
|
|
4249
|
+
protectedPaths.push(currentPath);
|
|
4358
4250
|
}
|
|
4359
|
-
|
|
4360
|
-
return scope === void 0 || typeof scope === "string" && scope.trim().length > 0;
|
|
4251
|
+
return protectedPaths;
|
|
4361
4252
|
}
|
|
4362
|
-
function
|
|
4363
|
-
const
|
|
4364
|
-
return
|
|
4253
|
+
function isPathInsideOrEqual(childPath, parentPath) {
|
|
4254
|
+
const relativePath = path2.relative(parentPath, childPath);
|
|
4255
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path2.isAbsolute(relativePath);
|
|
4365
4256
|
}
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
if (response.redirected || response.type === "opaqueredirect") {
|
|
4374
|
-
void response.body?.cancel().catch(() => void 0);
|
|
4375
|
-
throw new Error("MCP HTTP redirects are not allowed");
|
|
4257
|
+
async function removeIfPresent(fileSystem, filePath) {
|
|
4258
|
+
try {
|
|
4259
|
+
await fileSystem.unlink(filePath);
|
|
4260
|
+
} catch (error) {
|
|
4261
|
+
if (!isNotFoundError(error)) {
|
|
4262
|
+
throw error;
|
|
4263
|
+
}
|
|
4376
4264
|
}
|
|
4377
|
-
return response;
|
|
4378
4265
|
}
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
// ../mcp-oauth/dist/client/loopback-authorization.js
|
|
4384
|
-
import http from "node:http";
|
|
4385
|
-
|
|
4386
|
-
// ../mcp-oauth/dist/client/authorization-state.js
|
|
4387
|
-
import crypto2 from "node:crypto";
|
|
4388
|
-
function createAuthorizationState(input) {
|
|
4389
|
-
const payload = {
|
|
4390
|
-
v: 1,
|
|
4391
|
-
n: crypto2.randomBytes(16).toString("base64url"),
|
|
4392
|
-
i: input.issuer,
|
|
4393
|
-
r: input.requireIssuer
|
|
4266
|
+
function defaultMachineIdentity() {
|
|
4267
|
+
return {
|
|
4268
|
+
hostname: hostname(),
|
|
4269
|
+
username: userInfo().username
|
|
4394
4270
|
};
|
|
4395
|
-
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
4396
4271
|
}
|
|
4397
|
-
function
|
|
4398
|
-
|
|
4399
|
-
|
|
4272
|
+
async function deriveEncryptionKey(getMachineIdentity, salt) {
|
|
4273
|
+
const machineIdentity = await getMachineIdentity();
|
|
4274
|
+
const secret = `${machineIdentity.hostname}:${machineIdentity.username}`;
|
|
4275
|
+
const cacheKey = JSON.stringify([machineIdentity.hostname, machineIdentity.username, salt]);
|
|
4276
|
+
const cached = derivedKeyCache.get(cacheKey);
|
|
4277
|
+
if (cached) {
|
|
4278
|
+
return cached;
|
|
4400
4279
|
}
|
|
4280
|
+
const keyPromise = new Promise((resolve, reject) => {
|
|
4281
|
+
scrypt(secret, salt, ENCRYPTION_KEY_BYTES, (error, derivedKey) => {
|
|
4282
|
+
if (error) {
|
|
4283
|
+
reject(error);
|
|
4284
|
+
return;
|
|
4285
|
+
}
|
|
4286
|
+
resolve(Buffer.from(derivedKey));
|
|
4287
|
+
});
|
|
4288
|
+
});
|
|
4289
|
+
derivedKeyCache.set(cacheKey, keyPromise);
|
|
4290
|
+
return keyPromise.catch((error) => {
|
|
4291
|
+
if (derivedKeyCache.get(cacheKey) === keyPromise) {
|
|
4292
|
+
derivedKeyCache.delete(cacheKey);
|
|
4293
|
+
}
|
|
4294
|
+
throw error;
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
function parseEncryptedDocument(raw) {
|
|
4401
4298
|
try {
|
|
4402
|
-
const
|
|
4403
|
-
|
|
4404
|
-
if (!isObjectRecord2(parsed)) {
|
|
4299
|
+
const parsed = JSON.parse(raw);
|
|
4300
|
+
if (!isRecord2(parsed)) {
|
|
4405
4301
|
return null;
|
|
4406
4302
|
}
|
|
4407
|
-
const version =
|
|
4408
|
-
const
|
|
4409
|
-
const
|
|
4410
|
-
const
|
|
4411
|
-
if (version !==
|
|
4303
|
+
const version = getOwnEntry2(parsed, "version");
|
|
4304
|
+
const iv = getOwnEntry2(parsed, "iv");
|
|
4305
|
+
const authTag = getOwnEntry2(parsed, "authTag");
|
|
4306
|
+
const ciphertext = getOwnEntry2(parsed, "ciphertext");
|
|
4307
|
+
if (version !== ENCRYPTION_VERSION) {
|
|
4308
|
+
return null;
|
|
4309
|
+
}
|
|
4310
|
+
if (typeof iv !== "string" || typeof authTag !== "string" || typeof ciphertext !== "string") {
|
|
4412
4311
|
return null;
|
|
4413
4312
|
}
|
|
4414
4313
|
return {
|
|
4415
|
-
|
|
4416
|
-
|
|
4314
|
+
version,
|
|
4315
|
+
iv,
|
|
4316
|
+
authTag,
|
|
4317
|
+
ciphertext
|
|
4417
4318
|
};
|
|
4418
4319
|
} catch {
|
|
4419
4320
|
return null;
|
|
4420
4321
|
}
|
|
4421
4322
|
}
|
|
4422
|
-
function
|
|
4423
|
-
return typeof value === "object" &&
|
|
4323
|
+
function isRecord2(value) {
|
|
4324
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
4424
4325
|
}
|
|
4425
|
-
function
|
|
4326
|
+
function getOwnEntry2(record2, key2) {
|
|
4426
4327
|
return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
|
|
4427
4328
|
}
|
|
4329
|
+
function isNotFoundError(error) {
|
|
4330
|
+
return hasOwnErrorCode(error, "ENOENT");
|
|
4331
|
+
}
|
|
4332
|
+
function isAlreadyExistsError(error) {
|
|
4333
|
+
return hasOwnErrorCode(error, "EEXIST");
|
|
4334
|
+
}
|
|
4428
4335
|
|
|
4429
|
-
// ../
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
callerAbort();
|
|
4456
|
-
let port;
|
|
4457
|
-
try {
|
|
4458
|
-
port = await startServer(server, target.port, target.host, controller.signal);
|
|
4459
|
-
} catch (error) {
|
|
4460
|
-
controller.abort(error);
|
|
4461
|
-
throw error;
|
|
4336
|
+
// ../auth-store/dist/keychain-store.js
|
|
4337
|
+
import { spawn } from "node:child_process";
|
|
4338
|
+
import { createHash } from "node:crypto";
|
|
4339
|
+
import { promises as nodeFs } from "node:fs";
|
|
4340
|
+
import { homedir as homedir2 } from "node:os";
|
|
4341
|
+
import path3 from "node:path";
|
|
4342
|
+
var SECURITY_CLI = "security";
|
|
4343
|
+
var KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
|
|
4344
|
+
var KeychainStore = class {
|
|
4345
|
+
runCommand;
|
|
4346
|
+
service;
|
|
4347
|
+
account;
|
|
4348
|
+
lockFs;
|
|
4349
|
+
lockDirectory;
|
|
4350
|
+
constructor(input) {
|
|
4351
|
+
this.runCommand = input.runCommand ?? runSecurityCommand;
|
|
4352
|
+
this.service = input.service.trim();
|
|
4353
|
+
this.account = input.account.trim();
|
|
4354
|
+
this.lockFs = input.lock?.fs ?? nodeFs;
|
|
4355
|
+
this.lockDirectory = input.lock?.directory ?? path3.join(homedir2(), ".auth-store", "keychain-locks");
|
|
4356
|
+
if (this.service.length === 0) {
|
|
4357
|
+
throw new Error("Keychain service must not be empty");
|
|
4358
|
+
}
|
|
4359
|
+
if (this.account.length === 0) {
|
|
4360
|
+
throw new Error("Keychain account must not be empty");
|
|
4361
|
+
}
|
|
4462
4362
|
}
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4363
|
+
async get() {
|
|
4364
|
+
const result = await this.executeSecurityCommand(["find-generic-password", "-s", this.service, "-a", this.account, "-w"], "read secret from macOS Keychain");
|
|
4365
|
+
if (getCommandExitCode(result) === 0) {
|
|
4366
|
+
return stripTrailingLineBreak(getCommandOutput(result, "stdout"));
|
|
4367
|
+
}
|
|
4368
|
+
if (isKeychainEntryNotFound(result)) {
|
|
4369
|
+
return null;
|
|
4370
|
+
}
|
|
4371
|
+
throw createSecurityCliFailure("read secret from macOS Keychain", result);
|
|
4372
|
+
}
|
|
4373
|
+
async withLock(operation, options = {}) {
|
|
4374
|
+
const identity = createHash("sha256").update(JSON.stringify([this.service, this.account])).digest("hex");
|
|
4375
|
+
return withSecretStoreFileLock(this.lockFs, path3.join(this.lockDirectory, identity), operation, options);
|
|
4376
|
+
}
|
|
4377
|
+
async set(value) {
|
|
4378
|
+
if (value.includes("\n") || value.includes("\r")) {
|
|
4379
|
+
throw new Error("Keychain secrets cannot contain line breaks");
|
|
4380
|
+
}
|
|
4381
|
+
const result = await this.executeSecurityCommand([
|
|
4382
|
+
"add-generic-password",
|
|
4383
|
+
"-s",
|
|
4384
|
+
this.service,
|
|
4385
|
+
"-a",
|
|
4386
|
+
this.account,
|
|
4387
|
+
"-U",
|
|
4388
|
+
"-w",
|
|
4389
|
+
value
|
|
4390
|
+
], "store secret in macOS Keychain");
|
|
4391
|
+
if (getCommandExitCode(result) !== 0) {
|
|
4392
|
+
throw createSecurityCliFailure("store secret in macOS Keychain", result);
|
|
4479
4393
|
}
|
|
4480
|
-
}
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
try {
|
|
4486
|
-
url = new URL(options.redirectUri);
|
|
4487
|
-
} catch (cause) {
|
|
4488
|
-
throw new Error("Invalid OAuth loopback redirect URI", { cause });
|
|
4394
|
+
}
|
|
4395
|
+
async delete() {
|
|
4396
|
+
const result = await this.executeSecurityCommand(["delete-generic-password", "-s", this.service, "-a", this.account], "delete secret from macOS Keychain");
|
|
4397
|
+
if (getCommandExitCode(result) === 0 || isKeychainEntryNotFound(result)) {
|
|
4398
|
+
return;
|
|
4489
4399
|
}
|
|
4490
|
-
|
|
4491
|
-
if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password || url.hash || url.port === "0" || forbiddenQuery.some((name) => url.searchParams.has(name)) || [...options.redirectUri].some((char) => char.codePointAt(0) <= 32) || options.callbackPath !== void 0 && options.callbackPath !== url.pathname)
|
|
4492
|
-
throw new Error("Invalid OAuth loopback redirect URI");
|
|
4493
|
-
return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
|
|
4400
|
+
throw createSecurityCliFailure("delete secret from macOS Keychain", result);
|
|
4494
4401
|
}
|
|
4495
|
-
|
|
4496
|
-
const parsed = new URL(callbackPath, "http://127.0.0.1");
|
|
4497
|
-
if (!callbackPath.startsWith("/") || parsed.origin !== "http://127.0.0.1" || parsed.pathname !== callbackPath || parsed.search || parsed.hash)
|
|
4498
|
-
throw new Error("Invalid OAuth loopback callback path");
|
|
4499
|
-
return { port: 0, host: "127.0.0.1", callbackPath };
|
|
4500
|
-
}
|
|
4501
|
-
async function startServer(server, port, host, signal) {
|
|
4502
|
-
signal.throwIfAborted();
|
|
4503
|
-
return new Promise((resolve, reject) => {
|
|
4504
|
-
const cleanup = () => {
|
|
4505
|
-
server.off("error", handleError);
|
|
4506
|
-
signal.removeEventListener("abort", aborted);
|
|
4507
|
-
};
|
|
4508
|
-
const handleError = (error) => {
|
|
4509
|
-
cleanup();
|
|
4510
|
-
reject(error);
|
|
4511
|
-
};
|
|
4512
|
-
const aborted = () => {
|
|
4513
|
-
cleanup();
|
|
4514
|
-
reject(signal.reason);
|
|
4515
|
-
};
|
|
4516
|
-
server.once("error", handleError);
|
|
4517
|
-
signal.addEventListener("abort", aborted, { once: true });
|
|
4402
|
+
async executeSecurityCommand(args, operation, options) {
|
|
4518
4403
|
try {
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
reject(signal.reason);
|
|
4524
|
-
return;
|
|
4525
|
-
}
|
|
4526
|
-
const address = server.address();
|
|
4527
|
-
if (address === null || typeof address === "string") {
|
|
4528
|
-
reject(new Error("OAuth listener has no TCP address"));
|
|
4529
|
-
return;
|
|
4530
|
-
}
|
|
4531
|
-
resolve(address.port);
|
|
4532
|
-
});
|
|
4404
|
+
if (options === void 0) {
|
|
4405
|
+
return await this.runCommand(SECURITY_CLI, args);
|
|
4406
|
+
}
|
|
4407
|
+
return await this.runCommand(SECURITY_CLI, args, options);
|
|
4533
4408
|
} catch (error) {
|
|
4534
|
-
|
|
4535
|
-
|
|
4409
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4410
|
+
throw new Error(`Failed to ${operation}: ${message}`);
|
|
4536
4411
|
}
|
|
4537
|
-
}
|
|
4538
|
-
}
|
|
4539
|
-
function
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
signal.removeEventListener("abort", aborted);
|
|
4550
|
-
fn();
|
|
4412
|
+
}
|
|
4413
|
+
};
|
|
4414
|
+
function runSecurityCommand(command, args, options) {
|
|
4415
|
+
return new Promise((resolve) => {
|
|
4416
|
+
const child = spawn(command, args, {
|
|
4417
|
+
stdio: [options?.stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
|
|
4418
|
+
});
|
|
4419
|
+
let stdout = "";
|
|
4420
|
+
let stderr = "";
|
|
4421
|
+
let stdinErrorMessage;
|
|
4422
|
+
const appendStderr = (message) => {
|
|
4423
|
+
stderr = stderr.length === 0 ? message : `${stderr}${stderr.endsWith("\n") ? "" : "\n"}${message}`;
|
|
4551
4424
|
};
|
|
4552
|
-
const
|
|
4553
|
-
|
|
4554
|
-
let url;
|
|
4555
|
-
try {
|
|
4556
|
-
url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
4557
|
-
} catch {
|
|
4558
|
-
res.writeHead(400);
|
|
4559
|
-
res.end("Invalid callback URL");
|
|
4560
|
-
return;
|
|
4561
|
-
}
|
|
4562
|
-
if (url.pathname !== callbackPath) {
|
|
4563
|
-
res.writeHead(404);
|
|
4564
|
-
res.end("Not found");
|
|
4425
|
+
const appendStdinError = () => {
|
|
4426
|
+
if (stdinErrorMessage === void 0) {
|
|
4565
4427
|
return;
|
|
4566
4428
|
}
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
error: url.searchParams.get("error"),
|
|
4570
|
-
errorDescription: url.searchParams.get("error_description"),
|
|
4571
|
-
state: url.searchParams.get("state"),
|
|
4572
|
-
iss: url.searchParams.get("iss")
|
|
4573
|
-
};
|
|
4574
|
-
try {
|
|
4575
|
-
validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
|
|
4576
|
-
if (callbackParameters.error !== null)
|
|
4577
|
-
throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
|
|
4578
|
-
const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
|
|
4579
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
4580
|
-
res.end(buildSuccessPage(options.landingPage));
|
|
4581
|
-
settle(() => resolve(code));
|
|
4582
|
-
} catch (error) {
|
|
4583
|
-
res.writeHead(400);
|
|
4584
|
-
res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
|
|
4585
|
-
settle(() => reject(error));
|
|
4586
|
-
}
|
|
4429
|
+
appendStderr(stdinErrorMessage);
|
|
4430
|
+
stdinErrorMessage = void 0;
|
|
4587
4431
|
};
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
settle(() => resolve(code));
|
|
4602
|
-
}).catch((error) => settle(() => reject(error)));
|
|
4603
|
-
}
|
|
4604
|
-
if (options.openBrowser !== void 0) {
|
|
4605
|
-
void Promise.resolve().then(() => settled ? void 0 : options.openBrowser(authorizationUrl)).catch((error) => settle(() => reject(error)));
|
|
4432
|
+
child.stdout?.setEncoding("utf8");
|
|
4433
|
+
child.stdout?.on("data", (chunk) => {
|
|
4434
|
+
stdout += chunk.toString();
|
|
4435
|
+
});
|
|
4436
|
+
child.stderr?.setEncoding("utf8");
|
|
4437
|
+
child.stderr?.on("data", (chunk) => {
|
|
4438
|
+
stderr += chunk.toString();
|
|
4439
|
+
});
|
|
4440
|
+
if (options?.stdin !== void 0) {
|
|
4441
|
+
child.stdin?.once("error", (error) => {
|
|
4442
|
+
stdinErrorMessage = error instanceof Error ? error.message : String(error);
|
|
4443
|
+
});
|
|
4444
|
+
child.stdin?.end(options.stdin);
|
|
4606
4445
|
}
|
|
4446
|
+
child.on("error", (error) => {
|
|
4447
|
+
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
4448
|
+
appendStdinError();
|
|
4449
|
+
appendStderr(message);
|
|
4450
|
+
resolve({
|
|
4451
|
+
stdout,
|
|
4452
|
+
stderr,
|
|
4453
|
+
exitCode: 127
|
|
4454
|
+
});
|
|
4455
|
+
});
|
|
4456
|
+
child.on("close", (code) => {
|
|
4457
|
+
appendStdinError();
|
|
4458
|
+
resolve({
|
|
4459
|
+
stdout,
|
|
4460
|
+
stderr,
|
|
4461
|
+
exitCode: code ?? 1
|
|
4462
|
+
});
|
|
4463
|
+
});
|
|
4607
4464
|
});
|
|
4608
4465
|
}
|
|
4609
|
-
function
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4466
|
+
function stripTrailingLineBreak(value) {
|
|
4467
|
+
if (value.endsWith("\r\n")) {
|
|
4468
|
+
return value.slice(0, -2);
|
|
4469
|
+
}
|
|
4470
|
+
if (value.endsWith("\n") || value.endsWith("\r")) {
|
|
4471
|
+
return value.slice(0, -1);
|
|
4472
|
+
}
|
|
4473
|
+
return value;
|
|
4474
|
+
}
|
|
4475
|
+
function isKeychainEntryNotFound(result) {
|
|
4476
|
+
return getCommandExitCode(result) === KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE;
|
|
4477
|
+
}
|
|
4478
|
+
function createSecurityCliFailure(operation, result) {
|
|
4479
|
+
const exitCode = getCommandExitCode(result);
|
|
4480
|
+
const details = getCommandOutput(result, "stderr").trim() || getCommandOutput(result, "stdout").trim();
|
|
4481
|
+
if (details) {
|
|
4482
|
+
return new Error(`Failed to ${operation}: security exited with code ${exitCode}: ${details}`);
|
|
4483
|
+
}
|
|
4484
|
+
return new Error(`Failed to ${operation}: security exited with code ${exitCode}`);
|
|
4485
|
+
}
|
|
4486
|
+
function getCommandExitCode(result) {
|
|
4487
|
+
const value = getOwnEntry3(result, "exitCode");
|
|
4488
|
+
return typeof value === "number" && Number.isInteger(value) ? value : 1;
|
|
4489
|
+
}
|
|
4490
|
+
function getCommandOutput(result, key2) {
|
|
4491
|
+
const value = getOwnEntry3(result, key2);
|
|
4492
|
+
return typeof value === "string" ? value : "";
|
|
4493
|
+
}
|
|
4494
|
+
function getOwnEntry3(record2, key2) {
|
|
4495
|
+
return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
// ../auth-store/dist/create-secret-store.js
|
|
4499
|
+
var DEFAULT_BACKEND_ENV_VAR = "AUTH_BACKEND";
|
|
4500
|
+
var MACOS_PLATFORM = "darwin";
|
|
4501
|
+
var storeFactories = {
|
|
4502
|
+
file: (input) => {
|
|
4503
|
+
if (!input.fileStore) {
|
|
4504
|
+
throw new Error("fileStore configuration is required for file backend");
|
|
4505
|
+
}
|
|
4506
|
+
return new EncryptedFileStore(input.fileStore);
|
|
4507
|
+
},
|
|
4508
|
+
keychain: (input) => {
|
|
4509
|
+
if (!input.keychainStore) {
|
|
4510
|
+
throw new Error("keychainStore configuration is required for keychain backend");
|
|
4511
|
+
}
|
|
4512
|
+
return new KeychainStore(input.keychainStore);
|
|
4513
|
+
}
|
|
4514
|
+
};
|
|
4515
|
+
function createSecretStore(input) {
|
|
4516
|
+
const backend = resolveBackend(input);
|
|
4517
|
+
const platform = input.platform ?? process.platform;
|
|
4518
|
+
if (backend === "keychain" && platform !== MACOS_PLATFORM) {
|
|
4519
|
+
throw new Error(`Keychain backend is only supported on macOS. Current platform: ${platform}`);
|
|
4520
|
+
}
|
|
4521
|
+
const store = storeFactories[backend](input);
|
|
4522
|
+
return { backend, store };
|
|
4523
|
+
}
|
|
4524
|
+
function resolveBackend(input) {
|
|
4525
|
+
const envVar = input.backendEnvVar ?? DEFAULT_BACKEND_ENV_VAR;
|
|
4526
|
+
const configuredBackend = input.backend ?? getOwnEnvValue(input.env, envVar) ?? getOwnEnvValue(process.env, envVar);
|
|
4527
|
+
const backend = configuredBackend?.trim();
|
|
4528
|
+
if (backend === "keychain") {
|
|
4529
|
+
return "keychain";
|
|
4530
|
+
}
|
|
4531
|
+
if (backend === void 0 || backend === "file") {
|
|
4532
|
+
return "file";
|
|
4613
4533
|
}
|
|
4534
|
+
throw new Error(`Unsupported auth store backend: ${backend}`);
|
|
4535
|
+
}
|
|
4536
|
+
function getOwnEnvValue(env, key2) {
|
|
4537
|
+
return env !== void 0 && Object.prototype.hasOwnProperty.call(env, key2) ? env[key2] : void 0;
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
// ../mcp-oauth/dist/resource-indicator.js
|
|
4541
|
+
function canonicalizeResourceIndicator(value) {
|
|
4542
|
+
let url;
|
|
4614
4543
|
try {
|
|
4615
|
-
|
|
4616
|
-
return {
|
|
4617
|
-
code: url.searchParams.get("code"),
|
|
4618
|
-
error: url.searchParams.get("error"),
|
|
4619
|
-
errorDescription: url.searchParams.get("error_description"),
|
|
4620
|
-
state: url.searchParams.get("state"),
|
|
4621
|
-
iss: url.searchParams.get("iss")
|
|
4622
|
-
};
|
|
4544
|
+
url = value instanceof URL ? new URL(value.toString()) : new URL(value);
|
|
4623
4545
|
} catch {
|
|
4624
|
-
|
|
4625
|
-
code: trimmed,
|
|
4626
|
-
error: null,
|
|
4627
|
-
errorDescription: null,
|
|
4628
|
-
state: null,
|
|
4629
|
-
iss: null
|
|
4630
|
-
};
|
|
4546
|
+
throw new Error("Resource indicator must be an absolute URL");
|
|
4631
4547
|
}
|
|
4548
|
+
url.hash = "";
|
|
4549
|
+
return url.toString();
|
|
4632
4550
|
}
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4551
|
+
|
|
4552
|
+
// ../mcp-oauth/dist/client/auth-store-session-store.js
|
|
4553
|
+
var DEFAULT_FILE_SALT = "poe-code:mcp-oauth:v1";
|
|
4554
|
+
var DEFAULT_FILE_DIRECTORY = ".poe-code/mcp-oauth";
|
|
4555
|
+
var DEFAULT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth";
|
|
4556
|
+
var DEFAULT_CLIENT_FILE_SALT = "poe-code:mcp-oauth:clients:v1";
|
|
4557
|
+
var DEFAULT_CLIENT_FILE_DIRECTORY = ".poe-code/mcp-oauth/clients";
|
|
4558
|
+
var DEFAULT_CLIENT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth-clients";
|
|
4559
|
+
var MAX_JS_DATE_MS = 864e13;
|
|
4560
|
+
function createAuthStoreSessionStore(options = {}, namespace) {
|
|
4561
|
+
assertPersistenceNamespace(namespace);
|
|
4637
4562
|
return {
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4563
|
+
async withLock(resource, operation, lockOptions) {
|
|
4564
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
4565
|
+
if (store.withLock === void 0)
|
|
4566
|
+
throw new Error("OAuth secret-store backend does not support transaction locks");
|
|
4567
|
+
return store.withLock(operation, lockOptions);
|
|
4568
|
+
},
|
|
4569
|
+
async load(resource) {
|
|
4570
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
4571
|
+
const value = await store.get();
|
|
4572
|
+
if (value === null) {
|
|
4573
|
+
return null;
|
|
4574
|
+
}
|
|
4575
|
+
let parsed;
|
|
4576
|
+
try {
|
|
4577
|
+
parsed = JSON.parse(value);
|
|
4578
|
+
} catch {
|
|
4579
|
+
throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
|
|
4580
|
+
}
|
|
4581
|
+
if (isStoredOAuthSession(parsed)) {
|
|
4582
|
+
return parsed;
|
|
4583
|
+
}
|
|
4584
|
+
throw new Error("Stored OAuth session must match the expected shape");
|
|
4585
|
+
},
|
|
4586
|
+
async save(resource, session) {
|
|
4587
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
4588
|
+
await store.set(JSON.stringify(session));
|
|
4589
|
+
},
|
|
4590
|
+
async clear(resource) {
|
|
4591
|
+
const store = createResourceSecretStore(resource, options, namespace);
|
|
4592
|
+
await store.delete();
|
|
4593
|
+
}
|
|
4641
4594
|
};
|
|
4642
4595
|
}
|
|
4643
|
-
function
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4596
|
+
function createAuthStoreClientStore(options, namespace) {
|
|
4597
|
+
assertPersistenceNamespace(namespace);
|
|
4598
|
+
return {
|
|
4599
|
+
async load(issuer) {
|
|
4600
|
+
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
4601
|
+
const value = await store.get();
|
|
4602
|
+
if (value === null) {
|
|
4603
|
+
return null;
|
|
4604
|
+
}
|
|
4605
|
+
let parsed;
|
|
4606
|
+
try {
|
|
4607
|
+
parsed = JSON.parse(value);
|
|
4608
|
+
} catch {
|
|
4609
|
+
throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
|
|
4610
|
+
}
|
|
4611
|
+
if (isObjectRecord2(parsed) && typeof getOwnEntry4(parsed, "clientId") === "string")
|
|
4612
|
+
return parsed;
|
|
4613
|
+
throw new Error("Stored OAuth client must be a JSON object with clientId");
|
|
4614
|
+
},
|
|
4615
|
+
async save(issuer, client) {
|
|
4616
|
+
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
4617
|
+
await store.set(JSON.stringify(client));
|
|
4618
|
+
},
|
|
4619
|
+
async clear(issuer) {
|
|
4620
|
+
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
4621
|
+
await store.delete();
|
|
4622
|
+
}
|
|
4623
|
+
};
|
|
4624
|
+
}
|
|
4625
|
+
function createNamedSecretStore(key2, options, defaults, namespace) {
|
|
4626
|
+
const hash = crypto2.createHash("sha256").update(namespace === void 0 ? key2 : JSON.stringify([namespace, key2])).digest("hex");
|
|
4627
|
+
const configuredFilePath = options.fileStore?.filePath;
|
|
4628
|
+
const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
|
|
4629
|
+
const fileStore = {
|
|
4630
|
+
...options.fileStore,
|
|
4631
|
+
throwOnInvalidDocument: true,
|
|
4632
|
+
filePath: parsedFilePath === null ? void 0 : path4.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
|
|
4633
|
+
salt: options.fileStore?.salt ?? defaults.salt,
|
|
4634
|
+
defaultDirectory: options.fileStore?.defaultDirectory || defaults.directory,
|
|
4635
|
+
defaultFileName: parsedFilePath === null ? `${hash}.enc` : `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`
|
|
4636
|
+
};
|
|
4637
|
+
const keychainStore = {
|
|
4638
|
+
...options.keychainStore,
|
|
4639
|
+
service: options.keychainStore?.service ?? defaults.service,
|
|
4640
|
+
account: `${options.keychainStore?.account ?? defaults.accountPrefix}:${hash}`
|
|
4641
|
+
};
|
|
4642
|
+
return createSecretStore({ ...options, fileStore, keychainStore }).store;
|
|
4643
|
+
}
|
|
4644
|
+
function createResourceSecretStore(resource, options, namespace) {
|
|
4645
|
+
return createNamedSecretStore(canonicalizeResourceIndicator(resource), options, {
|
|
4646
|
+
salt: DEFAULT_FILE_SALT,
|
|
4647
|
+
directory: DEFAULT_FILE_DIRECTORY,
|
|
4648
|
+
service: DEFAULT_KEYCHAIN_SERVICE,
|
|
4649
|
+
accountPrefix: "provider"
|
|
4650
|
+
}, namespace);
|
|
4651
|
+
}
|
|
4652
|
+
function createIssuerSecretStore(issuer, options, namespace) {
|
|
4653
|
+
return createNamedSecretStore(issuer, options, {
|
|
4654
|
+
salt: DEFAULT_CLIENT_FILE_SALT,
|
|
4655
|
+
directory: DEFAULT_CLIENT_FILE_DIRECTORY,
|
|
4656
|
+
service: DEFAULT_CLIENT_KEYCHAIN_SERVICE,
|
|
4657
|
+
accountPrefix: "issuer"
|
|
4658
|
+
}, namespace);
|
|
4659
|
+
}
|
|
4660
|
+
function assertPersistenceNamespace(namespace) {
|
|
4661
|
+
if (namespace !== void 0 && (typeof namespace !== "string" || namespace.trim() === "" || Buffer.byteLength(namespace, "utf8") > 1024))
|
|
4662
|
+
throw new Error("OAuth persistence namespace must be a nonempty string within 1024 bytes");
|
|
4663
|
+
}
|
|
4664
|
+
function isObjectRecord2(value) {
|
|
4665
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4666
|
+
}
|
|
4667
|
+
function getOwnEntry4(record2, key2) {
|
|
4668
|
+
return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
|
|
4669
|
+
}
|
|
4670
|
+
function getOwnString(record2, key2) {
|
|
4671
|
+
const value = getOwnEntry4(record2, key2);
|
|
4672
|
+
return typeof value === "string" ? value : void 0;
|
|
4673
|
+
}
|
|
4674
|
+
function isStoredOAuthSession(value) {
|
|
4675
|
+
if (!isObjectRecord2(value)) {
|
|
4676
|
+
return false;
|
|
4647
4677
|
}
|
|
4648
|
-
return
|
|
4678
|
+
return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && normalizeStoredOAuthClient(getOwnEntry4(value, "client")) !== null && isStoredOAuthDiscovery(getOwnEntry4(value, "discovery")) && (getOwnEntry4(value, "requestedScope") === void 0 || isNonBlankOwnString(value, "requestedScope")) && (getOwnEntry4(value, "refreshState") === void 0 || getOwnEntry4(value, "refreshState") === "pending" && getOwnEntry4(value, "tokens") === void 0) && isStoredOAuthTokensOrMissing(getOwnEntry4(value, "tokens"));
|
|
4649
4679
|
}
|
|
4650
|
-
function
|
|
4651
|
-
if (
|
|
4652
|
-
|
|
4653
|
-
throw new Error("OAuth callback missing state");
|
|
4654
|
-
}
|
|
4655
|
-
if (callback.state !== expected.state) {
|
|
4656
|
-
throw new Error("OAuth callback state mismatch");
|
|
4657
|
-
}
|
|
4680
|
+
function isStoredOAuthDiscovery(value) {
|
|
4681
|
+
if (!isObjectRecord2(value)) {
|
|
4682
|
+
return false;
|
|
4658
4683
|
}
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4684
|
+
return isNonBlankOwnString(value, "resourceMetadataUrl") && isObjectRecord2(getOwnEntry4(value, "resourceMetadata")) && isObjectRecord2(getOwnEntry4(value, "authorizationServerMetadata"));
|
|
4685
|
+
}
|
|
4686
|
+
function isStoredOAuthTokensOrMissing(value) {
|
|
4687
|
+
if (value === void 0) {
|
|
4688
|
+
return true;
|
|
4663
4689
|
}
|
|
4664
|
-
if (
|
|
4665
|
-
|
|
4690
|
+
if (!isObjectRecord2(value)) {
|
|
4691
|
+
return false;
|
|
4692
|
+
}
|
|
4693
|
+
if (!isNonBlankOwnString(value, "accessToken") || getOwnString(value, "tokenType") !== "Bearer") {
|
|
4694
|
+
return false;
|
|
4695
|
+
}
|
|
4696
|
+
const expiresAt = getOwnEntry4(value, "expiresAt");
|
|
4697
|
+
if (expiresAt !== null && (typeof expiresAt !== "number" || !Number.isSafeInteger(expiresAt) || expiresAt > MAX_JS_DATE_MS || !Number.isFinite(new Date(expiresAt).getTime()))) {
|
|
4698
|
+
return false;
|
|
4666
4699
|
}
|
|
4700
|
+
const refreshToken = getOwnEntry4(value, "refreshToken");
|
|
4701
|
+
if (refreshToken !== void 0 && (typeof refreshToken !== "string" || refreshToken.trim().length === 0)) {
|
|
4702
|
+
return false;
|
|
4703
|
+
}
|
|
4704
|
+
const scope = getOwnEntry4(value, "scope");
|
|
4705
|
+
return scope === void 0 || typeof scope === "string" && scope.trim().length > 0;
|
|
4667
4706
|
}
|
|
4668
|
-
function
|
|
4669
|
-
|
|
4707
|
+
function isNonBlankOwnString(record2, key2) {
|
|
4708
|
+
const value = getOwnString(record2, key2);
|
|
4709
|
+
return value !== void 0 && value.trim().length > 0;
|
|
4670
4710
|
}
|
|
4671
|
-
|
|
4672
|
-
|
|
4711
|
+
|
|
4712
|
+
// ../mcp-oauth/dist/client/resource-bound-store.js
|
|
4713
|
+
function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
4714
|
+
assertPersistenceNamespace(identity);
|
|
4715
|
+
const store = createNamedSecretStore(identity, options, {
|
|
4716
|
+
salt: "poe-code:mcp-oauth:resources:v1",
|
|
4717
|
+
directory: ".poe-code/mcp-oauth/resources",
|
|
4718
|
+
service: "poe-code-mcp-oauth-resources",
|
|
4719
|
+
accountPrefix: "resource"
|
|
4720
|
+
}, namespace);
|
|
4721
|
+
const result = { initialGrantAllowed: true, sessionStore: {}, clientStore: {} };
|
|
4722
|
+
async function read() {
|
|
4723
|
+
const raw = await store.get();
|
|
4724
|
+
if (raw === null)
|
|
4725
|
+
return null;
|
|
4726
|
+
let value;
|
|
4727
|
+
try {
|
|
4728
|
+
value = JSON.parse(raw);
|
|
4729
|
+
} catch {
|
|
4730
|
+
throw new Error("Stored OAuth resource identity must be valid JSON; reset explicitly to recover");
|
|
4731
|
+
}
|
|
4732
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
4733
|
+
throw new Error("Invalid stored OAuth resource identity");
|
|
4734
|
+
const record2 = value;
|
|
4735
|
+
if (!["version", "resource", "generation", "session", "clients"].every((key2) => Object.hasOwn(record2, key2)) || record2.version !== 1 || typeof record2.resource !== "string" || record2.resource !== canonicalizeResourceIndicator(record2.resource) || !Number.isSafeInteger(record2.generation) || record2.generation < 0 || record2.session !== null && (!isStoredOAuthSession(record2.session) || record2.session.resource !== record2.resource) || typeof record2.clients !== "object" || record2.clients === null || Array.isArray(record2.clients))
|
|
4736
|
+
throw new Error("Invalid stored OAuth resource identity; reset explicitly to recover");
|
|
4737
|
+
let resourceUrl;
|
|
4738
|
+
try {
|
|
4739
|
+
resourceUrl = new URL(record2.resource);
|
|
4740
|
+
} catch {
|
|
4741
|
+
throw new Error("Invalid stored OAuth resource identity URL");
|
|
4742
|
+
}
|
|
4743
|
+
if (!["http:", "https:"].includes(resourceUrl.protocol) || resourceUrl.username || resourceUrl.password || resourceUrl.hash)
|
|
4744
|
+
throw new Error("Invalid stored OAuth resource identity URL");
|
|
4745
|
+
for (const [issuer, client] of Object.entries(record2.clients)) {
|
|
4746
|
+
const normalized = normalizeStoredOAuthClient(client);
|
|
4747
|
+
let url;
|
|
4748
|
+
try {
|
|
4749
|
+
url = new URL(issuer);
|
|
4750
|
+
} catch {
|
|
4751
|
+
throw new Error("Invalid stored OAuth resource client issuer");
|
|
4752
|
+
}
|
|
4753
|
+
if (normalized === null || !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
|
|
4754
|
+
throw new Error("Invalid stored OAuth resource client");
|
|
4755
|
+
Object.defineProperty(record2.clients, issuer, { value: normalized, enumerable: true, configurable: true, writable: true });
|
|
4756
|
+
}
|
|
4757
|
+
return record2;
|
|
4758
|
+
}
|
|
4759
|
+
async function reconcile(resource) {
|
|
4760
|
+
resource = canonicalizeResourceIndicator(resource);
|
|
4761
|
+
const existing = await read();
|
|
4762
|
+
if (existing?.resource === resource) {
|
|
4763
|
+
result.initialGrantAllowed = existing.generation === 0;
|
|
4764
|
+
return existing;
|
|
4765
|
+
}
|
|
4766
|
+
const record2 = { version: 1, resource, generation: existing === null ? 0 : existing.generation + 1, session: null, clients: {} };
|
|
4767
|
+
if (!Number.isSafeInteger(record2.generation))
|
|
4768
|
+
throw new Error("OAuth resource identity generation limit exceeded");
|
|
4769
|
+
await store.set(JSON.stringify(record2));
|
|
4770
|
+
result.initialGrantAllowed = record2.generation === 0;
|
|
4771
|
+
return record2;
|
|
4772
|
+
}
|
|
4773
|
+
result.sessionStore = {
|
|
4774
|
+
async withLock(_resource, operation, options2) {
|
|
4775
|
+
if (store.withLock === void 0)
|
|
4776
|
+
throw new Error("OAuth resource identity backend must support transaction locks");
|
|
4777
|
+
return store.withLock(operation, options2);
|
|
4778
|
+
},
|
|
4779
|
+
async load(resource) {
|
|
4780
|
+
return (await reconcile(resource)).session;
|
|
4781
|
+
},
|
|
4782
|
+
async save(resource, session) {
|
|
4783
|
+
const record2 = await reconcile(resource);
|
|
4784
|
+
if (canonicalizeResourceIndicator(session.resource) !== record2.resource)
|
|
4785
|
+
throw new Error("OAuth session does not match its resource identity");
|
|
4786
|
+
record2.session = session;
|
|
4787
|
+
await store.set(JSON.stringify(record2));
|
|
4788
|
+
},
|
|
4789
|
+
async clear(resource) {
|
|
4790
|
+
const record2 = await reconcile(resource);
|
|
4791
|
+
record2.session = null;
|
|
4792
|
+
record2.generation = Math.max(1, record2.generation);
|
|
4793
|
+
result.initialGrantAllowed = false;
|
|
4794
|
+
await store.set(JSON.stringify(record2));
|
|
4795
|
+
}
|
|
4796
|
+
};
|
|
4797
|
+
result.clientStore = {
|
|
4798
|
+
async load(issuer) {
|
|
4799
|
+
const record2 = await read();
|
|
4800
|
+
return record2 !== null && Object.hasOwn(record2.clients, issuer) ? record2.clients[issuer] : null;
|
|
4801
|
+
},
|
|
4802
|
+
async save(issuer, client) {
|
|
4803
|
+
const record2 = await read();
|
|
4804
|
+
if (record2 === null)
|
|
4805
|
+
throw new Error("OAuth resource identity must be bound before registering a client");
|
|
4806
|
+
Object.defineProperty(record2.clients, issuer, { value: client, enumerable: true, configurable: true, writable: true });
|
|
4807
|
+
await store.set(JSON.stringify(record2));
|
|
4808
|
+
},
|
|
4809
|
+
async clear(issuer) {
|
|
4810
|
+
const record2 = await read();
|
|
4811
|
+
if (record2 === null || !Object.hasOwn(record2.clients, issuer))
|
|
4812
|
+
return;
|
|
4813
|
+
delete record2.clients[issuer];
|
|
4814
|
+
await store.set(JSON.stringify(record2));
|
|
4815
|
+
}
|
|
4816
|
+
};
|
|
4817
|
+
return result;
|
|
4673
4818
|
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4819
|
+
|
|
4820
|
+
// ../mcp-oauth/dist/client/default-oauth-client-provider.js
|
|
4821
|
+
import { isIP } from "node:net";
|
|
4822
|
+
|
|
4823
|
+
// ../mcp-oauth/dist/http-fetch.js
|
|
4824
|
+
async function fetchMcpResponse(fetchImplementation, input, init = {}) {
|
|
4825
|
+
const response = await fetchImplementation(input, { ...init, redirect: "error" });
|
|
4826
|
+
if (response.redirected || response.type === "opaqueredirect") {
|
|
4827
|
+
void response.body?.cancel().catch(() => void 0);
|
|
4828
|
+
throw new Error("MCP HTTP redirects are not allowed");
|
|
4829
|
+
}
|
|
4830
|
+
return response;
|
|
4686
4831
|
}
|
|
4687
4832
|
|
|
4833
|
+
// ../mcp-oauth/dist/client/default-oauth-client-provider.js
|
|
4834
|
+
import { URL as URL2 } from "node:url";
|
|
4835
|
+
|
|
4688
4836
|
// ../mcp-oauth/dist/client/pkce.js
|
|
4689
4837
|
import crypto3 from "node:crypto";
|
|
4690
4838
|
function generateCodeVerifier() {
|
|
@@ -4983,8 +5131,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4983
5131
|
const configuredClient = normalizeConfiguredClient(options.client);
|
|
4984
5132
|
const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
|
|
4985
5133
|
const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
|
|
4986
|
-
|
|
4987
|
-
|
|
5134
|
+
if (options.resourceIdentity !== void 0 && options.sessionStore !== void 0)
|
|
5135
|
+
throw new Error("OAuth resourceIdentity requires native-owned persistence; custom stores own their resource trust policy");
|
|
5136
|
+
const resourceStores = options.resourceIdentity === void 0 ? void 0 : createResourceBoundOAuthStores(options.authStore ?? {}, options.persistenceNamespace, options.resourceIdentity);
|
|
5137
|
+
const sessionStore = resourceStores?.sessionStore ?? options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
|
|
5138
|
+
const clientStore = resourceStores?.clientStore ?? (options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace));
|
|
4988
5139
|
const now = options.now ?? Date.now;
|
|
4989
5140
|
const registeredClients = /* @__PURE__ */ new Map();
|
|
4990
5141
|
if (options.initialGrant !== void 0) {
|
|
@@ -4999,7 +5150,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4999
5150
|
}
|
|
5000
5151
|
const initialGrant = options.initialGrant === void 0 ? void 0 : {
|
|
5001
5152
|
resource: canonicalizeResourceIndicator(options.initialGrant.resource),
|
|
5002
|
-
tokens:
|
|
5153
|
+
tokens: normalizeImportedTokens(options.initialGrant.tokens, now),
|
|
5003
5154
|
client: configuredClient
|
|
5004
5155
|
};
|
|
5005
5156
|
if (initialGrant !== void 0 && (initialGrant.tokens === void 0 || initialGrant.client === null))
|
|
@@ -5077,6 +5228,15 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5077
5228
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
5078
5229
|
return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
|
|
5079
5230
|
let session = await loadSession(canonicalResource);
|
|
5231
|
+
if (resourceStores !== void 0) {
|
|
5232
|
+
registeredClients.clear();
|
|
5233
|
+
if (!resourceStores.initialGrantAllowed)
|
|
5234
|
+
initialGrantConsumed = true;
|
|
5235
|
+
}
|
|
5236
|
+
if (session !== null)
|
|
5237
|
+
assertRegistrationIssuer(session.client, session.authorizationServer);
|
|
5238
|
+
if (configuredClient !== null && discovery !== void 0)
|
|
5239
|
+
assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
|
|
5080
5240
|
if (session !== null && initialGrant?.resource === canonicalResource)
|
|
5081
5241
|
initialGrantConsumed = true;
|
|
5082
5242
|
signal?.throwIfAborted();
|
|
@@ -5122,6 +5282,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5122
5282
|
return session;
|
|
5123
5283
|
}
|
|
5124
5284
|
if (session?.tokens?.refreshToken !== void 0 && sessionDiscovery !== void 0 && (forceRefresh || isExpired(session.tokens, now))) {
|
|
5285
|
+
if (hasExpiredClientSecret(session.client, now)) {
|
|
5286
|
+
if (!allowInteractive || options.allowInteractive === false || options.client.mode === "static" || configuredClient?.registration !== void 0)
|
|
5287
|
+
throw new Error("OAuth client secret has expired; authorize again or update the imported registration");
|
|
5288
|
+
return authorizeSession(canonicalResource, clearSessionTokens(session), sessionDiscovery, fetch2, signal);
|
|
5289
|
+
}
|
|
5125
5290
|
session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
|
|
5126
5291
|
if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) {
|
|
5127
5292
|
return session;
|
|
@@ -5289,6 +5454,9 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5289
5454
|
if (configuredClient === null) {
|
|
5290
5455
|
throw new Error("OAuth client_id must not be blank");
|
|
5291
5456
|
}
|
|
5457
|
+
assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
|
|
5458
|
+
if (hasExpiredClientSecret(configuredClient, now))
|
|
5459
|
+
throw new Error("OAuth client secret has expired; update the imported registration");
|
|
5292
5460
|
return {
|
|
5293
5461
|
kind: "static",
|
|
5294
5462
|
fromStoredRegistration: false,
|
|
@@ -5303,7 +5471,14 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5303
5471
|
client: configuredClient
|
|
5304
5472
|
};
|
|
5305
5473
|
}
|
|
5306
|
-
|
|
5474
|
+
let storedClient = await loadRegisteredClient(discovery.authorizationServer);
|
|
5475
|
+
if (storedClient !== null) {
|
|
5476
|
+
assertRegistrationIssuer(storedClient, discovery.authorizationServer);
|
|
5477
|
+
if (hasExpiredClientSecret(storedClient, now) || !registrationMatchesRedirect(storedClient, redirectUri)) {
|
|
5478
|
+
await clearRegisteredClient(discovery.authorizationServer);
|
|
5479
|
+
storedClient = null;
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5307
5482
|
if (storedClient !== null) {
|
|
5308
5483
|
return {
|
|
5309
5484
|
kind: "dynamic",
|
|
@@ -5312,7 +5487,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5312
5487
|
};
|
|
5313
5488
|
}
|
|
5314
5489
|
if (registrationEndpoint === void 0) {
|
|
5315
|
-
if (existingSession !== null && existingSession.client.clientId.length > 0) {
|
|
5490
|
+
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
|
|
5316
5491
|
return {
|
|
5317
5492
|
kind: "dynamic",
|
|
5318
5493
|
fromStoredRegistration: true,
|
|
@@ -5321,7 +5496,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5321
5496
|
}
|
|
5322
5497
|
throw new Error("Authorization server metadata is missing registration_endpoint");
|
|
5323
5498
|
}
|
|
5324
|
-
if (existingSession !== null && existingSession.client.clientId.length > 0) {
|
|
5499
|
+
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
|
|
5325
5500
|
const isConfiguredStaticFallback = configuredClient !== null && existingSession.client.clientId === configuredClient.clientId && existingSession.client.clientSecret === configuredClient.clientSecret;
|
|
5326
5501
|
if (!isConfiguredStaticFallback) {
|
|
5327
5502
|
await saveRegisteredClient(discovery.authorizationServer, existingSession.client);
|
|
@@ -5357,11 +5532,17 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5357
5532
|
...responseMethod === void 0 ? {} : { tokenEndpointAuthMethod: responseMethod },
|
|
5358
5533
|
registration
|
|
5359
5534
|
};
|
|
5360
|
-
|
|
5535
|
+
assertRegistrationIssuer(registeredClient, discovery.authorizationServer);
|
|
5536
|
+
if (hasExpiredClientSecret(registeredClient, now))
|
|
5537
|
+
throw new Error("OAuth client secret has expired in the registration response");
|
|
5538
|
+
if (!registrationMatchesRedirect(registeredClient, redirectUri, true))
|
|
5539
|
+
throw new Error("OAuth registration response does not match the requested redirect URI");
|
|
5540
|
+
const clientWithRedirect = { ...registeredClient, requestedRedirectUri: redirectUri };
|
|
5541
|
+
await saveRegisteredClient(discovery.authorizationServer, clientWithRedirect);
|
|
5361
5542
|
return {
|
|
5362
5543
|
kind: "dynamic",
|
|
5363
5544
|
fromStoredRegistration: false,
|
|
5364
|
-
client:
|
|
5545
|
+
client: clientWithRedirect
|
|
5365
5546
|
};
|
|
5366
5547
|
}
|
|
5367
5548
|
async function loadSession(resource) {
|
|
@@ -5464,6 +5645,19 @@ function normalizeLoadedSession(session) {
|
|
|
5464
5645
|
tokens: normalizeStoredTokens(getOwnEntry6(session, "tokens"))
|
|
5465
5646
|
};
|
|
5466
5647
|
}
|
|
5648
|
+
function normalizeImportedTokens(value, now) {
|
|
5649
|
+
if (!isObjectRecord3(value))
|
|
5650
|
+
return void 0;
|
|
5651
|
+
const absolute = getOwnEntry6(value, "expiresAt");
|
|
5652
|
+
const lifetime = getOwnEntry6(value, "expiresIn");
|
|
5653
|
+
const issuedAt = getOwnEntry6(value, "issuedAt");
|
|
5654
|
+
if (lifetime !== void 0 && (typeof lifetime !== "number" || !Number.isSafeInteger(lifetime) || lifetime < 0))
|
|
5655
|
+
throw new Error("OAuth initial grant has invalid relative expiry");
|
|
5656
|
+
if (issuedAt !== void 0 && (typeof issuedAt !== "number" || !Number.isSafeInteger(issuedAt) || Math.abs(issuedAt) > MAX_JS_DATE_MS3))
|
|
5657
|
+
throw new Error("OAuth initial grant has invalid issuance time");
|
|
5658
|
+
const expiresAt = absolute !== void 0 && absolute !== null ? absolute : lifetime === void 0 ? null : (issuedAt === void 0 ? now() : issuedAt) + lifetime * 1e3;
|
|
5659
|
+
return normalizeStoredTokens({ ...value, expiresAt });
|
|
5660
|
+
}
|
|
5467
5661
|
function normalizeStoredTokens(value) {
|
|
5468
5662
|
if (value === void 0 || !isObjectRecord3(value)) {
|
|
5469
5663
|
return void 0;
|
|
@@ -5604,6 +5798,17 @@ function assertRequestMatchesResource(requestUrl, resource) {
|
|
|
5604
5798
|
throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
|
|
5605
5799
|
}
|
|
5606
5800
|
}
|
|
5801
|
+
function assertRegistrationIssuer(client, issuer) {
|
|
5802
|
+
const registrationIssuer = client.registration === void 0 ? void 0 : getOwnString2(client.registration, "issuer");
|
|
5803
|
+
if (registrationIssuer !== void 0 && registrationIssuer !== issuer)
|
|
5804
|
+
throw new Error("OAuth client registration issuer does not match the authorization server");
|
|
5805
|
+
}
|
|
5806
|
+
function hasExpiredClientSecret(client, now) {
|
|
5807
|
+
if (client.clientSecret === void 0 || client.tokenEndpointAuthMethod === "none" || client.registration === void 0)
|
|
5808
|
+
return false;
|
|
5809
|
+
const expiry = getOwnEntry6(client.registration, "client_secret_expires_at");
|
|
5810
|
+
return typeof expiry === "number" && expiry !== 0 && expiry <= now() / 1e3;
|
|
5811
|
+
}
|
|
5607
5812
|
function getSupportedTokenAuthMethods(metadata) {
|
|
5608
5813
|
const value = getOwnEntry6(metadata, "token_endpoint_auth_methods_supported");
|
|
5609
5814
|
if (value === void 0)
|