tiny-http-mcp-server 0.1.35 → 0.1.36
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/dist/composition.json +1 -1
- package/node_modules/mcp-oauth/README.md +8 -0
- package/node_modules/mcp-oauth/dist/client/client-registration.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/client/client-registration.js +45 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +9 -6
- package/node_modules/mcp-oauth/dist/client/types.d.ts +2 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +2 -0
- package/node_modules/tiny-mcp-client/dist/index.js +1202 -1159
- package/package.json +1 -1
|
@@ -3395,1296 +3395,1336 @@ 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;
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
|
-
async delete() {
|
|
3814
|
-
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
3815
|
-
try {
|
|
3816
|
-
await this.fs.unlink(this.filePath);
|
|
3817
|
-
} catch (error) {
|
|
3818
|
-
if (!isNotFoundError(error)) {
|
|
3819
|
-
throw error;
|
|
3820
|
-
}
|
|
3821
|
-
}
|
|
3822
|
-
}
|
|
3823
|
-
async assertCredentialPathHasNoSymbolicLinks(targetPath) {
|
|
3824
|
-
const resolvedPath = path2.resolve(targetPath);
|
|
3825
|
-
const protectedPaths = getProtectedCredentialPaths(resolvedPath, this.symbolicLinkCheckStartPath);
|
|
3826
|
-
for (const currentPath of protectedPaths) {
|
|
3827
|
-
try {
|
|
3828
|
-
const stats = await this.fs.lstat(currentPath);
|
|
3829
|
-
if (stats.isSymbolicLink()) {
|
|
3830
|
-
throw new Error(`Refusing to use encrypted credential path through symbolic link: ${currentPath}`);
|
|
3831
|
-
}
|
|
3832
|
-
} catch (error) {
|
|
3833
|
-
if (isNotFoundError(error)) {
|
|
3834
|
-
return;
|
|
3835
|
-
}
|
|
3836
|
-
throw error;
|
|
3837
|
-
}
|
|
3674
|
+
if (expected.requireIssuer) {
|
|
3675
|
+
if (callback.iss === null || callback.iss.length === 0) {
|
|
3676
|
+
throw new Error("OAuth callback missing issuer");
|
|
3838
3677
|
}
|
|
3839
3678
|
}
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
const retryableKeyPromise = deriveEncryptionKey(this.getMachineIdentity, this.salt).catch((error) => {
|
|
3843
|
-
if (this.keyPromise === retryableKeyPromise) {
|
|
3844
|
-
this.keyPromise = null;
|
|
3845
|
-
}
|
|
3846
|
-
throw error;
|
|
3847
|
-
});
|
|
3848
|
-
this.keyPromise = retryableKeyPromise;
|
|
3849
|
-
}
|
|
3850
|
-
return this.keyPromise;
|
|
3679
|
+
if (callback.iss !== null && callback.iss.length > 0 && expected.issuer !== null && callback.iss !== expected.issuer) {
|
|
3680
|
+
throw new Error("OAuth callback issuer mismatch");
|
|
3851
3681
|
}
|
|
3852
|
-
};
|
|
3853
|
-
function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
|
|
3854
|
-
const [firstSegment] = defaultDirectory.split(/[\\/]+/).filter(Boolean);
|
|
3855
|
-
return path2.resolve(homeDirectory, firstSegment ?? ".");
|
|
3856
3682
|
}
|
|
3857
|
-
function
|
|
3858
|
-
|
|
3859
|
-
return getExplicitProtectedCredentialPaths(resolvedPath);
|
|
3860
|
-
}
|
|
3861
|
-
const resolvedStartPath = path2.resolve(symbolicLinkCheckStartPath);
|
|
3862
|
-
if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
|
|
3863
|
-
return [path2.dirname(resolvedPath), resolvedPath];
|
|
3864
|
-
}
|
|
3865
|
-
const protectedPaths = [resolvedStartPath];
|
|
3866
|
-
let currentPath = resolvedStartPath;
|
|
3867
|
-
for (const segment of path2.relative(resolvedStartPath, resolvedPath).split(path2.sep).filter(Boolean)) {
|
|
3868
|
-
currentPath = path2.join(currentPath, segment);
|
|
3869
|
-
protectedPaths.push(currentPath);
|
|
3870
|
-
}
|
|
3871
|
-
return protectedPaths;
|
|
3683
|
+
function createAuthorizationError(error, description) {
|
|
3684
|
+
return new Error(`OAuth authorization failed: ${error} \u2014 ${description}`);
|
|
3872
3685
|
}
|
|
3873
|
-
function
|
|
3874
|
-
|
|
3875
|
-
throw new Error("defaultDirectory must be a relative path inside the home directory");
|
|
3876
|
-
}
|
|
3877
|
-
for (const segment of splitPathSegments(defaultDirectory)) {
|
|
3878
|
-
if (segment === "..") {
|
|
3879
|
-
throw new Error("defaultDirectory must be a relative path inside the home directory");
|
|
3880
|
-
}
|
|
3881
|
-
}
|
|
3686
|
+
function escapeHtml(text) {
|
|
3687
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3882
3688
|
}
|
|
3883
|
-
function
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
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("");
|
|
3887
3701
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
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;
|
|
3890
3711
|
}
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
if (
|
|
3895
|
-
return
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
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));
|
|
3746
|
+
}
|
|
3747
|
+
return result2;
|
|
3903
3748
|
}
|
|
3904
|
-
|
|
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
|
+
}));
|
|
3905
3756
|
}
|
|
3906
|
-
|
|
3907
|
-
}
|
|
3908
|
-
function isPathInsideOrEqual(childPath, parentPath) {
|
|
3909
|
-
const relativePath = path2.relative(parentPath, childPath);
|
|
3910
|
-
return relativePath === "" || !relativePath.startsWith("..") && !path2.isAbsolute(relativePath);
|
|
3911
|
-
}
|
|
3912
|
-
async function removeIfPresent(fileSystem, filePath) {
|
|
3757
|
+
let result;
|
|
3913
3758
|
try {
|
|
3914
|
-
|
|
3915
|
-
} catch
|
|
3916
|
-
|
|
3917
|
-
throw error;
|
|
3918
|
-
}
|
|
3759
|
+
result = copy(value, 0);
|
|
3760
|
+
} catch {
|
|
3761
|
+
throw invalid();
|
|
3919
3762
|
}
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
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();
|
|
3789
|
+
}
|
|
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();
|
|
3796
|
+
}
|
|
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();
|
|
3934
3801
|
}
|
|
3935
|
-
const keyPromise = new Promise((resolve, reject) => {
|
|
3936
|
-
scrypt(secret, salt, ENCRYPTION_KEY_BYTES, (error, derivedKey) => {
|
|
3937
|
-
if (error) {
|
|
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);
|
|
3948
|
-
}
|
|
3949
|
-
throw error;
|
|
3950
|
-
});
|
|
3951
|
-
}
|
|
3952
|
-
function parseEncryptedDocument(raw) {
|
|
3953
3802
|
try {
|
|
3954
|
-
|
|
3955
|
-
if (!isRecord2(parsed)) {
|
|
3956
|
-
return null;
|
|
3957
|
-
}
|
|
3958
|
-
const version = getOwnEntry(parsed, "version");
|
|
3959
|
-
const iv = getOwnEntry(parsed, "iv");
|
|
3960
|
-
const authTag = getOwnEntry(parsed, "authTag");
|
|
3961
|
-
const ciphertext = getOwnEntry(parsed, "ciphertext");
|
|
3962
|
-
if (version !== ENCRYPTION_VERSION) {
|
|
3963
|
-
return null;
|
|
3964
|
-
}
|
|
3965
|
-
if (typeof iv !== "string" || typeof authTag !== "string" || typeof ciphertext !== "string") {
|
|
3966
|
-
return null;
|
|
3967
|
-
}
|
|
3968
|
-
return {
|
|
3969
|
-
version,
|
|
3970
|
-
iv,
|
|
3971
|
-
authTag,
|
|
3972
|
-
ciphertext
|
|
3973
|
-
};
|
|
3803
|
+
normalizeOAuthScope(Object.hasOwn(record2, "scope") && record2.scope !== null ? record2.scope : void 0);
|
|
3974
3804
|
} catch {
|
|
3975
|
-
|
|
3805
|
+
throw invalid();
|
|
3976
3806
|
}
|
|
3807
|
+
if (Buffer.byteLength(JSON.stringify(record2), "utf8") > 64 * 1024)
|
|
3808
|
+
throw invalid();
|
|
3809
|
+
return record2;
|
|
3977
3810
|
}
|
|
3978
|
-
function
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
}
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
import { homedir as homedir2 } from "node:os";
|
|
3996
|
-
import path3 from "node:path";
|
|
3997
|
-
var SECURITY_CLI = "security";
|
|
3998
|
-
var KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
|
|
3999
|
-
var KeychainStore = class {
|
|
4000
|
-
runCommand;
|
|
4001
|
-
service;
|
|
4002
|
-
account;
|
|
4003
|
-
lockFs;
|
|
4004
|
-
lockDirectory;
|
|
4005
|
-
constructor(input) {
|
|
4006
|
-
this.runCommand = input.runCommand ?? runSecurityCommand;
|
|
4007
|
-
this.service = input.service.trim();
|
|
4008
|
-
this.account = input.account.trim();
|
|
4009
|
-
this.lockFs = input.lock?.fs ?? nodeFs;
|
|
4010
|
-
this.lockDirectory = input.lock?.directory ?? path3.join(homedir2(), ".auth-store", "keychain-locks");
|
|
4011
|
-
if (this.service.length === 0) {
|
|
4012
|
-
throw new Error("Keychain service must not be empty");
|
|
4013
|
-
}
|
|
4014
|
-
if (this.account.length === 0) {
|
|
4015
|
-
throw new Error("Keychain account must not be empty");
|
|
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;
|
|
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");
|
|
4025
3828
|
}
|
|
4026
|
-
|
|
3829
|
+
client.requestedRedirectUri = requestedRedirectUri;
|
|
4027
3830
|
}
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
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;
|
|
4031
3843
|
}
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
3844
|
+
if (method !== void 0)
|
|
3845
|
+
client.tokenEndpointAuthMethod = method;
|
|
3846
|
+
return client;
|
|
3847
|
+
}
|
|
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;
|
|
3865
|
+
}
|
|
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;
|
|
3875
|
+
});
|
|
3876
|
+
}
|
|
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;
|
|
3891
|
+
}
|
|
3892
|
+
|
|
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;
|
|
4035
3919
|
}
|
|
4036
|
-
const
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
"
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
throw createSecurityCliFailure("store secret in macOS Keychain", result);
|
|
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;
|
|
3925
|
+
try {
|
|
3926
|
+
await fs2.writeFile(temporaryPath, JSON.stringify({ ticket }), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
3927
|
+
} catch (error) {
|
|
3928
|
+
if (hasOwnErrorCode(error, "EEXIST"))
|
|
3929
|
+
temporaryCreated = false;
|
|
3930
|
+
throw error;
|
|
4048
3931
|
}
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
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();
|
|
3955
|
+
});
|
|
4054
3956
|
}
|
|
4055
|
-
|
|
3957
|
+
options.signal?.throwIfAborted();
|
|
3958
|
+
outcome = { result: await operation() };
|
|
3959
|
+
} catch (error) {
|
|
3960
|
+
outcome = { error };
|
|
4056
3961
|
}
|
|
4057
|
-
|
|
3962
|
+
const cleanup = [];
|
|
3963
|
+
for (const target of [...temporaryCreated ? [temporaryPath] : [], ...claimed ? [claimPath] : []]) {
|
|
4058
3964
|
try {
|
|
4059
|
-
|
|
4060
|
-
return await this.runCommand(SECURITY_CLI, args);
|
|
4061
|
-
}
|
|
4062
|
-
return await this.runCommand(SECURITY_CLI, args, options);
|
|
3965
|
+
await fs2.unlink(target);
|
|
4063
3966
|
} catch (error) {
|
|
4064
|
-
|
|
4065
|
-
|
|
3967
|
+
if (!hasOwnErrorCode(error, "ENOENT"))
|
|
3968
|
+
cleanup.push(error);
|
|
4066
3969
|
}
|
|
4067
3970
|
}
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
});
|
|
4074
|
-
let stdout = "";
|
|
4075
|
-
let stderr = "";
|
|
4076
|
-
let stdinErrorMessage;
|
|
4077
|
-
const appendStderr = (message) => {
|
|
4078
|
-
stderr = stderr.length === 0 ? message : `${stderr}${stderr.endsWith("\n") ? "" : "\n"}${message}`;
|
|
4079
|
-
};
|
|
4080
|
-
const appendStdinError = () => {
|
|
4081
|
-
if (stdinErrorMessage === void 0) {
|
|
4082
|
-
return;
|
|
4083
|
-
}
|
|
4084
|
-
appendStderr(stdinErrorMessage);
|
|
4085
|
-
stdinErrorMessage = void 0;
|
|
4086
|
-
};
|
|
4087
|
-
child.stdout?.setEncoding("utf8");
|
|
4088
|
-
child.stdout?.on("data", (chunk) => {
|
|
4089
|
-
stdout += chunk.toString();
|
|
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);
|
|
4098
|
-
});
|
|
4099
|
-
child.stdin?.end(options.stdin);
|
|
4100
|
-
}
|
|
4101
|
-
child.on("error", (error) => {
|
|
4102
|
-
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
4103
|
-
appendStdinError();
|
|
4104
|
-
appendStderr(message);
|
|
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
|
-
});
|
|
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;
|
|
4120
3976
|
}
|
|
4121
|
-
function
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
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;
|
|
4127
3984
|
}
|
|
4128
|
-
return value;
|
|
4129
|
-
}
|
|
4130
|
-
function isKeychainEntryNotFound(result) {
|
|
4131
|
-
return getCommandExitCode(result) === KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE;
|
|
4132
3985
|
}
|
|
4133
|
-
function
|
|
4134
|
-
const
|
|
4135
|
-
const
|
|
4136
|
-
|
|
4137
|
-
|
|
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);
|
|
4138
3994
|
}
|
|
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
|
-
}
|
|
4149
|
-
function getOwnEntry2(record2, key2) {
|
|
4150
|
-
return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
|
|
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
|
-
try {
|
|
4232
|
-
parsed = JSON.parse(value);
|
|
4233
|
-
} catch {
|
|
4234
|
-
throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
|
|
4235
|
-
}
|
|
4236
|
-
if (isStoredOAuthSession(parsed)) {
|
|
4237
|
-
return parsed;
|
|
4238
|
-
}
|
|
4239
|
-
throw new Error("Stored OAuth session must match the expected shape");
|
|
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();
|
|
4087
|
+
throw error;
|
|
4248
4088
|
}
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
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");
|
|
4258
4103
|
return null;
|
|
4259
4104
|
}
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
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);
|
|
4265
4154
|
}
|
|
4266
|
-
|
|
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();
|
|
4155
|
+
throw error;
|
|
4277
4156
|
}
|
|
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
4157
|
}
|
|
4333
|
-
|
|
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;
|
|
4165
|
+
}
|
|
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) {
|
|
4172
|
+
try {
|
|
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;
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
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
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
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
|
+
}
|
|
4362
|
+
}
|
|
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);
|
|
4462
4372
|
}
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
used = true;
|
|
4471
|
-
try {
|
|
4472
|
-
return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
|
|
4473
|
-
} finally {
|
|
4474
|
-
clearTimeout(timer);
|
|
4475
|
-
}
|
|
4476
|
-
},
|
|
4477
|
-
close() {
|
|
4478
|
-
controller.abort(new Error("OAuth authorization session closed"));
|
|
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");
|
|
4479
4380
|
}
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
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);
|
|
4489
4393
|
}
|
|
4490
|
-
const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
|
|
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 };
|
|
4494
4394
|
}
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
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 });
|
|
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;
|
|
4399
|
+
}
|
|
4400
|
+
throw createSecurityCliFailure("delete secret from macOS Keychain", result);
|
|
4401
|
+
}
|
|
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)));
|
|
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);
|
|
4603
4445
|
}
|
|
4604
|
-
|
|
4605
|
-
|
|
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
|
+
});
|
|
4464
|
+
});
|
|
4465
|
+
}
|
|
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");
|
|
4606
4505
|
}
|
|
4607
|
-
|
|
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 };
|
|
4608
4523
|
}
|
|
4609
|
-
function
|
|
4610
|
-
const
|
|
4611
|
-
|
|
4612
|
-
|
|
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";
|
|
4613
4530
|
}
|
|
4531
|
+
if (backend === void 0 || backend === "file") {
|
|
4532
|
+
return "file";
|
|
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;
|
|
4666
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;
|
|
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
|
-
|
|
4670
|
-
|
|
4671
|
-
function escapeHtml(text) {
|
|
4672
|
-
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
4707
|
+
function isNonBlankOwnString(record2, key2) {
|
|
4708
|
+
const value = getOwnString(record2, key2);
|
|
4709
|
+
return value !== void 0 && value.trim().length > 0;
|
|
4673
4710
|
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4711
|
+
|
|
4712
|
+
// ../mcp-oauth/dist/client/default-oauth-client-provider.js
|
|
4713
|
+
import { isIP } from "node:net";
|
|
4714
|
+
|
|
4715
|
+
// ../mcp-oauth/dist/http-fetch.js
|
|
4716
|
+
async function fetchMcpResponse(fetchImplementation, input, init = {}) {
|
|
4717
|
+
const response = await fetchImplementation(input, { ...init, redirect: "error" });
|
|
4718
|
+
if (response.redirected || response.type === "opaqueredirect") {
|
|
4719
|
+
void response.body?.cancel().catch(() => void 0);
|
|
4720
|
+
throw new Error("MCP HTTP redirects are not allowed");
|
|
4721
|
+
}
|
|
4722
|
+
return response;
|
|
4686
4723
|
}
|
|
4687
4724
|
|
|
4725
|
+
// ../mcp-oauth/dist/client/default-oauth-client-provider.js
|
|
4726
|
+
import { URL as URL2 } from "node:url";
|
|
4727
|
+
|
|
4688
4728
|
// ../mcp-oauth/dist/client/pkce.js
|
|
4689
4729
|
import crypto3 from "node:crypto";
|
|
4690
4730
|
function generateCodeVerifier() {
|
|
@@ -5318,7 +5358,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5318
5358
|
let storedClient = await loadRegisteredClient(discovery.authorizationServer);
|
|
5319
5359
|
if (storedClient !== null) {
|
|
5320
5360
|
assertRegistrationIssuer(storedClient, discovery.authorizationServer);
|
|
5321
|
-
if (hasExpiredClientSecret(storedClient, now)) {
|
|
5361
|
+
if (hasExpiredClientSecret(storedClient, now) || !registrationMatchesRedirect(storedClient, redirectUri)) {
|
|
5322
5362
|
await clearRegisteredClient(discovery.authorizationServer);
|
|
5323
5363
|
storedClient = null;
|
|
5324
5364
|
}
|
|
@@ -5331,7 +5371,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5331
5371
|
};
|
|
5332
5372
|
}
|
|
5333
5373
|
if (registrationEndpoint === void 0) {
|
|
5334
|
-
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now)) {
|
|
5374
|
+
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
|
|
5335
5375
|
return {
|
|
5336
5376
|
kind: "dynamic",
|
|
5337
5377
|
fromStoredRegistration: true,
|
|
@@ -5340,7 +5380,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5340
5380
|
}
|
|
5341
5381
|
throw new Error("Authorization server metadata is missing registration_endpoint");
|
|
5342
5382
|
}
|
|
5343
|
-
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now)) {
|
|
5383
|
+
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
|
|
5344
5384
|
const isConfiguredStaticFallback = configuredClient !== null && existingSession.client.clientId === configuredClient.clientId && existingSession.client.clientSecret === configuredClient.clientSecret;
|
|
5345
5385
|
if (!isConfiguredStaticFallback) {
|
|
5346
5386
|
await saveRegisteredClient(discovery.authorizationServer, existingSession.client);
|
|
@@ -5379,11 +5419,14 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5379
5419
|
assertRegistrationIssuer(registeredClient, discovery.authorizationServer);
|
|
5380
5420
|
if (hasExpiredClientSecret(registeredClient, now))
|
|
5381
5421
|
throw new Error("OAuth client secret has expired in the registration response");
|
|
5382
|
-
|
|
5422
|
+
if (!registrationMatchesRedirect(registeredClient, redirectUri, true))
|
|
5423
|
+
throw new Error("OAuth registration response does not match the requested redirect URI");
|
|
5424
|
+
const clientWithRedirect = { ...registeredClient, requestedRedirectUri: redirectUri };
|
|
5425
|
+
await saveRegisteredClient(discovery.authorizationServer, clientWithRedirect);
|
|
5383
5426
|
return {
|
|
5384
5427
|
kind: "dynamic",
|
|
5385
5428
|
fromStoredRegistration: false,
|
|
5386
|
-
client:
|
|
5429
|
+
client: clientWithRedirect
|
|
5387
5430
|
};
|
|
5388
5431
|
}
|
|
5389
5432
|
async function loadSession(resource) {
|