toolcraft 0.0.314 → 0.0.316
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/composition.json +3 -3
- package/dist/composition.json +3 -3
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/README.md +40 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +1 -6
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +6 -18
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.d.ts +6 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.js +148 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +112 -49
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-auth-method.d.ts +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-auth-method.js +7 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +3 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +19 -9
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/types.d.ts +36 -5
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +1 -0
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/dist/index.d.ts +36 -5
- package/node_modules/tiny-mcp-client/dist/index.js +1312 -1095
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
|
@@ -3395,1184 +3395,1336 @@ var SubscriptionManager = class {
|
|
|
3395
3395
|
}
|
|
3396
3396
|
};
|
|
3397
3397
|
|
|
3398
|
-
// ../mcp-oauth/dist/client/
|
|
3399
|
-
import
|
|
3400
|
-
import path4 from "node:path";
|
|
3401
|
-
|
|
3402
|
-
// ../auth-store/dist/encrypted-file-store.js
|
|
3403
|
-
import { createCipheriv, createDecipheriv, randomBytes, randomUUID as randomUUID2, scrypt } from "node:crypto";
|
|
3404
|
-
import { promises as fs } from "node:fs";
|
|
3405
|
-
import { homedir, hostname, userInfo } from "node:os";
|
|
3406
|
-
import path2 from "node:path";
|
|
3398
|
+
// ../mcp-oauth/dist/client/loopback-authorization.js
|
|
3399
|
+
import http from "node:http";
|
|
3407
3400
|
|
|
3408
|
-
// ../
|
|
3409
|
-
|
|
3410
|
-
|
|
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");
|
|
3411
3411
|
}
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
async function withSecretStoreFileLock(fs2, lockDirectory, operation, options = {}) {
|
|
3417
|
-
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
3418
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2147483647)
|
|
3419
|
-
throw new Error("Invalid secret-store transaction lock timeout");
|
|
3420
|
-
options.signal?.throwIfAborted();
|
|
3421
|
-
const deadline = performance.now() + timeoutMs;
|
|
3422
|
-
const directory = path.resolve(lockDirectory);
|
|
3423
|
-
await assertLockDirectoryPath(fs2, directory);
|
|
3424
|
-
await fs2.mkdir(directory, { recursive: true, mode: 448 });
|
|
3425
|
-
await assertLockDirectoryPath(fs2, directory);
|
|
3426
|
-
const name = `${process.pid}-${randomUUID()}.claim`;
|
|
3427
|
-
const claimPath = path.join(directory, name);
|
|
3428
|
-
const temporaryPath = `${claimPath}.tmp`;
|
|
3429
|
-
let claimed = true;
|
|
3430
|
-
let temporaryCreated = false;
|
|
3431
|
-
let outcome;
|
|
3412
|
+
function parseAuthorizationState(value) {
|
|
3413
|
+
if (value === null || value.length === 0) {
|
|
3414
|
+
return null;
|
|
3415
|
+
}
|
|
3432
3416
|
try {
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
claimed = false;
|
|
3438
|
-
throw error;
|
|
3439
|
-
}
|
|
3440
|
-
const existing = await readClaims(fs2, directory, name);
|
|
3441
|
-
const ticket = existing.reduce((max, claim) => Math.max(max, claim.ticket ?? 0), 0) + 1;
|
|
3442
|
-
if (!Number.isSafeInteger(ticket))
|
|
3443
|
-
throw new Error("Secret-store transaction lock ticket overflow");
|
|
3444
|
-
temporaryCreated = true;
|
|
3445
|
-
try {
|
|
3446
|
-
await fs2.writeFile(temporaryPath, JSON.stringify({ ticket }), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
3447
|
-
} catch (error) {
|
|
3448
|
-
if (hasOwnErrorCode(error, "EEXIST"))
|
|
3449
|
-
temporaryCreated = false;
|
|
3450
|
-
throw error;
|
|
3451
|
-
}
|
|
3452
|
-
await fs2.rename(temporaryPath, claimPath);
|
|
3453
|
-
temporaryCreated = false;
|
|
3454
|
-
for (; ; ) {
|
|
3455
|
-
options.signal?.throwIfAborted();
|
|
3456
|
-
const peers = await readClaims(fs2, directory, name);
|
|
3457
|
-
if (!peers.some((peer) => peer.ticket === null || peer.ticket < ticket || peer.ticket === ticket && peer.name < name))
|
|
3458
|
-
break;
|
|
3459
|
-
const remaining = deadline - performance.now();
|
|
3460
|
-
if (remaining <= 0)
|
|
3461
|
-
throw new Error("Timed out waiting for secret-store transaction lock");
|
|
3462
|
-
await new Promise((resolve, reject) => {
|
|
3463
|
-
const abort = () => {
|
|
3464
|
-
clearTimeout(timer);
|
|
3465
|
-
options.signal?.removeEventListener("abort", abort);
|
|
3466
|
-
reject(options.signal?.reason);
|
|
3467
|
-
};
|
|
3468
|
-
const timer = setTimeout(() => {
|
|
3469
|
-
options.signal?.removeEventListener("abort", abort);
|
|
3470
|
-
resolve();
|
|
3471
|
-
}, Math.min(10, remaining));
|
|
3472
|
-
options.signal?.addEventListener("abort", abort, { once: true });
|
|
3473
|
-
if (options.signal?.aborted)
|
|
3474
|
-
abort();
|
|
3475
|
-
});
|
|
3417
|
+
const decoded = Buffer.from(value, "base64url").toString("utf8");
|
|
3418
|
+
const parsed = JSON.parse(decoded);
|
|
3419
|
+
if (!isObjectRecord(parsed)) {
|
|
3420
|
+
return null;
|
|
3476
3421
|
}
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
for (const target of [...temporaryCreated ? [temporaryPath] : [], ...claimed ? [claimPath] : []]) {
|
|
3484
|
-
try {
|
|
3485
|
-
await fs2.unlink(target);
|
|
3486
|
-
} catch (error) {
|
|
3487
|
-
if (!hasOwnErrorCode(error, "ENOENT"))
|
|
3488
|
-
cleanup.push(error);
|
|
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;
|
|
3489
3428
|
}
|
|
3429
|
+
return {
|
|
3430
|
+
issuer,
|
|
3431
|
+
requireIssuer
|
|
3432
|
+
};
|
|
3433
|
+
} catch {
|
|
3434
|
+
return null;
|
|
3490
3435
|
}
|
|
3491
|
-
if (cleanup.length)
|
|
3492
|
-
throw new AggregateError([..."error" in outcome ? [outcome.error] : [], ...cleanup], "Secret-store transaction lock cleanup failed");
|
|
3493
|
-
if ("error" in outcome)
|
|
3494
|
-
throw outcome.error;
|
|
3495
|
-
return outcome.result;
|
|
3496
3436
|
}
|
|
3497
|
-
|
|
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;
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
// ../mcp-oauth/dist/client/loopback-authorization.js
|
|
3445
|
+
async function createLoopbackAuthorizationSession(options = {}) {
|
|
3446
|
+
options.signal?.throwIfAborted();
|
|
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;
|
|
3498
3472
|
try {
|
|
3499
|
-
|
|
3500
|
-
throw new Error("Refusing secret-store transaction lock through symbolic link");
|
|
3473
|
+
port = await startServer(server, target.port, target.host, controller.signal);
|
|
3501
3474
|
} catch (error) {
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
}
|
|
3505
|
-
}
|
|
3506
|
-
async function assertLockDirectoryPath(fs2, directory) {
|
|
3507
|
-
const root = path.parse(directory).root;
|
|
3508
|
-
const segments = directory.slice(root.length).split(path.sep).filter(Boolean);
|
|
3509
|
-
let current = root;
|
|
3510
|
-
for (const [index, segment] of segments.entries()) {
|
|
3511
|
-
current = path.join(current, segment);
|
|
3512
|
-
if (index > 0 || segments.length === 1)
|
|
3513
|
-
await assertNoSymbolicLink(fs2, current);
|
|
3475
|
+
controller.abort(error);
|
|
3476
|
+
throw error;
|
|
3514
3477
|
}
|
|
3515
|
-
}
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
if (!Number.isSafeInteger(pid) || pid < 1 || String(pid) !== pidText)
|
|
3524
|
-
throw new Error("Malformed secret-store transaction lock owner");
|
|
3525
|
-
const target = path.join(directory, name);
|
|
3526
|
-
await assertNoSymbolicLink(fs2, target);
|
|
3527
|
-
let alive = true;
|
|
3528
|
-
try {
|
|
3529
|
-
process.kill(pid, 0);
|
|
3530
|
-
} catch (error) {
|
|
3531
|
-
alive = !hasOwnErrorCode(error, "ESRCH");
|
|
3532
|
-
}
|
|
3533
|
-
if (!alive) {
|
|
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;
|
|
3534
3486
|
try {
|
|
3535
|
-
await
|
|
3536
|
-
}
|
|
3537
|
-
|
|
3538
|
-
throw error;
|
|
3487
|
+
return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
|
|
3488
|
+
} finally {
|
|
3489
|
+
clearTimeout(timer);
|
|
3539
3490
|
}
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
let raw;
|
|
3544
|
-
try {
|
|
3545
|
-
raw = await fs2.readFile(target, "utf8");
|
|
3546
|
-
} catch (error) {
|
|
3547
|
-
if (hasOwnErrorCode(error, "ENOENT"))
|
|
3548
|
-
continue;
|
|
3549
|
-
throw error;
|
|
3491
|
+
},
|
|
3492
|
+
close() {
|
|
3493
|
+
controller.abort(new Error("OAuth authorization session closed"));
|
|
3550
3494
|
}
|
|
3495
|
+
};
|
|
3496
|
+
}
|
|
3497
|
+
function loopbackTarget(options) {
|
|
3498
|
+
if (options.redirectUri !== void 0) {
|
|
3499
|
+
let url;
|
|
3551
3500
|
try {
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
} catch {
|
|
3501
|
+
url = new URL(options.redirectUri);
|
|
3502
|
+
} catch (cause) {
|
|
3503
|
+
throw new Error("Invalid OAuth loopback redirect URI", { cause });
|
|
3556
3504
|
}
|
|
3557
|
-
|
|
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 };
|
|
3558
3509
|
}
|
|
3559
|
-
|
|
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 };
|
|
3560
3515
|
}
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
var ENCRYPTION_IV_BYTES = 12;
|
|
3568
|
-
var ENCRYPTION_AUTH_TAG_BYTES = 16;
|
|
3569
|
-
var ENCRYPTION_FILE_MODE = 384;
|
|
3570
|
-
var EncryptedFileStore = class {
|
|
3571
|
-
fs;
|
|
3572
|
-
filePath;
|
|
3573
|
-
symbolicLinkCheckStartPath;
|
|
3574
|
-
salt;
|
|
3575
|
-
getMachineIdentity;
|
|
3576
|
-
getRandomBytes;
|
|
3577
|
-
keyPromise = null;
|
|
3578
|
-
throwOnInvalidDocument;
|
|
3579
|
-
constructor(input) {
|
|
3580
|
-
this.fs = input.fs ?? fs;
|
|
3581
|
-
this.salt = input.salt;
|
|
3582
|
-
if (input.filePath === void 0) {
|
|
3583
|
-
const homeDirectory = (input.getHomeDirectory ?? homedir)();
|
|
3584
|
-
const defaultDirectory = input.defaultDirectory ?? ".auth-store";
|
|
3585
|
-
const defaultFileName = input.defaultFileName ?? "credentials.enc";
|
|
3586
|
-
assertSafeDefaultDirectory(defaultDirectory);
|
|
3587
|
-
assertSafeDefaultFileName(defaultFileName);
|
|
3588
|
-
this.filePath = path2.join(homeDirectory, defaultDirectory, defaultFileName);
|
|
3589
|
-
this.symbolicLinkCheckStartPath = resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory);
|
|
3590
|
-
} else {
|
|
3591
|
-
this.filePath = input.filePath;
|
|
3592
|
-
this.symbolicLinkCheckStartPath = null;
|
|
3593
|
-
}
|
|
3594
|
-
this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
|
|
3595
|
-
this.getRandomBytes = input.getRandomBytes ?? randomBytes;
|
|
3596
|
-
this.throwOnInvalidDocument = input.throwOnInvalidDocument ?? false;
|
|
3597
|
-
}
|
|
3598
|
-
async get() {
|
|
3599
|
-
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
3600
|
-
let rawDocument;
|
|
3601
|
-
try {
|
|
3602
|
-
rawDocument = await this.fs.readFile(this.filePath, "utf8");
|
|
3603
|
-
} catch (error) {
|
|
3604
|
-
if (isNotFoundError(error)) {
|
|
3605
|
-
return null;
|
|
3606
|
-
}
|
|
3607
|
-
throw error;
|
|
3608
|
-
}
|
|
3609
|
-
const document = parseEncryptedDocument(rawDocument);
|
|
3610
|
-
if (!document) {
|
|
3611
|
-
if (this.throwOnInvalidDocument)
|
|
3612
|
-
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
3613
|
-
return null;
|
|
3614
|
-
}
|
|
3615
|
-
const key2 = await this.getEncryptionKey();
|
|
3616
|
-
try {
|
|
3617
|
-
const iv = Buffer.from(document.iv, "base64");
|
|
3618
|
-
const authTag = Buffer.from(document.authTag, "base64");
|
|
3619
|
-
const ciphertext = Buffer.from(document.ciphertext, "base64");
|
|
3620
|
-
if (iv.byteLength !== ENCRYPTION_IV_BYTES || authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
|
|
3621
|
-
if (this.throwOnInvalidDocument)
|
|
3622
|
-
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
3623
|
-
return null;
|
|
3624
|
-
}
|
|
3625
|
-
const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key2, iv);
|
|
3626
|
-
decipher.setAuthTag(authTag);
|
|
3627
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
3628
|
-
return plaintext.toString("utf8");
|
|
3629
|
-
} catch {
|
|
3630
|
-
if (this.throwOnInvalidDocument)
|
|
3631
|
-
throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
|
|
3632
|
-
return null;
|
|
3633
|
-
}
|
|
3634
|
-
}
|
|
3635
|
-
async withLock(operation, options = {}) {
|
|
3636
|
-
await this.assertCredentialPathHasNoSymbolicLinks(`${this.filePath}.lock`);
|
|
3637
|
-
if (this.fs.readdir === void 0)
|
|
3638
|
-
throw new Error("Secret-store transaction locks require filesystem readdir support");
|
|
3639
|
-
return withSecretStoreFileLock(this.fs, `${this.filePath}.lock`, operation, options);
|
|
3640
|
-
}
|
|
3641
|
-
async set(value) {
|
|
3642
|
-
await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
|
|
3643
|
-
const key2 = await this.getEncryptionKey();
|
|
3644
|
-
const iv = this.getRandomBytes(ENCRYPTION_IV_BYTES);
|
|
3645
|
-
const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key2, iv);
|
|
3646
|
-
const ciphertext = Buffer.concat([
|
|
3647
|
-
cipher.update(value, "utf8"),
|
|
3648
|
-
cipher.final()
|
|
3649
|
-
]);
|
|
3650
|
-
const authTag = cipher.getAuthTag();
|
|
3651
|
-
const document = {
|
|
3652
|
-
version: ENCRYPTION_VERSION,
|
|
3653
|
-
iv: iv.toString("base64"),
|
|
3654
|
-
authTag: authTag.toString("base64"),
|
|
3655
|
-
ciphertext: ciphertext.toString("base64")
|
|
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);
|
|
3656
3522
|
};
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
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 });
|
|
3661
3533
|
try {
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
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);
|
|
3667
3547
|
});
|
|
3668
|
-
temporaryCreated = true;
|
|
3669
|
-
await this.fs.chmod(temporaryPath, ENCRYPTION_FILE_MODE);
|
|
3670
|
-
await this.fs.rename(temporaryPath, this.filePath);
|
|
3671
3548
|
} catch (error) {
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
}
|
|
3675
|
-
throw error;
|
|
3549
|
+
cleanup();
|
|
3550
|
+
reject(error);
|
|
3676
3551
|
}
|
|
3677
|
-
}
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
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;
|
|
3685
3576
|
}
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
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
|
+
};
|
|
3692
3589
|
try {
|
|
3693
|
-
|
|
3694
|
-
if (
|
|
3695
|
-
throw
|
|
3696
|
-
|
|
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));
|
|
3697
3597
|
} catch (error) {
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
throw error;
|
|
3598
|
+
res.writeHead(400);
|
|
3599
|
+
res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
|
|
3600
|
+
settle(() => reject(error));
|
|
3702
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)));
|
|
3703
3618
|
}
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
if (!this.keyPromise) {
|
|
3707
|
-
const retryableKeyPromise = deriveEncryptionKey(this.getMachineIdentity, this.salt).catch((error) => {
|
|
3708
|
-
if (this.keyPromise === retryableKeyPromise) {
|
|
3709
|
-
this.keyPromise = null;
|
|
3710
|
-
}
|
|
3711
|
-
throw error;
|
|
3712
|
-
});
|
|
3713
|
-
this.keyPromise = retryableKeyPromise;
|
|
3619
|
+
if (options.openBrowser !== void 0) {
|
|
3620
|
+
void Promise.resolve().then(() => settled ? void 0 : options.openBrowser(authorizationUrl)).catch((error) => settle(() => reject(error)));
|
|
3714
3621
|
}
|
|
3715
|
-
|
|
3716
|
-
}
|
|
3717
|
-
};
|
|
3718
|
-
function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
|
|
3719
|
-
const [firstSegment] = defaultDirectory.split(/[\\/]+/).filter(Boolean);
|
|
3720
|
-
return path2.resolve(homeDirectory, firstSegment ?? ".");
|
|
3721
|
-
}
|
|
3722
|
-
function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
|
|
3723
|
-
if (symbolicLinkCheckStartPath === null) {
|
|
3724
|
-
return getExplicitProtectedCredentialPaths(resolvedPath);
|
|
3725
|
-
}
|
|
3726
|
-
const resolvedStartPath = path2.resolve(symbolicLinkCheckStartPath);
|
|
3727
|
-
if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
|
|
3728
|
-
return [path2.dirname(resolvedPath), resolvedPath];
|
|
3729
|
-
}
|
|
3730
|
-
const protectedPaths = [resolvedStartPath];
|
|
3731
|
-
let currentPath = resolvedStartPath;
|
|
3732
|
-
for (const segment of path2.relative(resolvedStartPath, resolvedPath).split(path2.sep).filter(Boolean)) {
|
|
3733
|
-
currentPath = path2.join(currentPath, segment);
|
|
3734
|
-
protectedPaths.push(currentPath);
|
|
3735
|
-
}
|
|
3736
|
-
return protectedPaths;
|
|
3622
|
+
});
|
|
3737
3623
|
}
|
|
3738
|
-
function
|
|
3739
|
-
|
|
3740
|
-
|
|
3624
|
+
function extractCallbackParametersFromInput(input) {
|
|
3625
|
+
const trimmed = input.replaceAll("\r", "").replaceAll("\n", "").trim();
|
|
3626
|
+
if (trimmed.length === 0) {
|
|
3627
|
+
return null;
|
|
3741
3628
|
}
|
|
3742
|
-
for (const segment of splitPathSegments(defaultDirectory)) {
|
|
3743
|
-
if (segment === "..") {
|
|
3744
|
-
throw new Error("defaultDirectory must be a relative path inside the home directory");
|
|
3745
|
-
}
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
function assertSafeDefaultFileName(defaultFileName) {
|
|
3749
|
-
if (defaultFileName.trim().length === 0 || defaultFileName === "." || defaultFileName === ".." || splitPathSegments(defaultFileName).length !== 1) {
|
|
3750
|
-
throw new Error("defaultFileName must be a file name without path separators");
|
|
3751
|
-
}
|
|
3752
|
-
}
|
|
3753
|
-
function splitPathSegments(value) {
|
|
3754
|
-
return value.split("/").flatMap((segment) => segment.split("\\")).filter((segment) => segment.length > 0);
|
|
3755
|
-
}
|
|
3756
|
-
function getExplicitProtectedCredentialPaths(resolvedPath) {
|
|
3757
|
-
const parsed = path2.parse(resolvedPath);
|
|
3758
|
-
const segments = resolvedPath.slice(parsed.root.length).split(path2.sep).filter((segment) => segment.length > 0);
|
|
3759
|
-
if (segments.length <= 1) {
|
|
3760
|
-
return [resolvedPath];
|
|
3761
|
-
}
|
|
3762
|
-
const protectedPaths = [];
|
|
3763
|
-
let currentPath = parsed.root;
|
|
3764
|
-
for (const [index, segment] of segments.entries()) {
|
|
3765
|
-
currentPath = path2.join(currentPath, segment);
|
|
3766
|
-
if (index === 0) {
|
|
3767
|
-
continue;
|
|
3768
|
-
}
|
|
3769
|
-
protectedPaths.push(currentPath);
|
|
3770
|
-
}
|
|
3771
|
-
return protectedPaths;
|
|
3772
|
-
}
|
|
3773
|
-
function isPathInsideOrEqual(childPath, parentPath) {
|
|
3774
|
-
const relativePath = path2.relative(parentPath, childPath);
|
|
3775
|
-
return relativePath === "" || !relativePath.startsWith("..") && !path2.isAbsolute(relativePath);
|
|
3776
|
-
}
|
|
3777
|
-
async function removeIfPresent(fileSystem, filePath) {
|
|
3778
3629
|
try {
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
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
|
+
};
|
|
3784
3646
|
}
|
|
3785
3647
|
}
|
|
3786
|
-
function
|
|
3648
|
+
function readExpectedAuthorizationCallback(authorizationUrl) {
|
|
3649
|
+
const url = new URL(authorizationUrl);
|
|
3650
|
+
const state = url.searchParams.get("state");
|
|
3651
|
+
const parsedState = parseAuthorizationState(state);
|
|
3787
3652
|
return {
|
|
3788
|
-
|
|
3789
|
-
|
|
3653
|
+
state,
|
|
3654
|
+
issuer: parsedState?.issuer ?? null,
|
|
3655
|
+
requireIssuer: parsedState?.requireIssuer ?? false
|
|
3790
3656
|
};
|
|
3791
3657
|
}
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
const cached = derivedKeyCache.get(cacheKey);
|
|
3797
|
-
if (cached) {
|
|
3798
|
-
return cached;
|
|
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");
|
|
3799
3662
|
}
|
|
3800
|
-
|
|
3801
|
-
scrypt(secret, salt, ENCRYPTION_KEY_BYTES, (error, derivedKey) => {
|
|
3802
|
-
if (error) {
|
|
3803
|
-
reject(error);
|
|
3804
|
-
return;
|
|
3805
|
-
}
|
|
3806
|
-
resolve(Buffer.from(derivedKey));
|
|
3807
|
-
});
|
|
3808
|
-
});
|
|
3809
|
-
derivedKeyCache.set(cacheKey, keyPromise);
|
|
3810
|
-
return keyPromise.catch((error) => {
|
|
3811
|
-
if (derivedKeyCache.get(cacheKey) === keyPromise) {
|
|
3812
|
-
derivedKeyCache.delete(cacheKey);
|
|
3813
|
-
}
|
|
3814
|
-
throw error;
|
|
3815
|
-
});
|
|
3663
|
+
return callback.code;
|
|
3816
3664
|
}
|
|
3817
|
-
function
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
return null;
|
|
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");
|
|
3822
3669
|
}
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
const authTag = getOwnEntry(parsed, "authTag");
|
|
3826
|
-
const ciphertext = getOwnEntry(parsed, "ciphertext");
|
|
3827
|
-
if (version !== ENCRYPTION_VERSION) {
|
|
3828
|
-
return null;
|
|
3670
|
+
if (callback.state !== expected.state) {
|
|
3671
|
+
throw new Error("OAuth callback state mismatch");
|
|
3829
3672
|
}
|
|
3830
|
-
|
|
3831
|
-
|
|
3673
|
+
}
|
|
3674
|
+
if (expected.requireIssuer) {
|
|
3675
|
+
if (callback.iss === null || callback.iss.length === 0) {
|
|
3676
|
+
throw new Error("OAuth callback missing issuer");
|
|
3832
3677
|
}
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
authTag,
|
|
3837
|
-
ciphertext
|
|
3838
|
-
};
|
|
3839
|
-
} catch {
|
|
3840
|
-
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");
|
|
3841
3681
|
}
|
|
3842
3682
|
}
|
|
3843
|
-
function
|
|
3844
|
-
return
|
|
3683
|
+
function createAuthorizationError(error, description) {
|
|
3684
|
+
return new Error(`OAuth authorization failed: ${error} \u2014 ${description}`);
|
|
3845
3685
|
}
|
|
3846
|
-
function
|
|
3847
|
-
return
|
|
3686
|
+
function escapeHtml(text) {
|
|
3687
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3848
3688
|
}
|
|
3849
|
-
function
|
|
3850
|
-
|
|
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("");
|
|
3851
3701
|
}
|
|
3852
|
-
|
|
3853
|
-
|
|
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;
|
|
3854
3711
|
}
|
|
3855
3712
|
|
|
3856
|
-
// ../
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
if (
|
|
3877
|
-
throw
|
|
3878
|
-
|
|
3879
|
-
if (
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
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;
|
|
3748
|
+
}
|
|
3749
|
+
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
|
|
3750
|
+
throw invalid();
|
|
3751
|
+
return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key2, descriptor]) => {
|
|
3752
|
+
if (!Object.hasOwn(descriptor, "value"))
|
|
3753
|
+
throw invalid();
|
|
3754
|
+
return [key2, copy(descriptor.value, depth + 1)];
|
|
3755
|
+
}));
|
|
3756
|
+
}
|
|
3757
|
+
let result;
|
|
3758
|
+
try {
|
|
3759
|
+
result = copy(value, 0);
|
|
3760
|
+
} catch {
|
|
3761
|
+
throw invalid();
|
|
3762
|
+
}
|
|
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();
|
|
3892
3789
|
}
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
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();
|
|
3896
3796
|
}
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
const result = await this.executeSecurityCommand([
|
|
3902
|
-
"add-generic-password",
|
|
3903
|
-
"-s",
|
|
3904
|
-
this.service,
|
|
3905
|
-
"-a",
|
|
3906
|
-
this.account,
|
|
3907
|
-
"-U",
|
|
3908
|
-
"-w",
|
|
3909
|
-
value
|
|
3910
|
-
], "store secret in macOS Keychain");
|
|
3911
|
-
if (getCommandExitCode(result) !== 0) {
|
|
3912
|
-
throw createSecurityCliFailure("store secret in macOS Keychain", result);
|
|
3913
|
-
}
|
|
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();
|
|
3914
3801
|
}
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
}
|
|
3920
|
-
throw createSecurityCliFailure("delete secret from macOS Keychain", result);
|
|
3802
|
+
try {
|
|
3803
|
+
normalizeOAuthScope(Object.hasOwn(record2, "scope") && record2.scope !== null ? record2.scope : void 0);
|
|
3804
|
+
} catch {
|
|
3805
|
+
throw invalid();
|
|
3921
3806
|
}
|
|
3922
|
-
|
|
3807
|
+
if (Buffer.byteLength(JSON.stringify(record2), "utf8") > 64 * 1024)
|
|
3808
|
+
throw invalid();
|
|
3809
|
+
return record2;
|
|
3810
|
+
}
|
|
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) {
|
|
3923
3822
|
try {
|
|
3924
|
-
if (
|
|
3925
|
-
|
|
3926
|
-
}
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
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;
|
|
3830
|
+
}
|
|
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;
|
|
3847
|
+
}
|
|
3848
|
+
function registrationMatchesRedirect(client, requestedUri, fresh = false) {
|
|
3849
|
+
if (client.requestedRedirectUri !== void 0)
|
|
3850
|
+
return client.requestedRedirectUri === requestedUri;
|
|
3851
|
+
const redirects = client.registration?.redirect_uris;
|
|
3852
|
+
if (redirects === void 0 || redirects === null || redirects.length === 0)
|
|
3853
|
+
return true;
|
|
3854
|
+
return redirects.some((returnedUri) => {
|
|
3855
|
+
if (returnedUri === requestedUri)
|
|
3856
|
+
return true;
|
|
3857
|
+
if (!fresh)
|
|
3858
|
+
return false;
|
|
3859
|
+
let returned, requested;
|
|
3860
|
+
try {
|
|
3861
|
+
returned = new URL(returnedUri);
|
|
3862
|
+
requested = new URL(requestedUri);
|
|
3863
|
+
} catch {
|
|
3864
|
+
return false;
|
|
3931
3865
|
}
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
let stdinErrorMessage;
|
|
3942
|
-
const appendStderr = (message) => {
|
|
3943
|
-
stderr = stderr.length === 0 ? message : `${stderr}${stderr.endsWith("\n") ? "" : "\n"}${message}`;
|
|
3944
|
-
};
|
|
3945
|
-
const appendStdinError = () => {
|
|
3946
|
-
if (stdinErrorMessage === void 0) {
|
|
3947
|
-
return;
|
|
3948
|
-
}
|
|
3949
|
-
appendStderr(stdinErrorMessage);
|
|
3950
|
-
stdinErrorMessage = void 0;
|
|
3951
|
-
};
|
|
3952
|
-
child.stdout?.setEncoding("utf8");
|
|
3953
|
-
child.stdout?.on("data", (chunk) => {
|
|
3954
|
-
stdout += chunk.toString();
|
|
3955
|
-
});
|
|
3956
|
-
child.stderr?.setEncoding("utf8");
|
|
3957
|
-
child.stderr?.on("data", (chunk) => {
|
|
3958
|
-
stderr += chunk.toString();
|
|
3959
|
-
});
|
|
3960
|
-
if (options?.stdin !== void 0) {
|
|
3961
|
-
child.stdin?.once("error", (error) => {
|
|
3962
|
-
stdinErrorMessage = error instanceof Error ? error.message : String(error);
|
|
3963
|
-
});
|
|
3964
|
-
child.stdin?.end(options.stdin);
|
|
3965
|
-
}
|
|
3966
|
-
child.on("error", (error) => {
|
|
3967
|
-
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
3968
|
-
appendStdinError();
|
|
3969
|
-
appendStderr(message);
|
|
3970
|
-
resolve({
|
|
3971
|
-
stdout,
|
|
3972
|
-
stderr,
|
|
3973
|
-
exitCode: 127
|
|
3974
|
-
});
|
|
3975
|
-
});
|
|
3976
|
-
child.on("close", (code) => {
|
|
3977
|
-
appendStdinError();
|
|
3978
|
-
resolve({
|
|
3979
|
-
stdout,
|
|
3980
|
-
stderr,
|
|
3981
|
-
exitCode: code ?? 1
|
|
3982
|
-
});
|
|
3983
|
-
});
|
|
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;
|
|
3984
3875
|
});
|
|
3985
3876
|
}
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
function
|
|
3999
|
-
|
|
4000
|
-
const details = getCommandOutput(result, "stderr").trim() || getCommandOutput(result, "stdout").trim();
|
|
4001
|
-
if (details) {
|
|
4002
|
-
return new Error(`Failed to ${operation}: security exited with code ${exitCode}: ${details}`);
|
|
4003
|
-
}
|
|
4004
|
-
return new Error(`Failed to ${operation}: security exited with code ${exitCode}`);
|
|
4005
|
-
}
|
|
4006
|
-
function getCommandExitCode(result) {
|
|
4007
|
-
const value = getOwnEntry2(result, "exitCode");
|
|
4008
|
-
return typeof value === "number" && Number.isInteger(value) ? value : 1;
|
|
4009
|
-
}
|
|
4010
|
-
function getCommandOutput(result, key2) {
|
|
4011
|
-
const value = getOwnEntry2(result, key2);
|
|
4012
|
-
return typeof value === "string" ? value : "";
|
|
4013
|
-
}
|
|
4014
|
-
function getOwnEntry2(record2, key2) {
|
|
4015
|
-
return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
|
|
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;
|
|
4016
3891
|
}
|
|
4017
3892
|
|
|
4018
|
-
// ../auth-store/dist/
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
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;
|
|
4025
3919
|
}
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
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;
|
|
4031
3931
|
}
|
|
4032
|
-
|
|
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 };
|
|
4033
3961
|
}
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
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
|
+
}
|
|
4040
3970
|
}
|
|
4041
|
-
|
|
4042
|
-
|
|
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;
|
|
4043
3976
|
}
|
|
4044
|
-
function
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
if (backend === void 0 || backend === "file") {
|
|
4052
|
-
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;
|
|
4053
3984
|
}
|
|
4054
|
-
throw new Error(`Unsupported auth store backend: ${backend}`);
|
|
4055
|
-
}
|
|
4056
|
-
function getOwnEnvValue(env, key2) {
|
|
4057
|
-
return env !== void 0 && Object.prototype.hasOwnProperty.call(env, key2) ? env[key2] : void 0;
|
|
4058
3985
|
}
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
let
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
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);
|
|
4067
3994
|
}
|
|
4068
|
-
url.hash = "";
|
|
4069
|
-
return url.toString();
|
|
4070
3995
|
}
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
}
|
|
4095
|
-
let parsed;
|
|
4096
|
-
try {
|
|
4097
|
-
parsed = JSON.parse(value);
|
|
4098
|
-
} catch {
|
|
4099
|
-
throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
|
|
4100
|
-
}
|
|
4101
|
-
if (isStoredOAuthSession(parsed)) {
|
|
4102
|
-
return parsed;
|
|
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;
|
|
4103
4019
|
}
|
|
4104
|
-
|
|
4105
|
-
},
|
|
4106
|
-
async save(resource, session) {
|
|
4107
|
-
const store = createResourceSecretStore(resource, options, namespace);
|
|
4108
|
-
await store.set(JSON.stringify(session));
|
|
4109
|
-
},
|
|
4110
|
-
async clear(resource) {
|
|
4111
|
-
const store = createResourceSecretStore(resource, options, namespace);
|
|
4112
|
-
await store.delete();
|
|
4020
|
+
continue;
|
|
4113
4021
|
}
|
|
4114
|
-
|
|
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 });
|
|
4038
|
+
}
|
|
4039
|
+
return claims;
|
|
4115
4040
|
}
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4041
|
+
|
|
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)) {
|
|
4123
4085
|
return null;
|
|
4124
4086
|
}
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
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;
|
|
4130
4104
|
}
|
|
4131
|
-
const
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
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);
|
|
4154
|
+
}
|
|
4155
|
+
throw error;
|
|
4156
|
+
}
|
|
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;
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
async assertCredentialPathHasNoSymbolicLinks(targetPath) {
|
|
4169
|
+
const resolvedPath = path2.resolve(targetPath);
|
|
4170
|
+
const protectedPaths = getProtectedCredentialPaths(resolvedPath, this.symbolicLinkCheckStartPath);
|
|
4171
|
+
for (const currentPath of protectedPaths) {
|
|
4172
|
+
try {
|
|
4173
|
+
const stats = await this.fs.lstat(currentPath);
|
|
4174
|
+
if (stats.isSymbolicLink()) {
|
|
4175
|
+
throw new Error(`Refusing to use encrypted credential path through symbolic link: ${currentPath}`);
|
|
4136
4176
|
}
|
|
4137
|
-
|
|
4177
|
+
} catch (error) {
|
|
4178
|
+
if (isNotFoundError(error)) {
|
|
4179
|
+
return;
|
|
4180
|
+
}
|
|
4181
|
+
throw error;
|
|
4138
4182
|
}
|
|
4139
|
-
throw new Error("Stored OAuth client must be a JSON object with clientId");
|
|
4140
|
-
},
|
|
4141
|
-
async save(issuer, client) {
|
|
4142
|
-
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
4143
|
-
await store.set(JSON.stringify(client));
|
|
4144
|
-
},
|
|
4145
|
-
async clear(issuer) {
|
|
4146
|
-
const store = createIssuerSecretStore(issuer, options, namespace);
|
|
4147
|
-
await store.delete();
|
|
4148
4183
|
}
|
|
4149
|
-
};
|
|
4150
|
-
}
|
|
4151
|
-
function createNamedSecretStore(key2, options, defaults, namespace) {
|
|
4152
|
-
const hash = crypto.createHash("sha256").update(namespace === void 0 ? key2 : JSON.stringify([namespace, key2])).digest("hex");
|
|
4153
|
-
const configuredFilePath = options.fileStore?.filePath;
|
|
4154
|
-
const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
|
|
4155
|
-
const fileStore = {
|
|
4156
|
-
...options.fileStore,
|
|
4157
|
-
throwOnInvalidDocument: true,
|
|
4158
|
-
filePath: parsedFilePath === null ? void 0 : path4.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
|
|
4159
|
-
salt: options.fileStore?.salt ?? defaults.salt,
|
|
4160
|
-
defaultDirectory: options.fileStore?.defaultDirectory || defaults.directory,
|
|
4161
|
-
defaultFileName: parsedFilePath === null ? `${hash}.enc` : `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`
|
|
4162
|
-
};
|
|
4163
|
-
const keychainStore = {
|
|
4164
|
-
...options.keychainStore,
|
|
4165
|
-
service: options.keychainStore?.service ?? defaults.service,
|
|
4166
|
-
account: `${options.keychainStore?.account ?? defaults.accountPrefix}:${hash}`
|
|
4167
|
-
};
|
|
4168
|
-
return createSecretStore({ ...options, fileStore, keychainStore }).store;
|
|
4169
|
-
}
|
|
4170
|
-
function createResourceSecretStore(resource, options, namespace) {
|
|
4171
|
-
return createNamedSecretStore(canonicalizeResourceIndicator(resource), options, {
|
|
4172
|
-
salt: DEFAULT_FILE_SALT,
|
|
4173
|
-
directory: DEFAULT_FILE_DIRECTORY,
|
|
4174
|
-
service: DEFAULT_KEYCHAIN_SERVICE,
|
|
4175
|
-
accountPrefix: "provider"
|
|
4176
|
-
}, namespace);
|
|
4177
|
-
}
|
|
4178
|
-
function createIssuerSecretStore(issuer, options, namespace) {
|
|
4179
|
-
return createNamedSecretStore(issuer, options, {
|
|
4180
|
-
salt: DEFAULT_CLIENT_FILE_SALT,
|
|
4181
|
-
directory: DEFAULT_CLIENT_FILE_DIRECTORY,
|
|
4182
|
-
service: DEFAULT_CLIENT_KEYCHAIN_SERVICE,
|
|
4183
|
-
accountPrefix: "issuer"
|
|
4184
|
-
}, namespace);
|
|
4185
|
-
}
|
|
4186
|
-
function assertPersistenceNamespace(namespace) {
|
|
4187
|
-
if (namespace !== void 0 && (typeof namespace !== "string" || namespace.trim() === "" || Buffer.byteLength(namespace, "utf8") > 1024))
|
|
4188
|
-
throw new Error("OAuth persistence namespace must be a nonempty string within 1024 bytes");
|
|
4189
|
-
}
|
|
4190
|
-
function isObjectRecord(value) {
|
|
4191
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4192
|
-
}
|
|
4193
|
-
function getOwnEntry3(record2, key2) {
|
|
4194
|
-
return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
|
|
4195
|
-
}
|
|
4196
|
-
function getOwnString(record2, key2) {
|
|
4197
|
-
const value = getOwnEntry3(record2, key2);
|
|
4198
|
-
return typeof value === "string" ? value : void 0;
|
|
4199
|
-
}
|
|
4200
|
-
function isStoredOAuthSession(value) {
|
|
4201
|
-
if (!isObjectRecord(value)) {
|
|
4202
|
-
return false;
|
|
4203
4184
|
}
|
|
4204
|
-
|
|
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 ?? ".");
|
|
4205
4201
|
}
|
|
4206
|
-
function
|
|
4207
|
-
if (
|
|
4208
|
-
return
|
|
4202
|
+
function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
|
|
4203
|
+
if (symbolicLinkCheckStartPath === null) {
|
|
4204
|
+
return getExplicitProtectedCredentialPaths(resolvedPath);
|
|
4209
4205
|
}
|
|
4210
|
-
const
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
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);
|
|
4216
4215
|
}
|
|
4217
|
-
return
|
|
4216
|
+
return protectedPaths;
|
|
4218
4217
|
}
|
|
4219
|
-
function
|
|
4220
|
-
if (
|
|
4221
|
-
|
|
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");
|
|
4222
4221
|
}
|
|
4223
|
-
|
|
4224
|
-
|
|
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
|
+
}
|
|
4225
4226
|
}
|
|
4226
|
-
|
|
4227
|
-
|
|
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");
|
|
4228
4231
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
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];
|
|
4232
4241
|
}
|
|
4233
|
-
const
|
|
4234
|
-
|
|
4235
|
-
|
|
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);
|
|
4236
4250
|
}
|
|
4237
|
-
|
|
4238
|
-
return scope === void 0 || typeof scope === "string" && scope.trim().length > 0;
|
|
4239
|
-
}
|
|
4240
|
-
function isNonBlankOwnString(record2, key2) {
|
|
4241
|
-
const value = getOwnString(record2, key2);
|
|
4242
|
-
return value !== void 0 && value.trim().length > 0;
|
|
4251
|
+
return protectedPaths;
|
|
4243
4252
|
}
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
if (scope === void 0)
|
|
4248
|
-
return void 0;
|
|
4249
|
-
if (typeof scope !== "string" || [...scope].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126 || char === '"' || char === "\\"))
|
|
4250
|
-
throw new Error("Invalid OAuth scope syntax");
|
|
4251
|
-
const normalized = [...new Set(scope.split(" ").filter(Boolean))].sort().join(" ");
|
|
4252
|
-
return normalized || void 0;
|
|
4253
|
+
function isPathInsideOrEqual(childPath, parentPath) {
|
|
4254
|
+
const relativePath = path2.relative(parentPath, childPath);
|
|
4255
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path2.isAbsolute(relativePath);
|
|
4253
4256
|
}
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
if (response.redirected || response.type === "opaqueredirect") {
|
|
4262
|
-
void response.body?.cancel().catch(() => void 0);
|
|
4263
|
-
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
|
+
}
|
|
4264
4264
|
}
|
|
4265
|
-
return response;
|
|
4266
4265
|
}
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
// ../mcp-oauth/dist/client/loopback-authorization.js
|
|
4272
|
-
import http from "node:http";
|
|
4273
|
-
|
|
4274
|
-
// ../mcp-oauth/dist/client/authorization-state.js
|
|
4275
|
-
import crypto2 from "node:crypto";
|
|
4276
|
-
function createAuthorizationState(input) {
|
|
4277
|
-
const payload = {
|
|
4278
|
-
v: 1,
|
|
4279
|
-
n: crypto2.randomBytes(16).toString("base64url"),
|
|
4280
|
-
i: input.issuer,
|
|
4281
|
-
r: input.requireIssuer
|
|
4266
|
+
function defaultMachineIdentity() {
|
|
4267
|
+
return {
|
|
4268
|
+
hostname: hostname(),
|
|
4269
|
+
username: userInfo().username
|
|
4282
4270
|
};
|
|
4283
|
-
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
4284
4271
|
}
|
|
4285
|
-
function
|
|
4286
|
-
|
|
4287
|
-
|
|
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;
|
|
4288
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) {
|
|
4289
4298
|
try {
|
|
4290
|
-
const
|
|
4291
|
-
|
|
4292
|
-
if (!isObjectRecord2(parsed)) {
|
|
4299
|
+
const parsed = JSON.parse(raw);
|
|
4300
|
+
if (!isRecord2(parsed)) {
|
|
4293
4301
|
return null;
|
|
4294
4302
|
}
|
|
4295
|
-
const version =
|
|
4296
|
-
const
|
|
4297
|
-
const
|
|
4298
|
-
const
|
|
4299
|
-
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") {
|
|
4300
4311
|
return null;
|
|
4301
4312
|
}
|
|
4302
4313
|
return {
|
|
4303
|
-
|
|
4304
|
-
|
|
4314
|
+
version,
|
|
4315
|
+
iv,
|
|
4316
|
+
authTag,
|
|
4317
|
+
ciphertext
|
|
4305
4318
|
};
|
|
4306
4319
|
} catch {
|
|
4307
4320
|
return null;
|
|
4308
4321
|
}
|
|
4309
4322
|
}
|
|
4310
|
-
function
|
|
4311
|
-
return typeof value === "object" &&
|
|
4323
|
+
function isRecord2(value) {
|
|
4324
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
4312
4325
|
}
|
|
4313
|
-
function
|
|
4326
|
+
function getOwnEntry2(record2, key2) {
|
|
4314
4327
|
return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
|
|
4315
4328
|
}
|
|
4329
|
+
function isNotFoundError(error) {
|
|
4330
|
+
return hasOwnErrorCode(error, "ENOENT");
|
|
4331
|
+
}
|
|
4332
|
+
function isAlreadyExistsError(error) {
|
|
4333
|
+
return hasOwnErrorCode(error, "EEXIST");
|
|
4334
|
+
}
|
|
4316
4335
|
|
|
4317
|
-
// ../
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4336
|
+
// ../auth-store/dist/keychain-store.js
|
|
4337
|
+
import { spawn } from "node:child_process";
|
|
4338
|
+
import { createHash } from "node:crypto";
|
|
4339
|
+
import { promises as nodeFs } from "node:fs";
|
|
4340
|
+
import { homedir as homedir2 } from "node:os";
|
|
4341
|
+
import path3 from "node:path";
|
|
4342
|
+
var SECURITY_CLI = "security";
|
|
4343
|
+
var KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
|
|
4344
|
+
var KeychainStore = class {
|
|
4345
|
+
runCommand;
|
|
4346
|
+
service;
|
|
4347
|
+
account;
|
|
4348
|
+
lockFs;
|
|
4349
|
+
lockDirectory;
|
|
4350
|
+
constructor(input) {
|
|
4351
|
+
this.runCommand = input.runCommand ?? runSecurityCommand;
|
|
4352
|
+
this.service = input.service.trim();
|
|
4353
|
+
this.account = input.account.trim();
|
|
4354
|
+
this.lockFs = input.lock?.fs ?? nodeFs;
|
|
4355
|
+
this.lockDirectory = input.lock?.directory ?? path3.join(homedir2(), ".auth-store", "keychain-locks");
|
|
4356
|
+
if (this.service.length === 0) {
|
|
4357
|
+
throw new Error("Keychain service must not be empty");
|
|
4358
|
+
}
|
|
4359
|
+
if (this.account.length === 0) {
|
|
4360
|
+
throw new Error("Keychain account must not be empty");
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
async get() {
|
|
4364
|
+
const result = await this.executeSecurityCommand(["find-generic-password", "-s", this.service, "-a", this.account, "-w"], "read secret from macOS Keychain");
|
|
4365
|
+
if (getCommandExitCode(result) === 0) {
|
|
4366
|
+
return stripTrailingLineBreak(getCommandOutput(result, "stdout"));
|
|
4367
|
+
}
|
|
4368
|
+
if (isKeychainEntryNotFound(result)) {
|
|
4369
|
+
return null;
|
|
4370
|
+
}
|
|
4371
|
+
throw createSecurityCliFailure("read secret from macOS Keychain", result);
|
|
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);
|
|
4393
|
+
}
|
|
4350
4394
|
}
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
controller.signal.throwIfAborted();
|
|
4356
|
-
if (used)
|
|
4357
|
-
throw new Error("OAuth authorization session has already been used");
|
|
4358
|
-
used = true;
|
|
4359
|
-
try {
|
|
4360
|
-
return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
|
|
4361
|
-
} finally {
|
|
4362
|
-
clearTimeout(timer);
|
|
4363
|
-
}
|
|
4364
|
-
},
|
|
4365
|
-
close() {
|
|
4366
|
-
controller.abort(new Error("OAuth authorization session closed"));
|
|
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;
|
|
4367
4399
|
}
|
|
4368
|
-
|
|
4369
|
-
}
|
|
4370
|
-
|
|
4371
|
-
if (options.redirectUri !== void 0) {
|
|
4372
|
-
let url;
|
|
4400
|
+
throw createSecurityCliFailure("delete secret from macOS Keychain", result);
|
|
4401
|
+
}
|
|
4402
|
+
async executeSecurityCommand(args, operation, options) {
|
|
4373
4403
|
try {
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4404
|
+
if (options === void 0) {
|
|
4405
|
+
return await this.runCommand(SECURITY_CLI, args);
|
|
4406
|
+
}
|
|
4407
|
+
return await this.runCommand(SECURITY_CLI, args, options);
|
|
4408
|
+
} catch (error) {
|
|
4409
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4410
|
+
throw new Error(`Failed to ${operation}: ${message}`);
|
|
4377
4411
|
}
|
|
4378
|
-
const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
|
|
4379
|
-
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)
|
|
4380
|
-
throw new Error("Invalid OAuth loopback redirect URI");
|
|
4381
|
-
return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
|
|
4382
4412
|
}
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
}
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
const
|
|
4393
|
-
|
|
4394
|
-
signal.removeEventListener("abort", aborted);
|
|
4395
|
-
};
|
|
4396
|
-
const handleError = (error) => {
|
|
4397
|
-
cleanup();
|
|
4398
|
-
reject(error);
|
|
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}`;
|
|
4399
4424
|
};
|
|
4400
|
-
const
|
|
4401
|
-
|
|
4402
|
-
|
|
4425
|
+
const appendStdinError = () => {
|
|
4426
|
+
if (stdinErrorMessage === void 0) {
|
|
4427
|
+
return;
|
|
4428
|
+
}
|
|
4429
|
+
appendStderr(stdinErrorMessage);
|
|
4430
|
+
stdinErrorMessage = void 0;
|
|
4403
4431
|
};
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
if (address === null || typeof address === "string") {
|
|
4416
|
-
reject(new Error("OAuth listener has no TCP address"));
|
|
4417
|
-
return;
|
|
4418
|
-
}
|
|
4419
|
-
resolve(address.port);
|
|
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);
|
|
4420
4443
|
});
|
|
4421
|
-
|
|
4422
|
-
cleanup();
|
|
4423
|
-
reject(error);
|
|
4444
|
+
child.stdin?.end(options.stdin);
|
|
4424
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
|
+
});
|
|
4425
4464
|
});
|
|
4426
4465
|
}
|
|
4427
|
-
function
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
|
|
4467
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
4468
|
-
res.end(buildSuccessPage(options.landingPage));
|
|
4469
|
-
settle(() => resolve(code));
|
|
4470
|
-
} catch (error) {
|
|
4471
|
-
res.writeHead(400);
|
|
4472
|
-
res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
|
|
4473
|
-
settle(() => reject(error));
|
|
4474
|
-
}
|
|
4475
|
-
};
|
|
4476
|
-
server.on("request", request);
|
|
4477
|
-
signal.addEventListener("abort", aborted, { once: true });
|
|
4478
|
-
if (options.readLine !== void 0) {
|
|
4479
|
-
void Promise.resolve().then(() => settled ? void 0 : options.readLine()).then((input) => {
|
|
4480
|
-
if (settled)
|
|
4481
|
-
return;
|
|
4482
|
-
const callbackParameters = extractCallbackParametersFromInput(input);
|
|
4483
|
-
if (callbackParameters === null)
|
|
4484
|
-
throw new Error("OAuth callback missing authorization code");
|
|
4485
|
-
validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
|
|
4486
|
-
if (callbackParameters.error !== null)
|
|
4487
|
-
throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
|
|
4488
|
-
const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
|
|
4489
|
-
settle(() => resolve(code));
|
|
4490
|
-
}).catch((error) => settle(() => reject(error)));
|
|
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");
|
|
4491
4505
|
}
|
|
4492
|
-
|
|
4493
|
-
|
|
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");
|
|
4494
4511
|
}
|
|
4495
|
-
|
|
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 };
|
|
4496
4523
|
}
|
|
4497
|
-
function
|
|
4498
|
-
const
|
|
4499
|
-
|
|
4500
|
-
|
|
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";
|
|
4501
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;
|
|
4502
4543
|
try {
|
|
4503
|
-
|
|
4504
|
-
return {
|
|
4505
|
-
code: url.searchParams.get("code"),
|
|
4506
|
-
error: url.searchParams.get("error"),
|
|
4507
|
-
errorDescription: url.searchParams.get("error_description"),
|
|
4508
|
-
state: url.searchParams.get("state"),
|
|
4509
|
-
iss: url.searchParams.get("iss")
|
|
4510
|
-
};
|
|
4544
|
+
url = value instanceof URL ? new URL(value.toString()) : new URL(value);
|
|
4511
4545
|
} catch {
|
|
4512
|
-
|
|
4513
|
-
code: trimmed,
|
|
4514
|
-
error: null,
|
|
4515
|
-
errorDescription: null,
|
|
4516
|
-
state: null,
|
|
4517
|
-
iss: null
|
|
4518
|
-
};
|
|
4546
|
+
throw new Error("Resource indicator must be an absolute URL");
|
|
4519
4547
|
}
|
|
4548
|
+
url.hash = "";
|
|
4549
|
+
return url.toString();
|
|
4520
4550
|
}
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
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);
|
|
4525
4562
|
return {
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
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
|
+
}
|
|
4529
4594
|
};
|
|
4530
4595
|
}
|
|
4531
|
-
function
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
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;
|
|
4535
4677
|
}
|
|
4536
|
-
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"));
|
|
4537
4679
|
}
|
|
4538
|
-
function
|
|
4539
|
-
if (
|
|
4540
|
-
|
|
4541
|
-
throw new Error("OAuth callback missing state");
|
|
4542
|
-
}
|
|
4543
|
-
if (callback.state !== expected.state) {
|
|
4544
|
-
throw new Error("OAuth callback state mismatch");
|
|
4545
|
-
}
|
|
4680
|
+
function isStoredOAuthDiscovery(value) {
|
|
4681
|
+
if (!isObjectRecord2(value)) {
|
|
4682
|
+
return false;
|
|
4546
4683
|
}
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
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;
|
|
4551
4689
|
}
|
|
4552
|
-
if (
|
|
4553
|
-
|
|
4690
|
+
if (!isObjectRecord2(value)) {
|
|
4691
|
+
return false;
|
|
4554
4692
|
}
|
|
4693
|
+
if (!isNonBlankOwnString(value, "accessToken") || getOwnString(value, "tokenType") !== "Bearer") {
|
|
4694
|
+
return false;
|
|
4695
|
+
}
|
|
4696
|
+
const expiresAt = getOwnEntry4(value, "expiresAt");
|
|
4697
|
+
if (expiresAt !== null && (typeof expiresAt !== "number" || !Number.isSafeInteger(expiresAt) || expiresAt > MAX_JS_DATE_MS || !Number.isFinite(new Date(expiresAt).getTime()))) {
|
|
4698
|
+
return false;
|
|
4699
|
+
}
|
|
4700
|
+
const refreshToken = getOwnEntry4(value, "refreshToken");
|
|
4701
|
+
if (refreshToken !== void 0 && (typeof refreshToken !== "string" || refreshToken.trim().length === 0)) {
|
|
4702
|
+
return false;
|
|
4703
|
+
}
|
|
4704
|
+
const scope = getOwnEntry4(value, "scope");
|
|
4705
|
+
return scope === void 0 || typeof scope === "string" && scope.trim().length > 0;
|
|
4555
4706
|
}
|
|
4556
|
-
function
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
function escapeHtml(text) {
|
|
4560
|
-
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
4707
|
+
function isNonBlankOwnString(record2, key2) {
|
|
4708
|
+
const value = getOwnString(record2, key2);
|
|
4709
|
+
return value !== void 0 && value.trim().length > 0;
|
|
4561
4710
|
}
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4711
|
+
|
|
4712
|
+
// ../mcp-oauth/dist/client/default-oauth-client-provider.js
|
|
4713
|
+
import { isIP } from "node:net";
|
|
4714
|
+
|
|
4715
|
+
// ../mcp-oauth/dist/http-fetch.js
|
|
4716
|
+
async function fetchMcpResponse(fetchImplementation, input, init = {}) {
|
|
4717
|
+
const response = await fetchImplementation(input, { ...init, redirect: "error" });
|
|
4718
|
+
if (response.redirected || response.type === "opaqueredirect") {
|
|
4719
|
+
void response.body?.cancel().catch(() => void 0);
|
|
4720
|
+
throw new Error("MCP HTTP redirects are not allowed");
|
|
4721
|
+
}
|
|
4722
|
+
return response;
|
|
4574
4723
|
}
|
|
4575
4724
|
|
|
4725
|
+
// ../mcp-oauth/dist/client/default-oauth-client-provider.js
|
|
4726
|
+
import { URL as URL2 } from "node:url";
|
|
4727
|
+
|
|
4576
4728
|
// ../mcp-oauth/dist/client/pkce.js
|
|
4577
4729
|
import crypto3 from "node:crypto";
|
|
4578
4730
|
function generateCodeVerifier() {
|
|
@@ -4665,6 +4817,7 @@ async function exchangeAuthorizationCode(input) {
|
|
|
4665
4817
|
tokenEndpoint: input.tokenEndpoint,
|
|
4666
4818
|
clientId: input.clientId,
|
|
4667
4819
|
clientSecret: input.clientSecret,
|
|
4820
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
4668
4821
|
params: {
|
|
4669
4822
|
grant_type: "authorization_code",
|
|
4670
4823
|
code: input.code,
|
|
@@ -4683,6 +4836,7 @@ async function refreshAccessToken(input) {
|
|
|
4683
4836
|
tokenEndpoint: input.tokenEndpoint,
|
|
4684
4837
|
clientId: input.clientId,
|
|
4685
4838
|
clientSecret: input.clientSecret,
|
|
4839
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
4686
4840
|
params: {
|
|
4687
4841
|
grant_type: "refresh_token",
|
|
4688
4842
|
refresh_token: input.refreshToken,
|
|
@@ -4694,21 +4848,26 @@ async function refreshAccessToken(input) {
|
|
|
4694
4848
|
});
|
|
4695
4849
|
}
|
|
4696
4850
|
async function requestTokens(input) {
|
|
4697
|
-
const
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4851
|
+
const method = normalizeOAuthTokenEndpointAuthMethod(input.tokenEndpointAuthMethod) ?? (input.clientSecret === void 0 ? "none" : "client_secret_post");
|
|
4852
|
+
if (method !== "none" && (input.clientSecret === void 0 || input.clientSecret.trim() === ""))
|
|
4853
|
+
throw new Error("OAuth token endpoint authentication requires a client secret");
|
|
4854
|
+
const body = new URLSearchParams(input.params);
|
|
4855
|
+
const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded" });
|
|
4856
|
+
if (method === "client_secret_basic") {
|
|
4857
|
+
const encoded = new URLSearchParams({ credential: input.clientId }).toString().slice("credential=".length);
|
|
4858
|
+
const encodedSecret = new URLSearchParams({ credential: input.clientSecret }).toString().slice("credential=".length);
|
|
4859
|
+
headers.set("Authorization", `Basic ${Buffer.from(`${encoded}:${encodedSecret}`).toString("base64")}`);
|
|
4860
|
+
} else {
|
|
4861
|
+
body.set("client_id", input.clientId);
|
|
4862
|
+
if (method === "client_secret_post")
|
|
4863
|
+
body.set("client_secret", input.clientSecret);
|
|
4703
4864
|
}
|
|
4704
4865
|
input.signal?.throwIfAborted();
|
|
4705
4866
|
const deadline = AbortSignal.timeout(3e4);
|
|
4706
4867
|
const signal = input.signal === void 0 ? deadline : AbortSignal.any([input.signal, deadline]);
|
|
4707
4868
|
const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
|
|
4708
4869
|
method: "POST",
|
|
4709
|
-
headers
|
|
4710
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
4711
|
-
},
|
|
4870
|
+
headers,
|
|
4712
4871
|
body: body.toString(),
|
|
4713
4872
|
signal
|
|
4714
4873
|
});
|
|
@@ -4861,6 +5020,9 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4861
5020
|
assertPersistenceNamespace(options.persistenceNamespace);
|
|
4862
5021
|
const clientMetadata = getClientMetadata(options.client);
|
|
4863
5022
|
const requestedScope = clientMetadata?.scope;
|
|
5023
|
+
const configuredClient = normalizeConfiguredClient(options.client);
|
|
5024
|
+
const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
|
|
5025
|
+
const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
|
|
4864
5026
|
const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
|
|
4865
5027
|
const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
|
|
4866
5028
|
const now = options.now ?? Date.now;
|
|
@@ -4877,8 +5039,8 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4877
5039
|
}
|
|
4878
5040
|
const initialGrant = options.initialGrant === void 0 ? void 0 : {
|
|
4879
5041
|
resource: canonicalizeResourceIndicator(options.initialGrant.resource),
|
|
4880
|
-
tokens:
|
|
4881
|
-
client:
|
|
5042
|
+
tokens: normalizeImportedTokens(options.initialGrant.tokens, now),
|
|
5043
|
+
client: configuredClient
|
|
4882
5044
|
};
|
|
4883
5045
|
if (initialGrant !== void 0 && (initialGrant.tokens === void 0 || initialGrant.client === null))
|
|
4884
5046
|
throw new Error("OAuth initial grant requires valid tokens and the original client ID");
|
|
@@ -4955,6 +5117,10 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4955
5117
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
4956
5118
|
return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
|
|
4957
5119
|
let session = await loadSession(canonicalResource);
|
|
5120
|
+
if (session !== null)
|
|
5121
|
+
assertRegistrationIssuer(session.client, session.authorizationServer);
|
|
5122
|
+
if (configuredClient !== null && discovery !== void 0)
|
|
5123
|
+
assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
|
|
4958
5124
|
if (session !== null && initialGrant?.resource === canonicalResource)
|
|
4959
5125
|
initialGrantConsumed = true;
|
|
4960
5126
|
signal?.throwIfAborted();
|
|
@@ -4982,13 +5148,15 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4982
5148
|
if (forceRefresh && rejectedTokens !== void 0 && (rejectedTokens === null || session?.tokens === void 0 || !sameTokenGrant(session.tokens, rejectedTokens)))
|
|
4983
5149
|
forceRefresh = false;
|
|
4984
5150
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
4985
|
-
if ((options.client.mode === "static" || initialGrant !== void 0) && session !== null && (session.tokens !== void 0 || session.refreshState === "pending")) {
|
|
4986
|
-
const configured =
|
|
5151
|
+
if ((options.client.mode === "static" || configuredClient?.registration !== void 0 || initialGrant !== void 0) && session !== null && (session.tokens !== void 0 || session.refreshState === "pending")) {
|
|
5152
|
+
const configured = configuredClient;
|
|
4987
5153
|
if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
|
|
4988
5154
|
throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
|
|
4989
5155
|
}
|
|
4990
5156
|
if (requestedScope !== void 0 && session?.tokens !== void 0 && normalizeOAuthScope(session.tokens.scope ?? session.requestedScope) !== requestedScope)
|
|
4991
5157
|
throw new Error("Stored session does not match the requested OAuth scope; authorize again or select separate persistence");
|
|
5158
|
+
if (configuredTokenMethod !== void 0 && session !== null && (session.tokens !== void 0 || session.refreshState === "pending") && (session.client.tokenEndpointAuthMethod ?? (session.client.clientSecret === void 0 ? "none" : "client_secret_post")) !== configuredTokenMethod)
|
|
5159
|
+
throw new Error("Stored session does not match the requested OAuth token endpoint authentication; select separate persistence or reset it");
|
|
4992
5160
|
if (session?.refreshState === "pending") {
|
|
4993
5161
|
if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === void 0)
|
|
4994
5162
|
throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
|
|
@@ -4998,6 +5166,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4998
5166
|
return session;
|
|
4999
5167
|
}
|
|
5000
5168
|
if (session?.tokens?.refreshToken !== void 0 && sessionDiscovery !== void 0 && (forceRefresh || isExpired(session.tokens, now))) {
|
|
5169
|
+
if (hasExpiredClientSecret(session.client, now)) {
|
|
5170
|
+
if (!allowInteractive || options.allowInteractive === false || options.client.mode === "static" || configuredClient?.registration !== void 0)
|
|
5171
|
+
throw new Error("OAuth client secret has expired; authorize again or update the imported registration");
|
|
5172
|
+
return authorizeSession(canonicalResource, clearSessionTokens(session), sessionDiscovery, fetch2, signal);
|
|
5173
|
+
}
|
|
5001
5174
|
session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
|
|
5002
5175
|
if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) {
|
|
5003
5176
|
return session;
|
|
@@ -5021,6 +5194,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5021
5194
|
if (session.tokens?.refreshToken === void 0) {
|
|
5022
5195
|
return session;
|
|
5023
5196
|
}
|
|
5197
|
+
assertTokenEndpointAuthentication(session.client, discovery.authorizationServerMetadata);
|
|
5024
5198
|
const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
|
|
5025
5199
|
await saveSession(resource, pendingSession);
|
|
5026
5200
|
signal?.throwIfAborted();
|
|
@@ -5032,6 +5206,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5032
5206
|
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
5033
5207
|
clientId: session.client.clientId,
|
|
5034
5208
|
clientSecret: session.client.clientSecret,
|
|
5209
|
+
tokenEndpointAuthMethod: session.client.tokenEndpointAuthMethod,
|
|
5035
5210
|
refreshToken: session.tokens.refreshToken,
|
|
5036
5211
|
resource,
|
|
5037
5212
|
fetch: fetch2,
|
|
@@ -5095,6 +5270,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5095
5270
|
let resolvedClient = null;
|
|
5096
5271
|
try {
|
|
5097
5272
|
resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2, signal);
|
|
5273
|
+
assertTokenEndpointAuthentication(resolvedClient.client, discovery.authorizationServerMetadata);
|
|
5098
5274
|
const sessionWithoutTokens = {
|
|
5099
5275
|
resource,
|
|
5100
5276
|
authorizationServer: discovery.authorizationServer,
|
|
@@ -5118,6 +5294,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5118
5294
|
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
5119
5295
|
clientId: resolvedClient.client.clientId,
|
|
5120
5296
|
clientSecret: resolvedClient.client.clientSecret,
|
|
5297
|
+
tokenEndpointAuthMethod: resolvedClient.client.tokenEndpointAuthMethod,
|
|
5121
5298
|
code,
|
|
5122
5299
|
codeVerifier: verifier,
|
|
5123
5300
|
redirectUri: loopback.redirectUri,
|
|
@@ -5157,11 +5334,13 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5157
5334
|
}
|
|
5158
5335
|
async function resolveClient(existingSession, discovery, redirectUri, fetch2, parentSignal) {
|
|
5159
5336
|
parentSignal?.throwIfAborted();
|
|
5160
|
-
|
|
5161
|
-
if (options.client.mode === "static") {
|
|
5337
|
+
if (options.client.mode === "static" || configuredClient?.registration !== void 0) {
|
|
5162
5338
|
if (configuredClient === null) {
|
|
5163
5339
|
throw new Error("OAuth client_id must not be blank");
|
|
5164
5340
|
}
|
|
5341
|
+
assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
|
|
5342
|
+
if (hasExpiredClientSecret(configuredClient, now))
|
|
5343
|
+
throw new Error("OAuth client secret has expired; update the imported registration");
|
|
5165
5344
|
return {
|
|
5166
5345
|
kind: "static",
|
|
5167
5346
|
fromStoredRegistration: false,
|
|
@@ -5176,7 +5355,14 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5176
5355
|
client: configuredClient
|
|
5177
5356
|
};
|
|
5178
5357
|
}
|
|
5179
|
-
|
|
5358
|
+
let storedClient = await loadRegisteredClient(discovery.authorizationServer);
|
|
5359
|
+
if (storedClient !== null) {
|
|
5360
|
+
assertRegistrationIssuer(storedClient, discovery.authorizationServer);
|
|
5361
|
+
if (hasExpiredClientSecret(storedClient, now) || !registrationMatchesRedirect(storedClient, redirectUri)) {
|
|
5362
|
+
await clearRegisteredClient(discovery.authorizationServer);
|
|
5363
|
+
storedClient = null;
|
|
5364
|
+
}
|
|
5365
|
+
}
|
|
5180
5366
|
if (storedClient !== null) {
|
|
5181
5367
|
return {
|
|
5182
5368
|
kind: "dynamic",
|
|
@@ -5185,7 +5371,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5185
5371
|
};
|
|
5186
5372
|
}
|
|
5187
5373
|
if (registrationEndpoint === void 0) {
|
|
5188
|
-
if (existingSession !== null && existingSession.client.clientId.length > 0) {
|
|
5374
|
+
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
|
|
5189
5375
|
return {
|
|
5190
5376
|
kind: "dynamic",
|
|
5191
5377
|
fromStoredRegistration: true,
|
|
@@ -5194,7 +5380,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5194
5380
|
}
|
|
5195
5381
|
throw new Error("Authorization server metadata is missing registration_endpoint");
|
|
5196
5382
|
}
|
|
5197
|
-
if (existingSession !== null && existingSession.client.clientId.length > 0) {
|
|
5383
|
+
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
|
|
5198
5384
|
const isConfiguredStaticFallback = configuredClient !== null && existingSession.client.clientId === configuredClient.clientId && existingSession.client.clientSecret === configuredClient.clientSecret;
|
|
5199
5385
|
if (!isConfiguredStaticFallback) {
|
|
5200
5386
|
await saveRegisteredClient(discovery.authorizationServer, existingSession.client);
|
|
@@ -5205,7 +5391,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5205
5391
|
};
|
|
5206
5392
|
}
|
|
5207
5393
|
}
|
|
5208
|
-
const
|
|
5394
|
+
const supported = getSupportedTokenAuthMethods(discovery.authorizationServerMetadata);
|
|
5395
|
+
const registrationMethod = requestedTokenMethod ?? (supported === void 0 ? "none" : ["none", "client_secret_basic", "client_secret_post"].find((method) => supported.includes(method)));
|
|
5396
|
+
if (registrationMethod === void 0 || supported !== void 0 && !supported.includes(registrationMethod))
|
|
5397
|
+
throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
|
|
5398
|
+
const registrationBody = buildClientRegistrationBody(clientMetadata, redirectUri, registrationMethod);
|
|
5209
5399
|
const deadline = AbortSignal.timeout(3e4);
|
|
5210
5400
|
const signal = parentSignal === void 0 ? deadline : AbortSignal.any([parentSignal, deadline]);
|
|
5211
5401
|
const response = await fetchMcpResponse(fetch2, registrationEndpoint, {
|
|
@@ -5217,27 +5407,33 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5217
5407
|
signal
|
|
5218
5408
|
});
|
|
5219
5409
|
const payload = await readOAuthJsonObjectResponse(response, signal);
|
|
5220
|
-
const
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
}
|
|
5224
|
-
const clientSecret = getOwnString2(payload, "client_secret");
|
|
5410
|
+
const registration = parseOAuthClientRegistration(payload);
|
|
5411
|
+
const registeredSecret = getOwnString2(registration, "client_secret");
|
|
5412
|
+
const responseMethod = normalizeOAuthTokenEndpointAuthMethod(getOwnEntry6(registration, "token_endpoint_auth_method")) ?? requestedTokenMethod ?? (supported === void 0 ? void 0 : normalizeOAuthTokenEndpointAuthMethod(registrationMethod));
|
|
5225
5413
|
const registeredClient = {
|
|
5226
|
-
clientId:
|
|
5227
|
-
|
|
5414
|
+
clientId: registration.client_id.trim(),
|
|
5415
|
+
...registeredSecret === void 0 ? {} : { clientSecret: registeredSecret.trim() },
|
|
5416
|
+
...responseMethod === void 0 ? {} : { tokenEndpointAuthMethod: responseMethod },
|
|
5417
|
+
registration
|
|
5228
5418
|
};
|
|
5229
|
-
|
|
5419
|
+
assertRegistrationIssuer(registeredClient, discovery.authorizationServer);
|
|
5420
|
+
if (hasExpiredClientSecret(registeredClient, now))
|
|
5421
|
+
throw new Error("OAuth client secret has expired in the registration response");
|
|
5422
|
+
if (!registrationMatchesRedirect(registeredClient, redirectUri, true))
|
|
5423
|
+
throw new Error("OAuth registration response does not match the requested redirect URI");
|
|
5424
|
+
const clientWithRedirect = { ...registeredClient, requestedRedirectUri: redirectUri };
|
|
5425
|
+
await saveRegisteredClient(discovery.authorizationServer, clientWithRedirect);
|
|
5230
5426
|
return {
|
|
5231
5427
|
kind: "dynamic",
|
|
5232
5428
|
fromStoredRegistration: false,
|
|
5233
|
-
client:
|
|
5429
|
+
client: clientWithRedirect
|
|
5234
5430
|
};
|
|
5235
5431
|
}
|
|
5236
5432
|
async function loadSession(resource) {
|
|
5237
5433
|
return normalizeLoadedSession(await sessionStore.load(resource));
|
|
5238
5434
|
}
|
|
5239
5435
|
async function saveSession(resource, session) {
|
|
5240
|
-
await sessionStore.save(resource, session);
|
|
5436
|
+
await sessionStore.save(resource, structuredClone(session));
|
|
5241
5437
|
}
|
|
5242
5438
|
async function clearSession(resource) {
|
|
5243
5439
|
await sessionStore.clear(resource);
|
|
@@ -5250,7 +5446,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5250
5446
|
return null;
|
|
5251
5447
|
}
|
|
5252
5448
|
const client = await clientStore.load(issuer);
|
|
5253
|
-
const normalizedClient = client === null ? null :
|
|
5449
|
+
const normalizedClient = client === null ? null : normalizeStoredOAuthClient(client);
|
|
5254
5450
|
if (client !== null && normalizedClient === null) {
|
|
5255
5451
|
await clientStore.clear(issuer);
|
|
5256
5452
|
return null;
|
|
@@ -5323,7 +5519,7 @@ function normalizeLoadedSession(session) {
|
|
|
5323
5519
|
const refreshState = getOwnEntry6(session, "refreshState");
|
|
5324
5520
|
if (refreshState !== void 0 && (refreshState !== "pending" || getOwnEntry6(session, "tokens") !== void 0))
|
|
5325
5521
|
throw new Error("Stored OAuth refresh state is invalid");
|
|
5326
|
-
const client =
|
|
5522
|
+
const client = normalizeStoredOAuthClient(getOwnEntry6(session, "client"));
|
|
5327
5523
|
if (client === null) {
|
|
5328
5524
|
return { ...session, client: { clientId: "" }, tokens: void 0 };
|
|
5329
5525
|
}
|
|
@@ -5333,24 +5529,18 @@ function normalizeLoadedSession(session) {
|
|
|
5333
5529
|
tokens: normalizeStoredTokens(getOwnEntry6(session, "tokens"))
|
|
5334
5530
|
};
|
|
5335
5531
|
}
|
|
5336
|
-
function
|
|
5337
|
-
if (!isObjectRecord3(value))
|
|
5338
|
-
return
|
|
5339
|
-
|
|
5340
|
-
const
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
}
|
|
5349
|
-
if (typeof clientSecret !== "string" || clientSecret.trim().length === 0) {
|
|
5350
|
-
return null;
|
|
5351
|
-
}
|
|
5352
|
-
const normalizedClientSecret = clientSecret.trim();
|
|
5353
|
-
return { clientId: normalizedClientId, clientSecret: normalizedClientSecret };
|
|
5532
|
+
function normalizeImportedTokens(value, now) {
|
|
5533
|
+
if (!isObjectRecord3(value))
|
|
5534
|
+
return void 0;
|
|
5535
|
+
const absolute = getOwnEntry6(value, "expiresAt");
|
|
5536
|
+
const lifetime = getOwnEntry6(value, "expiresIn");
|
|
5537
|
+
const issuedAt = getOwnEntry6(value, "issuedAt");
|
|
5538
|
+
if (lifetime !== void 0 && (typeof lifetime !== "number" || !Number.isSafeInteger(lifetime) || lifetime < 0))
|
|
5539
|
+
throw new Error("OAuth initial grant has invalid relative expiry");
|
|
5540
|
+
if (issuedAt !== void 0 && (typeof issuedAt !== "number" || !Number.isSafeInteger(issuedAt) || Math.abs(issuedAt) > MAX_JS_DATE_MS3))
|
|
5541
|
+
throw new Error("OAuth initial grant has invalid issuance time");
|
|
5542
|
+
const expiresAt = absolute !== void 0 && absolute !== null ? absolute : lifetime === void 0 ? null : (issuedAt === void 0 ? now() : issuedAt) + lifetime * 1e3;
|
|
5543
|
+
return normalizeStoredTokens({ ...value, expiresAt });
|
|
5354
5544
|
}
|
|
5355
5545
|
function normalizeStoredTokens(value) {
|
|
5356
5546
|
if (value === void 0 || !isObjectRecord3(value)) {
|
|
@@ -5387,12 +5577,12 @@ function getClientMetadata(client) {
|
|
|
5387
5577
|
};
|
|
5388
5578
|
}
|
|
5389
5579
|
function normalizeConfiguredClient(client) {
|
|
5390
|
-
const
|
|
5391
|
-
|
|
5580
|
+
const registration = client.registration === void 0 ? void 0 : parseOAuthClientRegistration(client.registration);
|
|
5581
|
+
const clientId = normalizeOptionalOAuthString(client.clientId) ?? registration?.client_id.trim();
|
|
5582
|
+
if (clientId === void 0)
|
|
5392
5583
|
return null;
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
return clientSecret === void 0 ? { clientId } : { clientId, clientSecret };
|
|
5584
|
+
const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === void 0 ? void 0 : getOwnString2(registration, "client_secret")?.trim());
|
|
5585
|
+
return normalizeStoredOAuthClient({ clientId, clientSecret, registration, tokenEndpointAuthMethod: client.tokenEndpointAuthMethod });
|
|
5396
5586
|
}
|
|
5397
5587
|
function normalizeOptionalOAuthString(value) {
|
|
5398
5588
|
if (value === void 0) {
|
|
@@ -5492,12 +5682,39 @@ function assertRequestMatchesResource(requestUrl, resource) {
|
|
|
5492
5682
|
throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
|
|
5493
5683
|
}
|
|
5494
5684
|
}
|
|
5495
|
-
function
|
|
5685
|
+
function assertRegistrationIssuer(client, issuer) {
|
|
5686
|
+
const registrationIssuer = client.registration === void 0 ? void 0 : getOwnString2(client.registration, "issuer");
|
|
5687
|
+
if (registrationIssuer !== void 0 && registrationIssuer !== issuer)
|
|
5688
|
+
throw new Error("OAuth client registration issuer does not match the authorization server");
|
|
5689
|
+
}
|
|
5690
|
+
function hasExpiredClientSecret(client, now) {
|
|
5691
|
+
if (client.clientSecret === void 0 || client.tokenEndpointAuthMethod === "none" || client.registration === void 0)
|
|
5692
|
+
return false;
|
|
5693
|
+
const expiry = getOwnEntry6(client.registration, "client_secret_expires_at");
|
|
5694
|
+
return typeof expiry === "number" && expiry !== 0 && expiry <= now() / 1e3;
|
|
5695
|
+
}
|
|
5696
|
+
function getSupportedTokenAuthMethods(metadata) {
|
|
5697
|
+
const value = getOwnEntry6(metadata, "token_endpoint_auth_methods_supported");
|
|
5698
|
+
if (value === void 0)
|
|
5699
|
+
return void 0;
|
|
5700
|
+
if (!Array.isArray(value) || value.length > 128 || value.some((method) => typeof method !== "string"))
|
|
5701
|
+
throw new Error("Invalid OAuth token endpoint authentication metadata");
|
|
5702
|
+
return value;
|
|
5703
|
+
}
|
|
5704
|
+
function assertTokenEndpointAuthentication(client, metadata) {
|
|
5705
|
+
const method = client.tokenEndpointAuthMethod ?? (client.clientSecret === void 0 ? "none" : "client_secret_post");
|
|
5706
|
+
if (method !== "none" && client.clientSecret === void 0)
|
|
5707
|
+
throw new Error("OAuth token endpoint authentication requires a client secret");
|
|
5708
|
+
const supported = getSupportedTokenAuthMethods(metadata);
|
|
5709
|
+
if (supported !== void 0 && !supported.includes(method))
|
|
5710
|
+
throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
|
|
5711
|
+
}
|
|
5712
|
+
function buildClientRegistrationBody(metadata, redirectUri, tokenEndpointAuthMethod) {
|
|
5496
5713
|
const body = {
|
|
5497
5714
|
redirect_uris: [redirectUri],
|
|
5498
5715
|
grant_types: ["authorization_code", "refresh_token"],
|
|
5499
5716
|
response_types: ["code"],
|
|
5500
|
-
token_endpoint_auth_method:
|
|
5717
|
+
token_endpoint_auth_method: tokenEndpointAuthMethod
|
|
5501
5718
|
};
|
|
5502
5719
|
const clientName = metadata === void 0 ? void 0 : getOwnString2(metadata, "clientName");
|
|
5503
5720
|
const scope = metadata === void 0 ? void 0 : getOwnString2(metadata, "scope");
|