@mcp-z/client 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/cjs/auth/capability-discovery.js +13 -7
- package/dist/cjs/auth/capability-discovery.js.map +1 -1
- package/dist/cjs/auth/discovery-fetch.d.cts +35 -0
- package/dist/cjs/auth/discovery-fetch.d.ts +35 -0
- package/dist/cjs/auth/discovery-fetch.js +658 -0
- package/dist/cjs/auth/discovery-fetch.js.map +1 -0
- package/dist/cjs/auth/index.d.cts +1 -0
- package/dist/cjs/auth/index.d.ts +1 -0
- package/dist/cjs/auth/index.js +7 -0
- package/dist/cjs/auth/index.js.map +1 -1
- package/dist/cjs/auth/interactive-oauth-flow.d.cts +12 -12
- package/dist/cjs/auth/interactive-oauth-flow.d.ts +12 -12
- package/dist/cjs/auth/interactive-oauth-flow.js +22 -16
- package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -1
- package/dist/cjs/auth/rfc9728-discovery.d.cts +12 -25
- package/dist/cjs/auth/rfc9728-discovery.d.ts +12 -25
- package/dist/cjs/auth/rfc9728-discovery.js +59 -48
- package/dist/cjs/auth/rfc9728-discovery.js.map +1 -1
- package/dist/cjs/auth/types.d.cts +16 -0
- package/dist/cjs/auth/types.d.ts +16 -0
- package/dist/cjs/auth/types.js.map +1 -1
- package/dist/cjs/dcr/dcr-authenticator.d.cts +3 -4
- package/dist/cjs/dcr/dcr-authenticator.d.ts +3 -4
- package/dist/cjs/dcr/dcr-authenticator.js +22 -12
- package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
- package/dist/cjs/dcr/dynamic-client-registrar.d.cts +6 -17
- package/dist/cjs/dcr/dynamic-client-registrar.d.ts +6 -17
- package/dist/cjs/dcr/dynamic-client-registrar.js +10 -16
- package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/auth/capability-discovery.js +12 -6
- package/dist/esm/auth/capability-discovery.js.map +1 -1
- package/dist/esm/auth/discovery-fetch.d.ts +35 -0
- package/dist/esm/auth/discovery-fetch.js +184 -0
- package/dist/esm/auth/discovery-fetch.js.map +1 -0
- package/dist/esm/auth/index.d.ts +1 -0
- package/dist/esm/auth/index.js +1 -0
- package/dist/esm/auth/index.js.map +1 -1
- package/dist/esm/auth/interactive-oauth-flow.d.ts +12 -12
- package/dist/esm/auth/interactive-oauth-flow.js +25 -16
- package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
- package/dist/esm/auth/rfc9728-discovery.d.ts +12 -25
- package/dist/esm/auth/rfc9728-discovery.js +49 -56
- package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
- package/dist/esm/auth/types.d.ts +16 -0
- package/dist/esm/auth/types.js.map +1 -1
- package/dist/esm/dcr/dcr-authenticator.d.ts +3 -4
- package/dist/esm/dcr/dcr-authenticator.js +20 -11
- package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
- package/dist/esm/dcr/dynamic-client-registrar.d.ts +6 -17
- package/dist/esm/dcr/dynamic-client-registrar.js +13 -18
- package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +2 -7
|
@@ -23,6 +23,7 @@ _export(exports, {
|
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
var _urlutilsts = require("../lib/url-utils.js");
|
|
26
|
+
var _discoveryfetchts = require("./discovery-fetch.js");
|
|
26
27
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
27
28
|
try {
|
|
28
29
|
var info = gen[key](arg);
|
|
@@ -148,30 +149,16 @@ function _ts_generator(thisArg, body) {
|
|
|
148
149
|
};
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
|
-
/**
|
|
152
|
-
* Extract origin (protocol + host) from a URL
|
|
153
|
-
* @param url - Full URL that may include a path
|
|
154
|
-
* @returns Origin (e.g., "https://example.com") or original string if invalid URL
|
|
155
|
-
*
|
|
156
|
-
* @example
|
|
157
|
-
* getOrigin('https://example.com/mcp') // → 'https://example.com'
|
|
158
|
-
* getOrigin('http://localhost:9999/api/v1/mcp') // → 'http://localhost:9999'
|
|
159
|
-
*/ function getOrigin(url) {
|
|
152
|
+
/** Returns `url`'s origin (protocol + host), or the original string if it doesn't parse. */ function getOrigin(url) {
|
|
160
153
|
try {
|
|
161
154
|
return new URL(url).origin;
|
|
162
155
|
} catch (unused) {
|
|
163
|
-
// Invalid URL - return as-is for graceful degradation
|
|
164
156
|
return url;
|
|
165
157
|
}
|
|
166
158
|
}
|
|
167
|
-
/**
|
|
168
|
-
* Extract path from a URL (without origin)
|
|
169
|
-
* @param url - Full URL
|
|
170
|
-
* @returns Path component (e.g., "/mcp", "/api/v1/mcp") or empty string if no path
|
|
171
|
-
*/ function getPath(url) {
|
|
159
|
+
/** Returns `url`'s pathname, or `''` for the root path or an unparseable URL. */ function getPath(url) {
|
|
172
160
|
try {
|
|
173
161
|
var parsed = new URL(url);
|
|
174
|
-
// pathname includes leading slash, e.g., "/mcp"
|
|
175
162
|
return parsed.pathname === '/' ? '' : parsed.pathname;
|
|
176
163
|
} catch (unused) {
|
|
177
164
|
return '';
|
|
@@ -179,7 +166,7 @@ function _ts_generator(thisArg, body) {
|
|
|
179
166
|
}
|
|
180
167
|
function discoverProtectedResourceMetadata(resourceUrl) {
|
|
181
168
|
return _async_to_generator(function() {
|
|
182
|
-
var normalizedResourceUrl, headerMetadata, localWellKnownUrl, response, unused, origin, path, rootUrl, response1, metadata, rootMetadata, subPathUrl, subPathResponse, unused1, unused2, subPathUrl1, response2, unused3, _error;
|
|
169
|
+
var normalizedResourceUrl, allowLoopback, headerMetadata, localWellKnownUrl, response, unused, origin, path, rootUrl, response1, metadata, rootMetadata, subPathUrl, subPathResponse, unused1, unused2, subPathUrl1, response2, unused3, _error;
|
|
183
170
|
return _ts_generator(this, function(_state) {
|
|
184
171
|
switch(_state.label){
|
|
185
172
|
case 0:
|
|
@@ -190,9 +177,12 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
190
177
|
27
|
|
191
178
|
]);
|
|
192
179
|
normalizedResourceUrl = (0, _urlutilsts.normalizeUrl)(resourceUrl);
|
|
180
|
+
// resourceUrl is the server the caller configured (never remote-supplied);
|
|
181
|
+
// its loopback-ness is the trust signal every fetch below relies on.
|
|
182
|
+
allowLoopback = (0, _discoveryfetchts.isLoopbackUrl)(normalizedResourceUrl);
|
|
193
183
|
return [
|
|
194
184
|
4,
|
|
195
|
-
discoverProtectedResourceMetadataFromHeader(normalizedResourceUrl)
|
|
185
|
+
discoverProtectedResourceMetadataFromHeader(normalizedResourceUrl, allowLoopback)
|
|
196
186
|
];
|
|
197
187
|
case 1:
|
|
198
188
|
headerMetadata = _state.sent();
|
|
@@ -212,12 +202,14 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
212
202
|
]);
|
|
213
203
|
return [
|
|
214
204
|
4,
|
|
215
|
-
|
|
205
|
+
(0, _discoveryfetchts.discoveryFetch)(localWellKnownUrl, {
|
|
216
206
|
method: 'GET',
|
|
217
207
|
headers: {
|
|
218
208
|
Accept: 'application/json',
|
|
219
209
|
Connection: 'close'
|
|
220
210
|
}
|
|
211
|
+
}, 'protected resource metadata (path-local)', {
|
|
212
|
+
allowLoopback: allowLoopback
|
|
221
213
|
})
|
|
222
214
|
];
|
|
223
215
|
case 3:
|
|
@@ -228,7 +220,7 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
228
220
|
];
|
|
229
221
|
return [
|
|
230
222
|
4,
|
|
231
|
-
|
|
223
|
+
(0, _discoveryfetchts.readDiscoveryJson)(response, 'protected resource metadata (path-local)')
|
|
232
224
|
];
|
|
233
225
|
case 4:
|
|
234
226
|
return [
|
|
@@ -261,12 +253,14 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
261
253
|
]);
|
|
262
254
|
return [
|
|
263
255
|
4,
|
|
264
|
-
|
|
256
|
+
(0, _discoveryfetchts.discoveryFetch)(rootUrl, {
|
|
265
257
|
method: 'GET',
|
|
266
258
|
headers: {
|
|
267
259
|
Accept: 'application/json',
|
|
268
260
|
Connection: 'close'
|
|
269
261
|
}
|
|
262
|
+
}, 'protected resource metadata (root)', {
|
|
263
|
+
allowLoopback: allowLoopback
|
|
270
264
|
})
|
|
271
265
|
];
|
|
272
266
|
case 9:
|
|
@@ -277,7 +271,7 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
277
271
|
];
|
|
278
272
|
return [
|
|
279
273
|
4,
|
|
280
|
-
|
|
274
|
+
(0, _discoveryfetchts.readDiscoveryJson)(response1, 'protected resource metadata (root)')
|
|
281
275
|
];
|
|
282
276
|
case 10:
|
|
283
277
|
metadata = _state.sent();
|
|
@@ -315,12 +309,14 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
315
309
|
]);
|
|
316
310
|
return [
|
|
317
311
|
4,
|
|
318
|
-
|
|
312
|
+
(0, _discoveryfetchts.discoveryFetch)(subPathUrl, {
|
|
319
313
|
method: 'GET',
|
|
320
314
|
headers: {
|
|
321
315
|
Accept: 'application/json',
|
|
322
316
|
Connection: 'close'
|
|
323
317
|
}
|
|
318
|
+
}, 'protected resource metadata (sub-path)', {
|
|
319
|
+
allowLoopback: allowLoopback
|
|
324
320
|
})
|
|
325
321
|
];
|
|
326
322
|
case 12:
|
|
@@ -331,7 +327,7 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
331
327
|
];
|
|
332
328
|
return [
|
|
333
329
|
4,
|
|
334
|
-
|
|
330
|
+
(0, _discoveryfetchts.readDiscoveryJson)(subPathResponse, 'protected resource metadata (sub-path)')
|
|
335
331
|
];
|
|
336
332
|
case 13:
|
|
337
333
|
return [
|
|
@@ -382,12 +378,14 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
382
378
|
]);
|
|
383
379
|
return [
|
|
384
380
|
4,
|
|
385
|
-
|
|
381
|
+
(0, _discoveryfetchts.discoveryFetch)(subPathUrl1, {
|
|
386
382
|
method: 'GET',
|
|
387
383
|
headers: {
|
|
388
384
|
Accept: 'application/json',
|
|
389
385
|
Connection: 'close'
|
|
390
386
|
}
|
|
387
|
+
}, 'protected resource metadata (sub-path)', {
|
|
388
|
+
allowLoopback: allowLoopback
|
|
391
389
|
})
|
|
392
390
|
];
|
|
393
391
|
case 21:
|
|
@@ -398,7 +396,7 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
398
396
|
];
|
|
399
397
|
return [
|
|
400
398
|
4,
|
|
401
|
-
|
|
399
|
+
(0, _discoveryfetchts.readDiscoveryJson)(response2, 'protected resource metadata (sub-path)')
|
|
402
400
|
];
|
|
403
401
|
case 22:
|
|
404
402
|
return [
|
|
@@ -437,7 +435,7 @@ function discoverProtectedResourceMetadata(resourceUrl) {
|
|
|
437
435
|
});
|
|
438
436
|
})();
|
|
439
437
|
}
|
|
440
|
-
function discoverProtectedResourceMetadataFromHeader(resourceUrl) {
|
|
438
|
+
function discoverProtectedResourceMetadataFromHeader(resourceUrl, allowLoopback) {
|
|
441
439
|
return _async_to_generator(function() {
|
|
442
440
|
var response, header, postResponse, match, metadataUrl, metadataResponse, _error;
|
|
443
441
|
return _ts_generator(this, function(_state) {
|
|
@@ -492,15 +490,19 @@ function discoverProtectedResourceMetadataFromHeader(resourceUrl) {
|
|
|
492
490
|
2,
|
|
493
491
|
null
|
|
494
492
|
];
|
|
493
|
+
// metadataUrl is remote-server-chosen (the primary SSRF vector here);
|
|
494
|
+
// allowLoopback reflects trust in resourceUrl, not in metadataUrl.
|
|
495
495
|
metadataUrl = match[1];
|
|
496
496
|
return [
|
|
497
497
|
4,
|
|
498
|
-
|
|
498
|
+
(0, _discoveryfetchts.discoveryFetch)(metadataUrl, {
|
|
499
499
|
method: 'GET',
|
|
500
500
|
headers: {
|
|
501
501
|
Accept: 'application/json',
|
|
502
502
|
Connection: 'close'
|
|
503
503
|
}
|
|
504
|
+
}, 'resource_metadata URL', {
|
|
505
|
+
allowLoopback: allowLoopback
|
|
504
506
|
})
|
|
505
507
|
];
|
|
506
508
|
case 4:
|
|
@@ -513,7 +515,7 @@ function discoverProtectedResourceMetadataFromHeader(resourceUrl) {
|
|
|
513
515
|
}
|
|
514
516
|
return [
|
|
515
517
|
4,
|
|
516
|
-
|
|
518
|
+
(0, _discoveryfetchts.readDiscoveryJson)(metadataResponse, 'resource_metadata URL')
|
|
517
519
|
];
|
|
518
520
|
case 5:
|
|
519
521
|
return [
|
|
@@ -534,59 +536,68 @@ function discoverProtectedResourceMetadataFromHeader(resourceUrl) {
|
|
|
534
536
|
});
|
|
535
537
|
})();
|
|
536
538
|
}
|
|
537
|
-
function discoverAuthorizationServerMetadata(
|
|
538
|
-
return _async_to_generator(function() {
|
|
539
|
-
var normalizedAuthServerUrl, localWellKnownUrl, localResponse, origin, wellKnownUrl, response, _error;
|
|
539
|
+
function discoverAuthorizationServerMetadata(_0) {
|
|
540
|
+
return _async_to_generator(function(authServerUrl) {
|
|
541
|
+
var options, _options_allowLoopback, allowLoopback, normalizedAuthServerUrl, localWellKnownUrl, localResponse, origin, wellKnownUrl, response, _error;
|
|
542
|
+
var _arguments = arguments;
|
|
540
543
|
return _ts_generator(this, function(_state) {
|
|
541
544
|
switch(_state.label){
|
|
542
545
|
case 0:
|
|
546
|
+
options = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
|
|
547
|
+
_options_allowLoopback = options.allowLoopback, allowLoopback = _options_allowLoopback === void 0 ? false : _options_allowLoopback;
|
|
548
|
+
_state.label = 1;
|
|
549
|
+
case 1:
|
|
543
550
|
_state.trys.push([
|
|
544
|
-
|
|
545
|
-
|
|
551
|
+
1,
|
|
552
|
+
7,
|
|
546
553
|
,
|
|
547
|
-
|
|
554
|
+
8
|
|
548
555
|
]);
|
|
549
556
|
normalizedAuthServerUrl = (0, _urlutilsts.normalizeUrl)(authServerUrl);
|
|
550
557
|
localWellKnownUrl = (0, _urlutilsts.joinWellKnown)(normalizedAuthServerUrl, '/.well-known/oauth-authorization-server');
|
|
551
558
|
return [
|
|
552
559
|
4,
|
|
553
|
-
|
|
560
|
+
(0, _discoveryfetchts.discoveryFetch)(localWellKnownUrl, {
|
|
554
561
|
method: 'GET',
|
|
555
562
|
headers: {
|
|
556
563
|
Accept: 'application/json',
|
|
557
564
|
Connection: 'close'
|
|
558
565
|
}
|
|
566
|
+
}, 'authorization server metadata (path-local)', {
|
|
567
|
+
allowLoopback: allowLoopback
|
|
559
568
|
})
|
|
560
569
|
];
|
|
561
|
-
case
|
|
570
|
+
case 2:
|
|
562
571
|
localResponse = _state.sent();
|
|
563
572
|
if (!localResponse.ok) return [
|
|
564
573
|
3,
|
|
565
|
-
|
|
574
|
+
4
|
|
566
575
|
];
|
|
567
576
|
return [
|
|
568
577
|
4,
|
|
569
|
-
|
|
578
|
+
(0, _discoveryfetchts.readDiscoveryJson)(localResponse, 'authorization server metadata (path-local)')
|
|
570
579
|
];
|
|
571
|
-
case
|
|
580
|
+
case 3:
|
|
572
581
|
return [
|
|
573
582
|
2,
|
|
574
583
|
_state.sent()
|
|
575
584
|
];
|
|
576
|
-
case
|
|
585
|
+
case 4:
|
|
577
586
|
origin = getOrigin(normalizedAuthServerUrl);
|
|
578
587
|
wellKnownUrl = "".concat(origin, "/.well-known/oauth-authorization-server");
|
|
579
588
|
return [
|
|
580
589
|
4,
|
|
581
|
-
|
|
590
|
+
(0, _discoveryfetchts.discoveryFetch)(wellKnownUrl, {
|
|
582
591
|
method: 'GET',
|
|
583
592
|
headers: {
|
|
584
593
|
Accept: 'application/json',
|
|
585
594
|
Connection: 'close'
|
|
586
595
|
}
|
|
596
|
+
}, 'authorization server metadata', {
|
|
597
|
+
allowLoopback: allowLoopback
|
|
587
598
|
})
|
|
588
599
|
];
|
|
589
|
-
case
|
|
600
|
+
case 5:
|
|
590
601
|
response = _state.sent();
|
|
591
602
|
if (!response.ok) {
|
|
592
603
|
return [
|
|
@@ -596,26 +607,26 @@ function discoverAuthorizationServerMetadata(authServerUrl) {
|
|
|
596
607
|
}
|
|
597
608
|
return [
|
|
598
609
|
4,
|
|
599
|
-
|
|
610
|
+
(0, _discoveryfetchts.readDiscoveryJson)(response, 'authorization server metadata')
|
|
600
611
|
];
|
|
601
|
-
case
|
|
612
|
+
case 6:
|
|
602
613
|
return [
|
|
603
614
|
2,
|
|
604
615
|
_state.sent()
|
|
605
616
|
];
|
|
606
|
-
case
|
|
617
|
+
case 7:
|
|
607
618
|
_error = _state.sent();
|
|
608
619
|
return [
|
|
609
620
|
2,
|
|
610
621
|
null
|
|
611
622
|
];
|
|
612
|
-
case
|
|
623
|
+
case 8:
|
|
613
624
|
return [
|
|
614
625
|
2
|
|
615
626
|
];
|
|
616
627
|
}
|
|
617
628
|
});
|
|
618
|
-
})();
|
|
629
|
+
}).apply(this, arguments);
|
|
619
630
|
}
|
|
620
631
|
function discoverAuthorizationServerIssuer(resourceUrl) {
|
|
621
632
|
return _async_to_generator(function() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/rfc9728-discovery.ts"],"sourcesContent":["/**\n * RFC 9728 Protected Resource Metadata Discovery\n * Probes .well-known/oauth-protected-resource endpoint\n */\n\nimport { joinWellKnown, normalizeUrl } from '../lib/url-utils.ts';\nimport type { AuthorizationServerMetadata, ProtectedResourceMetadata } from './types.ts';\n\n/**\n * Extract origin (protocol + host) from a URL\n * @param url - Full URL that may include a path\n * @returns Origin (e.g., \"https://example.com\") or original string if invalid URL\n *\n * @example\n * getOrigin('https://example.com/mcp') // → 'https://example.com'\n * getOrigin('http://localhost:9999/api/v1/mcp') // → 'http://localhost:9999'\n */\nfunction getOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n // Invalid URL - return as-is for graceful degradation\n return url;\n }\n}\n\n/**\n * Extract path from a URL (without origin)\n * @param url - Full URL\n * @returns Path component (e.g., \"/mcp\", \"/api/v1/mcp\") or empty string if no path\n */\nfunction getPath(url: string): string {\n try {\n const parsed = new URL(url);\n // pathname includes leading slash, e.g., \"/mcp\"\n return parsed.pathname === '/' ? '' : parsed.pathname;\n } catch {\n return '';\n }\n}\n\n/**\n * Normalize a resource URL by stripping query/hash and trailing slashes.\n */\n/**\n * Discover OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Probes .well-known/oauth-protected-resource endpoint\n *\n * Discovery Strategy:\n * 1. Try origin root: {origin}/.well-known/oauth-protected-resource\n * 2. If 404, try sub-path: {origin}/.well-known/oauth-protected-resource{path}\n *\n * @param resourceUrl - URL of the protected resource (e.g., https://ai.todoist.net/mcp)\n * @returns ProtectedResourceMetadata if discovered, null otherwise\n *\n * @example\n * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com\n * const metadata = await discoverProtectedResourceMetadata('https://ai.todoist.net/mcp');\n * // Returns: { resource: \"https://ai.todoist.net/mcp\", authorization_servers: [\"https://todoist.com\"] }\n */\nexport async function discoverProtectedResourceMetadata(resourceUrl: string): Promise<ProtectedResourceMetadata | null> {\n try {\n const normalizedResourceUrl = normalizeUrl(resourceUrl);\n const headerMetadata = await discoverProtectedResourceMetadataFromHeader(normalizedResourceUrl);\n if (headerMetadata) return headerMetadata;\n\n // Strategy 0: Try path-local well-known (supports path-prefixed deployments like /outlook)\n const localWellKnownUrl = joinWellKnown(normalizedResourceUrl, '/.well-known/oauth-protected-resource');\n try {\n const response = await fetch(localWellKnownUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n if (response.ok) {\n return (await response.json()) as ProtectedResourceMetadata;\n }\n } catch {\n // Continue to origin-based discovery\n }\n\n const origin = getOrigin(normalizedResourceUrl);\n const path = getPath(normalizedResourceUrl);\n\n // Strategy 1: Try root location (REQUIRED by RFC 9728)\n const rootUrl = `${origin}/.well-known/oauth-protected-resource`;\n\n try {\n const response = await fetch(rootUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (response.ok) {\n const metadata = (await response.json()) as ProtectedResourceMetadata;\n // Check if the discovered resource matches what we're looking for\n if (metadata.resource === normalizedResourceUrl) {\n return metadata;\n }\n // If there's no path component, return root metadata\n // (e.g., looking for http://example.com and found it)\n if (!path) {\n return metadata;\n }\n // If requested URL starts with metadata.resource, the root metadata applies to sub-paths\n // (e.g., looking for http://example.com/api/v1/mcp, found http://example.com)\n if (normalizedResourceUrl.startsWith(metadata.resource)) {\n // Still try sub-path location to see if there's more specific metadata\n // But save root metadata as fallback\n const rootMetadata = metadata;\n\n // Try sub-path location for more specific metadata\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n try {\n const subPathResponse = await fetch(subPathUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n if (subPathResponse.ok) {\n return (await subPathResponse.json()) as ProtectedResourceMetadata;\n }\n } catch {\n // Sub-path failed, use root metadata\n }\n\n // Return root metadata as it applies to this resource\n return rootMetadata;\n }\n // Otherwise, try sub-path location before giving up\n }\n } catch {\n // Continue to sub-path location\n }\n\n // Strategy 2: Try sub-path location (MCP spec extension)\n // Only try if there's a path component\n if (path) {\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n\n try {\n const response = await fetch(subPathUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (response.ok) {\n return (await response.json()) as ProtectedResourceMetadata;\n }\n } catch {\n // Fall through to return null\n }\n }\n\n // Neither location found or resource didn't match\n return null;\n } catch (_error) {\n // Network error, invalid URL, or other failure\n return null;\n }\n}\n\nasync function discoverProtectedResourceMetadataFromHeader(resourceUrl: string): Promise<ProtectedResourceMetadata | null> {\n try {\n const response = await fetch(resourceUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n let header = response.headers.get('www-authenticate');\n if (!header) {\n const postResponse = await fetch(resourceUrl, {\n method: 'POST',\n headers: { Accept: 'application/json', Connection: 'close', 'Content-Type': 'application/json' },\n body: '{}',\n });\n header = postResponse.headers.get('www-authenticate');\n }\n\n if (!header) return null;\n\n const match = header.match(/resource_metadata=\"([^\"]+)\"/i);\n if (!match || !match[1]) return null;\n\n const metadataUrl = match[1];\n const metadataResponse = await fetch(metadataUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (!metadataResponse.ok) {\n return null;\n }\n\n return (await metadataResponse.json()) as ProtectedResourceMetadata;\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Discover OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Probes .well-known/oauth-authorization-server endpoint\n *\n * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery)\n * @returns AuthorizationServerMetadata if discovered, null otherwise\n *\n * @example\n * const metadata = await discoverAuthorizationServerMetadata('https://todoist.com');\n * // Returns: { issuer: \"https://todoist.com\", authorization_endpoint: \"...\", ... }\n */\nexport async function discoverAuthorizationServerMetadata(authServerUrl: string): Promise<AuthorizationServerMetadata | null> {\n try {\n const normalizedAuthServerUrl = normalizeUrl(authServerUrl);\n const localWellKnownUrl = joinWellKnown(normalizedAuthServerUrl, '/.well-known/oauth-authorization-server');\n const localResponse = await fetch(localWellKnownUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (localResponse.ok) {\n return (await localResponse.json()) as AuthorizationServerMetadata;\n }\n\n const origin = getOrigin(normalizedAuthServerUrl);\n const wellKnownUrl = `${origin}/.well-known/oauth-authorization-server`;\n\n const response = await fetch(wellKnownUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n if (!response.ok) {\n return null;\n }\n\n return (await response.json()) as AuthorizationServerMetadata;\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Discover OAuth Authorization Server Issuer from resource response (RFC 9207)\n *\n * @param resourceUrl - URL of the protected resource\n * @returns Issuer URL if present in WWW-Authenticate header, null otherwise\n */\nexport async function discoverAuthorizationServerIssuer(resourceUrl: string): Promise<string | null> {\n try {\n const response = await fetch(resourceUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n const header = response.headers.get('www-authenticate');\n if (!header) return null;\n\n const match = header.match(/(?:authorization_server|issuer)=\"([^\"]+)\"/i);\n if (!match) return null;\n\n return match[1] ?? null;\n } catch (_error) {\n return null;\n }\n}\n"],"names":["discoverAuthorizationServerIssuer","discoverAuthorizationServerMetadata","discoverProtectedResourceMetadata","getOrigin","url","URL","origin","getPath","parsed","pathname","resourceUrl","normalizedResourceUrl","headerMetadata","localWellKnownUrl","response","path","rootUrl","metadata","rootMetadata","subPathUrl","subPathResponse","_error","normalizeUrl","discoverProtectedResourceMetadataFromHeader","joinWellKnown","fetch","method","headers","Accept","Connection","ok","json","resource","startsWith","header","postResponse","match","metadataUrl","metadataResponse","get","body","authServerUrl","normalizedAuthServerUrl","localResponse","wellKnownUrl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/rfc9728-discovery.ts"],"sourcesContent":["/**\n * RFC 9728 Protected Resource Metadata Discovery\n * Probes .well-known/oauth-protected-resource endpoint\n */\n\nimport { joinWellKnown, normalizeUrl } from '../lib/url-utils.ts';\nimport { discoveryFetch, isLoopbackUrl, readDiscoveryJson } from './discovery-fetch.ts';\nimport type { AuthorizationServerMetadata, ProtectedResourceMetadata } from './types.ts';\n\n/** Returns `url`'s origin (protocol + host), or the original string if it doesn't parse. */\nfunction getOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return url;\n }\n}\n\n/** Returns `url`'s pathname, or `''` for the root path or an unparseable URL. */\nfunction getPath(url: string): string {\n try {\n const parsed = new URL(url);\n return parsed.pathname === '/' ? '' : parsed.pathname;\n } catch {\n return '';\n }\n}\n\n/**\n * Discovers RFC 9728 Protected Resource Metadata: the `WWW-Authenticate`\n * header first, then `.well-known/oauth-protected-resource` at the origin root and, for a path-prefixed resource, its sub-path variant.\n *\n * @param resourceUrl - URL of the protected resource (e.g. `https://ai.todoist.net/mcp`).\n * @returns Discovered metadata, or `null` if none is found.\n */\nexport async function discoverProtectedResourceMetadata(resourceUrl: string): Promise<ProtectedResourceMetadata | null> {\n try {\n const normalizedResourceUrl = normalizeUrl(resourceUrl);\n // resourceUrl is the server the caller configured (never remote-supplied);\n // its loopback-ness is the trust signal every fetch below relies on.\n const allowLoopback = isLoopbackUrl(normalizedResourceUrl);\n const headerMetadata = await discoverProtectedResourceMetadataFromHeader(normalizedResourceUrl, allowLoopback);\n if (headerMetadata) return headerMetadata;\n\n // Strategy 0: Try path-local well-known (supports path-prefixed deployments like /outlook)\n const localWellKnownUrl = joinWellKnown(normalizedResourceUrl, '/.well-known/oauth-protected-resource');\n try {\n const response = await discoveryFetch(\n localWellKnownUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'protected resource metadata (path-local)',\n { allowLoopback }\n );\n if (response.ok) {\n return await readDiscoveryJson<ProtectedResourceMetadata>(response, 'protected resource metadata (path-local)');\n }\n } catch {\n // Continue to origin-based discovery\n }\n\n const origin = getOrigin(normalizedResourceUrl);\n const path = getPath(normalizedResourceUrl);\n\n // Strategy 1: Try root location (REQUIRED by RFC 9728)\n const rootUrl = `${origin}/.well-known/oauth-protected-resource`;\n\n try {\n const response = await discoveryFetch(\n rootUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'protected resource metadata (root)',\n { allowLoopback }\n );\n\n if (response.ok) {\n const metadata = await readDiscoveryJson<ProtectedResourceMetadata>(response, 'protected resource metadata (root)');\n // Check if the discovered resource matches what we're looking for\n if (metadata.resource === normalizedResourceUrl) {\n return metadata;\n }\n // If there's no path component, return root metadata\n // (e.g., looking for http://example.com and found it)\n if (!path) {\n return metadata;\n }\n // If requested URL starts with metadata.resource, the root metadata applies to sub-paths\n // (e.g., looking for http://example.com/api/v1/mcp, found http://example.com)\n if (normalizedResourceUrl.startsWith(metadata.resource)) {\n // Still try sub-path location to see if there's more specific metadata\n // But save root metadata as fallback\n const rootMetadata = metadata;\n\n // Try sub-path location for more specific metadata\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n try {\n const subPathResponse = await discoveryFetch(\n subPathUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'protected resource metadata (sub-path)',\n { allowLoopback }\n );\n if (subPathResponse.ok) {\n return await readDiscoveryJson<ProtectedResourceMetadata>(subPathResponse, 'protected resource metadata (sub-path)');\n }\n } catch {\n // Sub-path failed, use root metadata\n }\n\n // Return root metadata as it applies to this resource\n return rootMetadata;\n }\n // Otherwise, try sub-path location before giving up\n }\n } catch {\n // Continue to sub-path location\n }\n\n // Strategy 2: Try sub-path location (MCP spec extension)\n // Only try if there's a path component\n if (path) {\n const subPathUrl = `${origin}/.well-known/oauth-protected-resource${path}`;\n\n try {\n const response = await discoveryFetch(\n subPathUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'protected resource metadata (sub-path)',\n { allowLoopback }\n );\n\n if (response.ok) {\n return await readDiscoveryJson<ProtectedResourceMetadata>(response, 'protected resource metadata (sub-path)');\n }\n } catch {\n // Fall through to return null\n }\n }\n\n // Neither location found or resource didn't match\n return null;\n } catch (_error) {\n // Network error, invalid URL, or other failure\n return null;\n }\n}\n\nasync function discoverProtectedResourceMetadataFromHeader(resourceUrl: string, allowLoopback: boolean): Promise<ProtectedResourceMetadata | null> {\n try {\n const response = await fetch(resourceUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n let header = response.headers.get('www-authenticate');\n if (!header) {\n const postResponse = await fetch(resourceUrl, {\n method: 'POST',\n headers: { Accept: 'application/json', Connection: 'close', 'Content-Type': 'application/json' },\n body: '{}',\n });\n header = postResponse.headers.get('www-authenticate');\n }\n\n if (!header) return null;\n\n const match = header.match(/resource_metadata=\"([^\"]+)\"/i);\n if (!match || !match[1]) return null;\n\n // metadataUrl is remote-server-chosen (the primary SSRF vector here);\n // allowLoopback reflects trust in resourceUrl, not in metadataUrl.\n const metadataUrl = match[1];\n const metadataResponse = await discoveryFetch(\n metadataUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'resource_metadata URL',\n { allowLoopback }\n );\n\n if (!metadataResponse.ok) {\n return null;\n }\n\n return await readDiscoveryJson<ProtectedResourceMetadata>(metadataResponse, 'resource_metadata URL');\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Discovers RFC 8414 Authorization Server Metadata at\n * `.well-known/oauth-authorization-server`, path-local variant first.\n *\n * @param authServerUrl - URL of the authorization server (typically from RFC 9728 discovery).\n * @param options.allowLoopback - Loopback trust grant computed from the server the caller is actually talking to, never from `authServerUrl` itself. Defaults to `false`.\n * @returns Discovered metadata, or `null` if none is found.\n */\nexport async function discoverAuthorizationServerMetadata(authServerUrl: string, options: { allowLoopback?: boolean } = {}): Promise<AuthorizationServerMetadata | null> {\n const { allowLoopback = false } = options;\n try {\n const normalizedAuthServerUrl = normalizeUrl(authServerUrl);\n const localWellKnownUrl = joinWellKnown(normalizedAuthServerUrl, '/.well-known/oauth-authorization-server');\n const localResponse = await discoveryFetch(\n localWellKnownUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'authorization server metadata (path-local)',\n { allowLoopback }\n );\n\n if (localResponse.ok) {\n return await readDiscoveryJson<AuthorizationServerMetadata>(localResponse, 'authorization server metadata (path-local)');\n }\n\n const origin = getOrigin(normalizedAuthServerUrl);\n const wellKnownUrl = `${origin}/.well-known/oauth-authorization-server`;\n\n const response = await discoveryFetch(\n wellKnownUrl,\n {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n },\n 'authorization server metadata',\n { allowLoopback }\n );\n\n if (!response.ok) {\n return null;\n }\n\n return await readDiscoveryJson<AuthorizationServerMetadata>(response, 'authorization server metadata');\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Discover OAuth Authorization Server Issuer from resource response (RFC 9207)\n *\n * @param resourceUrl - URL of the protected resource\n * @returns Issuer URL if present in WWW-Authenticate header, null otherwise\n */\nexport async function discoverAuthorizationServerIssuer(resourceUrl: string): Promise<string | null> {\n try {\n const response = await fetch(resourceUrl, {\n method: 'GET',\n headers: { Accept: 'application/json', Connection: 'close' },\n });\n\n const header = response.headers.get('www-authenticate');\n if (!header) return null;\n\n const match = header.match(/(?:authorization_server|issuer)=\"([^\"]+)\"/i);\n if (!match) return null;\n\n return match[1] ?? null;\n } catch (_error) {\n return null;\n }\n}\n"],"names":["discoverAuthorizationServerIssuer","discoverAuthorizationServerMetadata","discoverProtectedResourceMetadata","getOrigin","url","URL","origin","getPath","parsed","pathname","resourceUrl","normalizedResourceUrl","allowLoopback","headerMetadata","localWellKnownUrl","response","path","rootUrl","metadata","rootMetadata","subPathUrl","subPathResponse","_error","normalizeUrl","isLoopbackUrl","discoverProtectedResourceMetadataFromHeader","joinWellKnown","discoveryFetch","method","headers","Accept","Connection","ok","readDiscoveryJson","resource","startsWith","header","postResponse","match","metadataUrl","metadataResponse","fetch","get","body","authServerUrl","options","normalizedAuthServerUrl","localResponse","wellKnownUrl"],"mappings":"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"}
|
|
@@ -113,6 +113,14 @@ export interface DcrRegistrationOptions {
|
|
|
113
113
|
clientName?: string;
|
|
114
114
|
/** Redirect URI for OAuth callback */
|
|
115
115
|
redirectUri?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Loopback trust grant for the registration_endpoint fetch (SSRF
|
|
118
|
+
* mitigation - see `src/auth/discovery-fetch.ts`). Compute this from the
|
|
119
|
+
* MCP server the caller is actually talking to, never from
|
|
120
|
+
* `registrationEndpoint` itself (which is typically sourced from
|
|
121
|
+
* remote-controlled AS metadata). Defaults to `false`.
|
|
122
|
+
*/
|
|
123
|
+
allowLoopback?: boolean;
|
|
116
124
|
}
|
|
117
125
|
/**
|
|
118
126
|
* Options for OAuth authorization flow
|
|
@@ -134,4 +142,12 @@ export interface OAuthFlowOptions {
|
|
|
134
142
|
timeout?: number;
|
|
135
143
|
/** Optional logger for debug output (defaults to singleton logger) */
|
|
136
144
|
logger?: import('../utils/logger.js').Logger;
|
|
145
|
+
/**
|
|
146
|
+
* Loopback trust grant for the token endpoint fetch (SSRF mitigation - see
|
|
147
|
+
* `src/auth/discovery-fetch.ts`). Compute this from the MCP server the
|
|
148
|
+
* caller is actually talking to, never from `tokenEndpoint` itself (which
|
|
149
|
+
* is typically sourced from remote-controlled AS metadata). Defaults to
|
|
150
|
+
* `false`.
|
|
151
|
+
*/
|
|
152
|
+
allowLoopback?: boolean;
|
|
137
153
|
}
|
package/dist/cjs/auth/types.d.ts
CHANGED
|
@@ -113,6 +113,14 @@ export interface DcrRegistrationOptions {
|
|
|
113
113
|
clientName?: string;
|
|
114
114
|
/** Redirect URI for OAuth callback */
|
|
115
115
|
redirectUri?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Loopback trust grant for the registration_endpoint fetch (SSRF
|
|
118
|
+
* mitigation - see `src/auth/discovery-fetch.ts`). Compute this from the
|
|
119
|
+
* MCP server the caller is actually talking to, never from
|
|
120
|
+
* `registrationEndpoint` itself (which is typically sourced from
|
|
121
|
+
* remote-controlled AS metadata). Defaults to `false`.
|
|
122
|
+
*/
|
|
123
|
+
allowLoopback?: boolean;
|
|
116
124
|
}
|
|
117
125
|
/**
|
|
118
126
|
* Options for OAuth authorization flow
|
|
@@ -134,4 +142,12 @@ export interface OAuthFlowOptions {
|
|
|
134
142
|
timeout?: number;
|
|
135
143
|
/** Optional logger for debug output (defaults to singleton logger) */
|
|
136
144
|
logger?: import('../utils/logger.js').Logger;
|
|
145
|
+
/**
|
|
146
|
+
* Loopback trust grant for the token endpoint fetch (SSRF mitigation - see
|
|
147
|
+
* `src/auth/discovery-fetch.ts`). Compute this from the MCP server the
|
|
148
|
+
* caller is actually talking to, never from `tokenEndpoint` itself (which
|
|
149
|
+
* is typically sourced from remote-controlled AS metadata). Defaults to
|
|
150
|
+
* `false`.
|
|
151
|
+
*/
|
|
152
|
+
allowLoopback?: boolean;
|
|
137
153
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/types.ts"],"sourcesContent":["/**\n * Shared types for OAuth and DCR authentication\n */\n\n/**\n * OAuth callback result from authorization server\n */\nexport interface CallbackResult {\n /** Authorization code from OAuth server */\n code: string;\n /** State parameter for CSRF protection */\n state?: string;\n}\n\n/**\n * PKCE (Proof Key for Code Exchange) parameters (RFC 7636)\n * Used to secure OAuth 2.0 authorization code flow for public clients\n */\nexport interface PkceParams {\n /** Code verifier - cryptographically random string (43-128 characters) */\n codeVerifier: string;\n /** Code challenge - derived from code verifier using challenge method */\n codeChallenge: string;\n /** Code challenge method - S256 (SHA-256) or plain */\n codeChallengeMethod: 'S256' | 'plain';\n}\n\n/**\n * OAuth token set with access and refresh tokens\n */\nexport interface TokenSet {\n /** Access token for API requests */\n accessToken: string;\n /** Refresh token for obtaining new access tokens */\n refreshToken: string;\n /** Timestamp when access token expires (milliseconds since epoch) */\n expiresAt: number;\n /** Scopes granted for this token set */\n scopes?: string[];\n /** Client ID used for DCR registration (stored for future use) */\n clientId?: string;\n /** Client secret used for DCR registration (stored for future use) */\n clientSecret?: string;\n}\n\n/**\n * OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Response from .well-known/oauth-protected-resource endpoint\n */\nexport interface ProtectedResourceMetadata {\n /** The protected resource identifier */\n resource: string;\n /** List of authorization server URLs that can issue tokens for this resource */\n authorization_servers: string[];\n /** Optional list of scopes supported by this resource */\n scopes_supported?: string[];\n /** Optional list of bearer token methods supported (header, query, body) */\n bearer_methods_supported?: string[];\n}\n\n/**\n * OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Response from .well-known/oauth-authorization-server endpoint\n */\nexport interface AuthorizationServerMetadata {\n /** The authorization server's issuer identifier */\n issuer?: string;\n /** URL of the authorization endpoint */\n authorization_endpoint?: string;\n /** URL of the token endpoint */\n token_endpoint?: string;\n /** URL of the client registration endpoint (DCR - RFC 7591) */\n registration_endpoint?: string;\n /** URL of the token introspection endpoint */\n introspection_endpoint?: string;\n /** List of OAuth scopes supported by the authorization server */\n scopes_supported?: string[];\n /** Response types supported (code, token, etc.) */\n response_types_supported?: string[];\n /** Grant types supported (authorization_code, refresh_token, etc.) */\n grant_types_supported?: string[];\n /** Token endpoint authentication methods supported */\n token_endpoint_auth_methods_supported?: string[];\n}\n\n/**\n * OAuth server capabilities discovered from .well-known endpoint\n */\nexport interface AuthCapabilities {\n /** Whether the server supports Dynamic Client Registration (RFC 7591) */\n supportsDcr: boolean;\n /** DCR client registration endpoint */\n registrationEndpoint?: string;\n /** OAuth authorization endpoint */\n authorizationEndpoint?: string;\n /** OAuth token endpoint */\n tokenEndpoint?: string;\n /** Token introspection endpoint */\n introspectionEndpoint?: string;\n /** Supported OAuth scopes */\n scopes?: string[];\n}\n\n/**\n * Client credentials from DCR registration\n */\nexport interface ClientCredentials {\n /** OAuth client ID */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n /** Timestamp when client was registered */\n issuedAt?: number;\n}\n\n/**\n * Options for DCR client registration\n */\nexport interface DcrRegistrationOptions {\n /** Client name to register */\n clientName?: string;\n /** Redirect URI for OAuth callback */\n redirectUri?: string;\n}\n\n/**\n * Options for OAuth authorization flow\n */\nexport interface OAuthFlowOptions {\n /** Port for OAuth callback listener (required - use get-port to find available port) */\n port: number;\n /** Redirect URI for OAuth callback (optional - will be built from port if not provided) */\n redirectUri?: string;\n /** OAuth scopes to request */\n scopes?: string[];\n /** Resource parameter (RFC 8707) - target resource server identifier */\n resource?: string;\n /** Enable PKCE (RFC 7636) - recommended for all clients, required for public clients */\n pkce?: boolean;\n /** Headless mode (don't open browser) */\n headless?: boolean;\n /** Timeout for callback (milliseconds) */\n timeout?: number;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: import('../utils/logger.ts').Logger;\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GAED;;CAEC"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/types.ts"],"sourcesContent":["/**\n * Shared types for OAuth and DCR authentication\n */\n\n/**\n * OAuth callback result from authorization server\n */\nexport interface CallbackResult {\n /** Authorization code from OAuth server */\n code: string;\n /** State parameter for CSRF protection */\n state?: string;\n}\n\n/**\n * PKCE (Proof Key for Code Exchange) parameters (RFC 7636)\n * Used to secure OAuth 2.0 authorization code flow for public clients\n */\nexport interface PkceParams {\n /** Code verifier - cryptographically random string (43-128 characters) */\n codeVerifier: string;\n /** Code challenge - derived from code verifier using challenge method */\n codeChallenge: string;\n /** Code challenge method - S256 (SHA-256) or plain */\n codeChallengeMethod: 'S256' | 'plain';\n}\n\n/**\n * OAuth token set with access and refresh tokens\n */\nexport interface TokenSet {\n /** Access token for API requests */\n accessToken: string;\n /** Refresh token for obtaining new access tokens */\n refreshToken: string;\n /** Timestamp when access token expires (milliseconds since epoch) */\n expiresAt: number;\n /** Scopes granted for this token set */\n scopes?: string[];\n /** Client ID used for DCR registration (stored for future use) */\n clientId?: string;\n /** Client secret used for DCR registration (stored for future use) */\n clientSecret?: string;\n}\n\n/**\n * OAuth 2.0 Protected Resource Metadata (RFC 9728)\n * Response from .well-known/oauth-protected-resource endpoint\n */\nexport interface ProtectedResourceMetadata {\n /** The protected resource identifier */\n resource: string;\n /** List of authorization server URLs that can issue tokens for this resource */\n authorization_servers: string[];\n /** Optional list of scopes supported by this resource */\n scopes_supported?: string[];\n /** Optional list of bearer token methods supported (header, query, body) */\n bearer_methods_supported?: string[];\n}\n\n/**\n * OAuth 2.0 Authorization Server Metadata (RFC 8414)\n * Response from .well-known/oauth-authorization-server endpoint\n */\nexport interface AuthorizationServerMetadata {\n /** The authorization server's issuer identifier */\n issuer?: string;\n /** URL of the authorization endpoint */\n authorization_endpoint?: string;\n /** URL of the token endpoint */\n token_endpoint?: string;\n /** URL of the client registration endpoint (DCR - RFC 7591) */\n registration_endpoint?: string;\n /** URL of the token introspection endpoint */\n introspection_endpoint?: string;\n /** List of OAuth scopes supported by the authorization server */\n scopes_supported?: string[];\n /** Response types supported (code, token, etc.) */\n response_types_supported?: string[];\n /** Grant types supported (authorization_code, refresh_token, etc.) */\n grant_types_supported?: string[];\n /** Token endpoint authentication methods supported */\n token_endpoint_auth_methods_supported?: string[];\n}\n\n/**\n * OAuth server capabilities discovered from .well-known endpoint\n */\nexport interface AuthCapabilities {\n /** Whether the server supports Dynamic Client Registration (RFC 7591) */\n supportsDcr: boolean;\n /** DCR client registration endpoint */\n registrationEndpoint?: string;\n /** OAuth authorization endpoint */\n authorizationEndpoint?: string;\n /** OAuth token endpoint */\n tokenEndpoint?: string;\n /** Token introspection endpoint */\n introspectionEndpoint?: string;\n /** Supported OAuth scopes */\n scopes?: string[];\n}\n\n/**\n * Client credentials from DCR registration\n */\nexport interface ClientCredentials {\n /** OAuth client ID */\n clientId: string;\n /** OAuth client secret */\n clientSecret: string;\n /** Timestamp when client was registered */\n issuedAt?: number;\n}\n\n/**\n * Options for DCR client registration\n */\nexport interface DcrRegistrationOptions {\n /** Client name to register */\n clientName?: string;\n /** Redirect URI for OAuth callback */\n redirectUri?: string;\n /**\n * Loopback trust grant for the registration_endpoint fetch (SSRF\n * mitigation - see `src/auth/discovery-fetch.ts`). Compute this from the\n * MCP server the caller is actually talking to, never from\n * `registrationEndpoint` itself (which is typically sourced from\n * remote-controlled AS metadata). Defaults to `false`.\n */\n allowLoopback?: boolean;\n}\n\n/**\n * Options for OAuth authorization flow\n */\nexport interface OAuthFlowOptions {\n /** Port for OAuth callback listener (required - use get-port to find available port) */\n port: number;\n /** Redirect URI for OAuth callback (optional - will be built from port if not provided) */\n redirectUri?: string;\n /** OAuth scopes to request */\n scopes?: string[];\n /** Resource parameter (RFC 8707) - target resource server identifier */\n resource?: string;\n /** Enable PKCE (RFC 7636) - recommended for all clients, required for public clients */\n pkce?: boolean;\n /** Headless mode (don't open browser) */\n headless?: boolean;\n /** Timeout for callback (milliseconds) */\n timeout?: number;\n /** Optional logger for debug output (defaults to singleton logger) */\n logger?: import('../utils/logger.ts').Logger;\n /**\n * Loopback trust grant for the token endpoint fetch (SSRF mitigation - see\n * `src/auth/discovery-fetch.ts`). Compute this from the MCP server the\n * caller is actually talking to, never from `tokenEndpoint` itself (which\n * is typically sourced from remote-controlled AS metadata). Defaults to\n * `false`.\n */\n allowLoopback?: boolean;\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GAED;;CAEC"}
|
|
@@ -58,12 +58,11 @@ export declare class DcrAuthenticator {
|
|
|
58
58
|
* Self-hosted servers manage their own token storage via /oauth/verify
|
|
59
59
|
*/
|
|
60
60
|
private ensureAuthenticatedSelfHosted;
|
|
61
|
-
/**
|
|
62
|
-
* Handle authentication for external OAuth providers (original implementation)
|
|
63
|
-
*/
|
|
61
|
+
/** Handles authentication for external (non-self-hosted) OAuth providers. */
|
|
64
62
|
private ensureAuthenticatedExternal;
|
|
65
63
|
/**
|
|
66
|
-
*
|
|
64
|
+
* Refreshes an access token using a refresh token.
|
|
65
|
+
* @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.
|
|
67
66
|
*/
|
|
68
67
|
private refreshTokens;
|
|
69
68
|
/**
|
|
@@ -58,12 +58,11 @@ export declare class DcrAuthenticator {
|
|
|
58
58
|
* Self-hosted servers manage their own token storage via /oauth/verify
|
|
59
59
|
*/
|
|
60
60
|
private ensureAuthenticatedSelfHosted;
|
|
61
|
-
/**
|
|
62
|
-
* Handle authentication for external OAuth providers (original implementation)
|
|
63
|
-
*/
|
|
61
|
+
/** Handles authentication for external (non-self-hosted) OAuth providers. */
|
|
64
62
|
private ensureAuthenticatedExternal;
|
|
65
63
|
/**
|
|
66
|
-
*
|
|
64
|
+
* Refreshes an access token using a refresh token.
|
|
65
|
+
* @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.
|
|
67
66
|
*/
|
|
68
67
|
private refreshTokens;
|
|
69
68
|
/**
|
|
@@ -15,6 +15,7 @@ var _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
|
|
|
15
15
|
var _fs = /*#__PURE__*/ _interop_require_wildcard(require("fs"));
|
|
16
16
|
var _keyv = /*#__PURE__*/ _interop_require_default(require("keyv"));
|
|
17
17
|
var _keyvfile = require("keyv-file");
|
|
18
|
+
var _discoveryfetchts = require("../auth/discovery-fetch.js");
|
|
18
19
|
var _interactiveoauthflowts = require("../auth/interactive-oauth-flow.js");
|
|
19
20
|
var _loggerts = require("../utils/logger.js");
|
|
20
21
|
var _dynamicclientregistrarts = require("./dynamic-client-registrar.js");
|
|
@@ -291,10 +292,13 @@ var DcrAuthenticator = /*#__PURE__*/ function() {
|
|
|
291
292
|
* Self-hosted servers manage their own token storage via /oauth/verify
|
|
292
293
|
*/ _proto.ensureAuthenticatedSelfHosted = function ensureAuthenticatedSelfHosted(baseUrl, capabilities) {
|
|
293
294
|
return _async_to_generator(function() {
|
|
294
|
-
var dcrTokenKey, tokens, verifyUrl, verifyResponse, verifyData, _error, port, client, flowOptions, verifyUrl1, verifyResponse1, verifyData1, error;
|
|
295
|
+
var allowLoopback, dcrTokenKey, tokens, verifyUrl, verifyResponse, verifyData, _error, port, client, flowOptions, verifyUrl1, verifyResponse1, verifyData1, error;
|
|
295
296
|
return _ts_generator(this, function(_state) {
|
|
296
297
|
switch(_state.label){
|
|
297
298
|
case 0:
|
|
299
|
+
// Loopback trust for every discovery-derived fetch below, computed from
|
|
300
|
+
// the server we're talking to, never from capabilities' endpoints (see discovery-fetch.ts).
|
|
301
|
+
allowLoopback = (0, _discoveryfetchts.isLoopbackUrl)(baseUrl);
|
|
298
302
|
dcrTokenKey = "dcr-tokens:".concat(baseUrl);
|
|
299
303
|
return [
|
|
300
304
|
4,
|
|
@@ -378,7 +382,8 @@ var DcrAuthenticator = /*#__PURE__*/ function() {
|
|
|
378
382
|
return [
|
|
379
383
|
4,
|
|
380
384
|
this.dcrClient.registerClient(capabilities.registrationEndpoint, {
|
|
381
|
-
redirectUri: this.redirectUri
|
|
385
|
+
redirectUri: this.redirectUri,
|
|
386
|
+
allowLoopback: allowLoopback
|
|
382
387
|
})
|
|
383
388
|
];
|
|
384
389
|
case 10:
|
|
@@ -389,7 +394,8 @@ var DcrAuthenticator = /*#__PURE__*/ function() {
|
|
|
389
394
|
headless: this.headless,
|
|
390
395
|
redirectUri: this.redirectUri,
|
|
391
396
|
pkce: true,
|
|
392
|
-
logger: this.logger
|
|
397
|
+
logger: this.logger,
|
|
398
|
+
allowLoopback: allowLoopback
|
|
393
399
|
};
|
|
394
400
|
if (capabilities.scopes) {
|
|
395
401
|
flowOptions.scopes = capabilities.scopes;
|
|
@@ -458,14 +464,14 @@ var DcrAuthenticator = /*#__PURE__*/ function() {
|
|
|
458
464
|
});
|
|
459
465
|
}).call(this);
|
|
460
466
|
};
|
|
461
|
-
/**
|
|
462
|
-
* Handle authentication for external OAuth providers (original implementation)
|
|
463
|
-
*/ _proto.ensureAuthenticatedExternal = function ensureAuthenticatedExternal(baseUrl, capabilities) {
|
|
467
|
+
/** Handles authentication for external (non-self-hosted) OAuth providers. */ _proto.ensureAuthenticatedExternal = function ensureAuthenticatedExternal(baseUrl, capabilities) {
|
|
464
468
|
return _async_to_generator(function() {
|
|
465
|
-
var tokenKey, tokens, _error, port, client, flowOptions;
|
|
469
|
+
var allowLoopback, tokenKey, tokens, _error, port, client, flowOptions;
|
|
466
470
|
return _ts_generator(this, function(_state) {
|
|
467
471
|
switch(_state.label){
|
|
468
472
|
case 0:
|
|
473
|
+
// See ensureAuthenticatedSelfHosted - same loopback trust rule.
|
|
474
|
+
allowLoopback = (0, _discoveryfetchts.isLoopbackUrl)(baseUrl);
|
|
469
475
|
tokenKey = "tokens:".concat(baseUrl);
|
|
470
476
|
return [
|
|
471
477
|
4,
|
|
@@ -492,7 +498,7 @@ var DcrAuthenticator = /*#__PURE__*/ function() {
|
|
|
492
498
|
]);
|
|
493
499
|
return [
|
|
494
500
|
4,
|
|
495
|
-
this.refreshTokens(tokens, capabilities.tokenEndpoint)
|
|
501
|
+
this.refreshTokens(tokens, capabilities.tokenEndpoint, allowLoopback)
|
|
496
502
|
];
|
|
497
503
|
case 3:
|
|
498
504
|
tokens = _state.sent();
|
|
@@ -543,7 +549,8 @@ var DcrAuthenticator = /*#__PURE__*/ function() {
|
|
|
543
549
|
return [
|
|
544
550
|
4,
|
|
545
551
|
this.dcrClient.registerClient(capabilities.registrationEndpoint, {
|
|
546
|
-
redirectUri: this.redirectUri
|
|
552
|
+
redirectUri: this.redirectUri,
|
|
553
|
+
allowLoopback: allowLoopback
|
|
547
554
|
})
|
|
548
555
|
];
|
|
549
556
|
case 9:
|
|
@@ -554,7 +561,8 @@ var DcrAuthenticator = /*#__PURE__*/ function() {
|
|
|
554
561
|
headless: this.headless,
|
|
555
562
|
redirectUri: this.redirectUri,
|
|
556
563
|
pkce: true,
|
|
557
|
-
logger: this.logger
|
|
564
|
+
logger: this.logger,
|
|
565
|
+
allowLoopback: allowLoopback
|
|
558
566
|
};
|
|
559
567
|
if (capabilities.scopes) {
|
|
560
568
|
flowOptions.scopes = capabilities.scopes;
|
|
@@ -582,8 +590,10 @@ var DcrAuthenticator = /*#__PURE__*/ function() {
|
|
|
582
590
|
}).call(this);
|
|
583
591
|
};
|
|
584
592
|
/**
|
|
585
|
-
*
|
|
593
|
+
* Refreshes an access token using a refresh token.
|
|
594
|
+
* @param allowLoopback - Loopback trust grant computed from the server actually being talked to (SSRF mitigation, see discovery-fetch.ts). Defaults to `false`.
|
|
586
595
|
*/ _proto.refreshTokens = function refreshTokens(tokens, tokenEndpoint) {
|
|
596
|
+
var allowLoopback = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false;
|
|
587
597
|
return _async_to_generator(function() {
|
|
588
598
|
return _ts_generator(this, function(_state) {
|
|
589
599
|
switch(_state.label){
|
|
@@ -599,7 +609,7 @@ var DcrAuthenticator = /*#__PURE__*/ function() {
|
|
|
599
609
|
}
|
|
600
610
|
return [
|
|
601
611
|
4,
|
|
602
|
-
this.oauthFlow.refreshTokens(tokenEndpoint, tokens.refreshToken, tokens.clientId, tokens.clientSecret)
|
|
612
|
+
this.oauthFlow.refreshTokens(tokenEndpoint, tokens.refreshToken, tokens.clientId, tokens.clientSecret, allowLoopback)
|
|
603
613
|
];
|
|
604
614
|
case 1:
|
|
605
615
|
return [
|