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