@atxp/client 0.3.0 → 0.4.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/dist/index.cjs +745 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +745 -142
- package/dist/index.js.map +1 -1
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js +369 -99
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js.map +1 -1
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js +18 -18
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js.map +1 -1
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/errors.js +162 -0
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/errors.js.map +1 -0
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/auth.js +86 -14
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/auth.js.map +1 -1
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js +47 -9
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js.map +1 -1
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js +64 -5
- package/dist/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js.map +1 -1
- package/dist/node_modules/eventsource-parser/dist/index.js +1 -1
- package/dist/node_modules/eventsource-parser/dist/index.js.map +1 -1
- package/dist/node_modules/zod/{dist/esm/v3 → v3}/ZodError.js +3 -2
- package/dist/node_modules/zod/v3/ZodError.js.map +1 -0
- package/dist/node_modules/zod/v3/errors.js.map +1 -0
- package/dist/node_modules/zod/v3/helpers/errorUtil.js.map +1 -0
- package/dist/node_modules/zod/v3/helpers/parseUtil.js.map +1 -0
- package/dist/node_modules/zod/v3/helpers/util.js.map +1 -0
- package/dist/node_modules/zod/{dist/esm/v3 → v3}/locales/en.js +2 -0
- package/dist/node_modules/zod/v3/locales/en.js.map +1 -0
- package/dist/node_modules/zod/{dist/esm/v3 → v3}/types.js +5 -2
- package/dist/node_modules/zod/v3/types.js.map +1 -0
- package/package.json +2 -2
- package/dist/node_modules/zod/dist/esm/v3/ZodError.js.map +0 -1
- package/dist/node_modules/zod/dist/esm/v3/errors.js.map +0 -1
- package/dist/node_modules/zod/dist/esm/v3/helpers/errorUtil.js.map +0 -1
- package/dist/node_modules/zod/dist/esm/v3/helpers/parseUtil.js.map +0 -1
- package/dist/node_modules/zod/dist/esm/v3/helpers/util.js.map +0 -1
- package/dist/node_modules/zod/dist/esm/v3/locales/en.js.map +0 -1
- package/dist/node_modules/zod/dist/esm/v3/types.js.map +0 -1
- /package/dist/node_modules/zod/{dist/esm/v3 → v3}/errors.js +0 -0
- /package/dist/node_modules/zod/{dist/esm/v3 → v3}/helpers/errorUtil.js +0 -0
- /package/dist/node_modules/zod/{dist/esm/v3 → v3}/helpers/parseUtil.js +0 -0
- /package/dist/node_modules/zod/{dist/esm/v3 → v3}/helpers/util.js +0 -0
package/dist/index.cjs
CHANGED
|
@@ -936,8 +936,9 @@ class ZodError extends Error {
|
|
|
936
936
|
const formErrors = [];
|
|
937
937
|
for (const sub of this.issues) {
|
|
938
938
|
if (sub.path.length > 0) {
|
|
939
|
-
|
|
940
|
-
fieldErrors[
|
|
939
|
+
const firstEl = sub.path[0];
|
|
940
|
+
fieldErrors[firstEl] = fieldErrors[firstEl] || [];
|
|
941
|
+
fieldErrors[firstEl].push(mapper(sub));
|
|
941
942
|
}
|
|
942
943
|
else {
|
|
943
944
|
formErrors.push(mapper(sub));
|
|
@@ -1021,6 +1022,8 @@ const errorMap = (issue, _ctx) => {
|
|
|
1021
1022
|
message = `String must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`;
|
|
1022
1023
|
else if (issue.type === "number")
|
|
1023
1024
|
message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;
|
|
1025
|
+
else if (issue.type === "bigint")
|
|
1026
|
+
message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;
|
|
1024
1027
|
else if (issue.type === "date")
|
|
1025
1028
|
message = `Date must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${new Date(Number(issue.minimum))}`;
|
|
1026
1029
|
else
|
|
@@ -1618,6 +1621,8 @@ function isValidJWT(jwt, alg) {
|
|
|
1618
1621
|
return false;
|
|
1619
1622
|
try {
|
|
1620
1623
|
const [header] = jwt.split(".");
|
|
1624
|
+
if (!header)
|
|
1625
|
+
return false;
|
|
1621
1626
|
// Convert base64url to base64
|
|
1622
1627
|
const base64 = header
|
|
1623
1628
|
.replace(/-/g, "+")
|
|
@@ -4658,6 +4663,7 @@ const enumType = ZodEnum.create;
|
|
|
4658
4663
|
ZodPromise.create;
|
|
4659
4664
|
const optionalType = ZodOptional.create;
|
|
4660
4665
|
ZodNullable.create;
|
|
4666
|
+
const NEVER = INVALID;
|
|
4661
4667
|
|
|
4662
4668
|
const LATEST_PROTOCOL_VERSION = "2025-06-18";
|
|
4663
4669
|
const SUPPORTED_PROTOCOL_VERSIONS = [
|
|
@@ -4819,6 +4825,24 @@ const CancelledNotificationSchema = NotificationSchema.extend({
|
|
|
4819
4825
|
}),
|
|
4820
4826
|
});
|
|
4821
4827
|
/* Base Metadata */
|
|
4828
|
+
/**
|
|
4829
|
+
* Icon schema for use in tools, prompts, resources, and implementations.
|
|
4830
|
+
*/
|
|
4831
|
+
const IconSchema = objectType({
|
|
4832
|
+
/**
|
|
4833
|
+
* URL or data URI for the icon.
|
|
4834
|
+
*/
|
|
4835
|
+
src: stringType(),
|
|
4836
|
+
/**
|
|
4837
|
+
* Optional MIME type for the icon.
|
|
4838
|
+
*/
|
|
4839
|
+
mimeType: optionalType(stringType()),
|
|
4840
|
+
/**
|
|
4841
|
+
* Optional string specifying icon dimensions (e.g., "48x48 96x96").
|
|
4842
|
+
*/
|
|
4843
|
+
sizes: optionalType(stringType()),
|
|
4844
|
+
})
|
|
4845
|
+
.passthrough();
|
|
4822
4846
|
/**
|
|
4823
4847
|
* Base metadata interface for common properties across resources, tools, prompts, and implementations.
|
|
4824
4848
|
*/
|
|
@@ -4842,6 +4866,19 @@ const BaseMetadataSchema = objectType({
|
|
|
4842
4866
|
*/
|
|
4843
4867
|
const ImplementationSchema = BaseMetadataSchema.extend({
|
|
4844
4868
|
version: stringType(),
|
|
4869
|
+
/**
|
|
4870
|
+
* An optional URL of the website for this implementation.
|
|
4871
|
+
*/
|
|
4872
|
+
websiteUrl: optionalType(stringType()),
|
|
4873
|
+
/**
|
|
4874
|
+
* An optional list of icons for this implementation.
|
|
4875
|
+
* This can be used by clients to display the implementation in a user interface.
|
|
4876
|
+
* Each icon should have a `kind` property that specifies whether it is a data representation or a URL source, a `src` property that points to the icon file or data representation, and may also include a `mimeType` and `sizes` property.
|
|
4877
|
+
* The `mimeType` property should be a valid MIME type for the icon file, such as "image/png" or "image/svg+xml".
|
|
4878
|
+
* The `sizes` property should be a string that specifies one or more sizes at which the icon file can be used, such as "48x48" or "any" for scalable formats like SVG.
|
|
4879
|
+
* The `sizes` property is optional, and if not provided, the client should assume that the icon can be used at any size.
|
|
4880
|
+
*/
|
|
4881
|
+
icons: optionalType(arrayType(IconSchema)),
|
|
4845
4882
|
});
|
|
4846
4883
|
/**
|
|
4847
4884
|
* Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.
|
|
@@ -5039,11 +5076,27 @@ const TextResourceContentsSchema = ResourceContentsSchema.extend({
|
|
|
5039
5076
|
*/
|
|
5040
5077
|
text: stringType(),
|
|
5041
5078
|
});
|
|
5079
|
+
/**
|
|
5080
|
+
* A Zod schema for validating Base64 strings that is more performant and
|
|
5081
|
+
* robust for very large inputs than the default regex-based check. It avoids
|
|
5082
|
+
* stack overflows by using the native `atob` function for validation.
|
|
5083
|
+
*/
|
|
5084
|
+
const Base64Schema = stringType().refine((val) => {
|
|
5085
|
+
try {
|
|
5086
|
+
// atob throws a DOMException if the string contains characters
|
|
5087
|
+
// that are not part of the Base64 character set.
|
|
5088
|
+
atob(val);
|
|
5089
|
+
return true;
|
|
5090
|
+
}
|
|
5091
|
+
catch (_a) {
|
|
5092
|
+
return false;
|
|
5093
|
+
}
|
|
5094
|
+
}, { message: "Invalid Base64 string" });
|
|
5042
5095
|
const BlobResourceContentsSchema = ResourceContentsSchema.extend({
|
|
5043
5096
|
/**
|
|
5044
5097
|
* A base64-encoded string representing the binary data of the item.
|
|
5045
5098
|
*/
|
|
5046
|
-
blob:
|
|
5099
|
+
blob: Base64Schema,
|
|
5047
5100
|
});
|
|
5048
5101
|
/**
|
|
5049
5102
|
* A known resource that the server is capable of reading.
|
|
@@ -5063,6 +5116,10 @@ const ResourceSchema = BaseMetadataSchema.extend({
|
|
|
5063
5116
|
* The MIME type of this resource, if known.
|
|
5064
5117
|
*/
|
|
5065
5118
|
mimeType: optionalType(stringType()),
|
|
5119
|
+
/**
|
|
5120
|
+
* An optional list of icons for this resource.
|
|
5121
|
+
*/
|
|
5122
|
+
icons: optionalType(arrayType(IconSchema)),
|
|
5066
5123
|
/**
|
|
5067
5124
|
* See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
|
5068
5125
|
* for notes on _meta usage.
|
|
@@ -5208,6 +5265,10 @@ const PromptSchema = BaseMetadataSchema.extend({
|
|
|
5208
5265
|
* A list of arguments to use for templating the prompt.
|
|
5209
5266
|
*/
|
|
5210
5267
|
arguments: optionalType(arrayType(PromptArgumentSchema)),
|
|
5268
|
+
/**
|
|
5269
|
+
* An optional list of icons for this prompt.
|
|
5270
|
+
*/
|
|
5271
|
+
icons: optionalType(arrayType(IconSchema)),
|
|
5211
5272
|
/**
|
|
5212
5273
|
* See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
|
5213
5274
|
* for notes on _meta usage.
|
|
@@ -5266,7 +5327,7 @@ const ImageContentSchema = objectType({
|
|
|
5266
5327
|
/**
|
|
5267
5328
|
* The base64-encoded image data.
|
|
5268
5329
|
*/
|
|
5269
|
-
data:
|
|
5330
|
+
data: Base64Schema,
|
|
5270
5331
|
/**
|
|
5271
5332
|
* The MIME type of the image. Different providers may support different image types.
|
|
5272
5333
|
*/
|
|
@@ -5286,7 +5347,7 @@ const AudioContentSchema = objectType({
|
|
|
5286
5347
|
/**
|
|
5287
5348
|
* The base64-encoded audio data.
|
|
5288
5349
|
*/
|
|
5289
|
-
data:
|
|
5350
|
+
data: Base64Schema,
|
|
5290
5351
|
/**
|
|
5291
5352
|
* The MIME type of the audio. Different providers may support different audio types.
|
|
5292
5353
|
*/
|
|
@@ -5435,6 +5496,10 @@ const ToolSchema = BaseMetadataSchema.extend({
|
|
|
5435
5496
|
* Optional additional tool information.
|
|
5436
5497
|
*/
|
|
5437
5498
|
annotations: optionalType(ToolAnnotationsSchema),
|
|
5499
|
+
/**
|
|
5500
|
+
* An optional list of icons for this tool.
|
|
5501
|
+
*/
|
|
5502
|
+
icons: optionalType(arrayType(IconSchema)),
|
|
5438
5503
|
/**
|
|
5439
5504
|
* See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
|
5440
5505
|
* for notes on _meta usage.
|
|
@@ -5927,6 +5992,7 @@ class Protocol {
|
|
|
5927
5992
|
this._responseHandlers = new Map();
|
|
5928
5993
|
this._progressHandlers = new Map();
|
|
5929
5994
|
this._timeoutInfo = new Map();
|
|
5995
|
+
this._pendingDebouncedNotifications = new Set();
|
|
5930
5996
|
this.setNotificationHandler(CancelledNotificationSchema, (notification) => {
|
|
5931
5997
|
const controller = this._requestHandlerAbortControllers.get(notification.params.requestId);
|
|
5932
5998
|
controller === null || controller === void 0 ? void 0 : controller.abort(notification.params.reason);
|
|
@@ -6009,6 +6075,7 @@ class Protocol {
|
|
|
6009
6075
|
const responseHandlers = this._responseHandlers;
|
|
6010
6076
|
this._responseHandlers = new Map();
|
|
6011
6077
|
this._progressHandlers.clear();
|
|
6078
|
+
this._pendingDebouncedNotifications.clear();
|
|
6012
6079
|
this._transport = undefined;
|
|
6013
6080
|
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
6014
6081
|
const error = new McpError(ErrorCode.ConnectionClosed, "Connection closed");
|
|
@@ -6033,10 +6100,12 @@ class Protocol {
|
|
|
6033
6100
|
.catch((error) => this._onerror(new Error(`Uncaught error in notification handler: ${error}`)));
|
|
6034
6101
|
}
|
|
6035
6102
|
_onrequest(request, extra) {
|
|
6036
|
-
var _a, _b
|
|
6103
|
+
var _a, _b;
|
|
6037
6104
|
const handler = (_a = this._requestHandlers.get(request.method)) !== null && _a !== void 0 ? _a : this.fallbackRequestHandler;
|
|
6105
|
+
// Capture the current transport at request time to ensure responses go to the correct client
|
|
6106
|
+
const capturedTransport = this._transport;
|
|
6038
6107
|
if (handler === undefined) {
|
|
6039
|
-
|
|
6108
|
+
capturedTransport === null || capturedTransport === void 0 ? void 0 : capturedTransport.send({
|
|
6040
6109
|
jsonrpc: "2.0",
|
|
6041
6110
|
id: request.id,
|
|
6042
6111
|
error: {
|
|
@@ -6050,8 +6119,8 @@ class Protocol {
|
|
|
6050
6119
|
this._requestHandlerAbortControllers.set(request.id, abortController);
|
|
6051
6120
|
const fullExtra = {
|
|
6052
6121
|
signal: abortController.signal,
|
|
6053
|
-
sessionId:
|
|
6054
|
-
_meta: (
|
|
6122
|
+
sessionId: capturedTransport === null || capturedTransport === void 0 ? void 0 : capturedTransport.sessionId,
|
|
6123
|
+
_meta: (_b = request.params) === null || _b === void 0 ? void 0 : _b._meta,
|
|
6055
6124
|
sendNotification: (notification) => this.notification(notification, { relatedRequestId: request.id }),
|
|
6056
6125
|
sendRequest: (r, resultSchema, options) => this.request(r, resultSchema, { ...options, relatedRequestId: request.id }),
|
|
6057
6126
|
authInfo: extra === null || extra === void 0 ? void 0 : extra.authInfo,
|
|
@@ -6062,28 +6131,27 @@ class Protocol {
|
|
|
6062
6131
|
Promise.resolve()
|
|
6063
6132
|
.then(() => handler(request, fullExtra))
|
|
6064
6133
|
.then((result) => {
|
|
6065
|
-
var _a;
|
|
6066
6134
|
if (abortController.signal.aborted) {
|
|
6067
6135
|
return;
|
|
6068
6136
|
}
|
|
6069
|
-
return
|
|
6137
|
+
return capturedTransport === null || capturedTransport === void 0 ? void 0 : capturedTransport.send({
|
|
6070
6138
|
result,
|
|
6071
6139
|
jsonrpc: "2.0",
|
|
6072
6140
|
id: request.id,
|
|
6073
6141
|
});
|
|
6074
6142
|
}, (error) => {
|
|
6075
|
-
var _a
|
|
6143
|
+
var _a;
|
|
6076
6144
|
if (abortController.signal.aborted) {
|
|
6077
6145
|
return;
|
|
6078
6146
|
}
|
|
6079
|
-
return
|
|
6147
|
+
return capturedTransport === null || capturedTransport === void 0 ? void 0 : capturedTransport.send({
|
|
6080
6148
|
jsonrpc: "2.0",
|
|
6081
6149
|
id: request.id,
|
|
6082
6150
|
error: {
|
|
6083
6151
|
code: Number.isSafeInteger(error["code"])
|
|
6084
6152
|
? error["code"]
|
|
6085
6153
|
: ErrorCode.InternalError,
|
|
6086
|
-
message: (
|
|
6154
|
+
message: (_a = error.message) !== null && _a !== void 0 ? _a : "Internal error",
|
|
6087
6155
|
},
|
|
6088
6156
|
});
|
|
6089
6157
|
})
|
|
@@ -6222,10 +6290,45 @@ class Protocol {
|
|
|
6222
6290
|
* Emits a notification, which is a one-way message that does not expect a response.
|
|
6223
6291
|
*/
|
|
6224
6292
|
async notification(notification, options) {
|
|
6293
|
+
var _a, _b;
|
|
6225
6294
|
if (!this._transport) {
|
|
6226
6295
|
throw new Error("Not connected");
|
|
6227
6296
|
}
|
|
6228
6297
|
this.assertNotificationCapability(notification.method);
|
|
6298
|
+
const debouncedMethods = (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.debouncedNotificationMethods) !== null && _b !== void 0 ? _b : [];
|
|
6299
|
+
// A notification can only be debounced if it's in the list AND it's "simple"
|
|
6300
|
+
// (i.e., has no parameters and no related request ID that could be lost).
|
|
6301
|
+
const canDebounce = debouncedMethods.includes(notification.method)
|
|
6302
|
+
&& !notification.params
|
|
6303
|
+
&& !(options === null || options === void 0 ? void 0 : options.relatedRequestId);
|
|
6304
|
+
if (canDebounce) {
|
|
6305
|
+
// If a notification of this type is already scheduled, do nothing.
|
|
6306
|
+
if (this._pendingDebouncedNotifications.has(notification.method)) {
|
|
6307
|
+
return;
|
|
6308
|
+
}
|
|
6309
|
+
// Mark this notification type as pending.
|
|
6310
|
+
this._pendingDebouncedNotifications.add(notification.method);
|
|
6311
|
+
// Schedule the actual send to happen in the next microtask.
|
|
6312
|
+
// This allows all synchronous calls in the current event loop tick to be coalesced.
|
|
6313
|
+
Promise.resolve().then(() => {
|
|
6314
|
+
var _a;
|
|
6315
|
+
// Un-mark the notification so the next one can be scheduled.
|
|
6316
|
+
this._pendingDebouncedNotifications.delete(notification.method);
|
|
6317
|
+
// SAFETY CHECK: If the connection was closed while this was pending, abort.
|
|
6318
|
+
if (!this._transport) {
|
|
6319
|
+
return;
|
|
6320
|
+
}
|
|
6321
|
+
const jsonrpcNotification = {
|
|
6322
|
+
...notification,
|
|
6323
|
+
jsonrpc: "2.0",
|
|
6324
|
+
};
|
|
6325
|
+
// Send the notification, but don't await it here to avoid blocking.
|
|
6326
|
+
// Handle potential errors with a .catch().
|
|
6327
|
+
(_a = this._transport) === null || _a === void 0 ? void 0 : _a.send(jsonrpcNotification, options).catch(error => this._onerror(error));
|
|
6328
|
+
});
|
|
6329
|
+
// Return immediately.
|
|
6330
|
+
return;
|
|
6331
|
+
}
|
|
6229
6332
|
const jsonrpcNotification = {
|
|
6230
6333
|
...notification,
|
|
6231
6334
|
jsonrpc: "2.0",
|
|
@@ -14123,12 +14226,29 @@ async function pkceChallenge(length) {
|
|
|
14123
14226
|
};
|
|
14124
14227
|
}
|
|
14125
14228
|
|
|
14229
|
+
/**
|
|
14230
|
+
* Reusable URL validation that disallows javascript: scheme
|
|
14231
|
+
*/
|
|
14232
|
+
const SafeUrlSchema = stringType().url()
|
|
14233
|
+
.superRefine((val, ctx) => {
|
|
14234
|
+
if (!URL.canParse(val)) {
|
|
14235
|
+
ctx.addIssue({
|
|
14236
|
+
code: ZodIssueCode.custom,
|
|
14237
|
+
message: "URL must be parseable",
|
|
14238
|
+
fatal: true,
|
|
14239
|
+
});
|
|
14240
|
+
return NEVER;
|
|
14241
|
+
}
|
|
14242
|
+
}).refine((url) => {
|
|
14243
|
+
const u = new URL(url);
|
|
14244
|
+
return u.protocol !== 'javascript:' && u.protocol !== 'data:' && u.protocol !== 'vbscript:';
|
|
14245
|
+
}, { message: "URL cannot use javascript:, data:, or vbscript: scheme" });
|
|
14126
14246
|
/**
|
|
14127
14247
|
* RFC 9728 OAuth Protected Resource Metadata
|
|
14128
14248
|
*/
|
|
14129
14249
|
const OAuthProtectedResourceMetadataSchema = objectType({
|
|
14130
14250
|
resource: stringType().url(),
|
|
14131
|
-
authorization_servers: arrayType(
|
|
14251
|
+
authorization_servers: arrayType(SafeUrlSchema).optional(),
|
|
14132
14252
|
jwks_uri: stringType().url().optional(),
|
|
14133
14253
|
scopes_supported: arrayType(stringType()).optional(),
|
|
14134
14254
|
bearer_methods_supported: arrayType(stringType()).optional(),
|
|
@@ -14148,9 +14268,9 @@ const OAuthProtectedResourceMetadataSchema = objectType({
|
|
|
14148
14268
|
*/
|
|
14149
14269
|
const OAuthMetadataSchema = objectType({
|
|
14150
14270
|
issuer: stringType(),
|
|
14151
|
-
authorization_endpoint:
|
|
14152
|
-
token_endpoint:
|
|
14153
|
-
registration_endpoint:
|
|
14271
|
+
authorization_endpoint: SafeUrlSchema,
|
|
14272
|
+
token_endpoint: SafeUrlSchema,
|
|
14273
|
+
registration_endpoint: SafeUrlSchema.optional(),
|
|
14154
14274
|
scopes_supported: arrayType(stringType()).optional(),
|
|
14155
14275
|
response_types_supported: arrayType(stringType()),
|
|
14156
14276
|
response_modes_supported: arrayType(stringType()).optional(),
|
|
@@ -14158,8 +14278,8 @@ const OAuthMetadataSchema = objectType({
|
|
|
14158
14278
|
token_endpoint_auth_methods_supported: arrayType(stringType()).optional(),
|
|
14159
14279
|
token_endpoint_auth_signing_alg_values_supported: arrayType(stringType())
|
|
14160
14280
|
.optional(),
|
|
14161
|
-
service_documentation:
|
|
14162
|
-
revocation_endpoint:
|
|
14281
|
+
service_documentation: SafeUrlSchema.optional(),
|
|
14282
|
+
revocation_endpoint: SafeUrlSchema.optional(),
|
|
14163
14283
|
revocation_endpoint_auth_methods_supported: arrayType(stringType()).optional(),
|
|
14164
14284
|
revocation_endpoint_auth_signing_alg_values_supported: arrayType(stringType())
|
|
14165
14285
|
.optional(),
|
|
@@ -14171,11 +14291,65 @@ const OAuthMetadataSchema = objectType({
|
|
|
14171
14291
|
code_challenge_methods_supported: arrayType(stringType()).optional(),
|
|
14172
14292
|
})
|
|
14173
14293
|
.passthrough();
|
|
14294
|
+
/**
|
|
14295
|
+
* OpenID Connect Discovery 1.0 Provider Metadata
|
|
14296
|
+
* see: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
|
14297
|
+
*/
|
|
14298
|
+
const OpenIdProviderMetadataSchema = objectType({
|
|
14299
|
+
issuer: stringType(),
|
|
14300
|
+
authorization_endpoint: SafeUrlSchema,
|
|
14301
|
+
token_endpoint: SafeUrlSchema,
|
|
14302
|
+
userinfo_endpoint: SafeUrlSchema.optional(),
|
|
14303
|
+
jwks_uri: SafeUrlSchema,
|
|
14304
|
+
registration_endpoint: SafeUrlSchema.optional(),
|
|
14305
|
+
scopes_supported: arrayType(stringType()).optional(),
|
|
14306
|
+
response_types_supported: arrayType(stringType()),
|
|
14307
|
+
response_modes_supported: arrayType(stringType()).optional(),
|
|
14308
|
+
grant_types_supported: arrayType(stringType()).optional(),
|
|
14309
|
+
acr_values_supported: arrayType(stringType()).optional(),
|
|
14310
|
+
subject_types_supported: arrayType(stringType()),
|
|
14311
|
+
id_token_signing_alg_values_supported: arrayType(stringType()),
|
|
14312
|
+
id_token_encryption_alg_values_supported: arrayType(stringType()).optional(),
|
|
14313
|
+
id_token_encryption_enc_values_supported: arrayType(stringType()).optional(),
|
|
14314
|
+
userinfo_signing_alg_values_supported: arrayType(stringType()).optional(),
|
|
14315
|
+
userinfo_encryption_alg_values_supported: arrayType(stringType()).optional(),
|
|
14316
|
+
userinfo_encryption_enc_values_supported: arrayType(stringType()).optional(),
|
|
14317
|
+
request_object_signing_alg_values_supported: arrayType(stringType()).optional(),
|
|
14318
|
+
request_object_encryption_alg_values_supported: arrayType(stringType())
|
|
14319
|
+
.optional(),
|
|
14320
|
+
request_object_encryption_enc_values_supported: arrayType(stringType())
|
|
14321
|
+
.optional(),
|
|
14322
|
+
token_endpoint_auth_methods_supported: arrayType(stringType()).optional(),
|
|
14323
|
+
token_endpoint_auth_signing_alg_values_supported: arrayType(stringType())
|
|
14324
|
+
.optional(),
|
|
14325
|
+
display_values_supported: arrayType(stringType()).optional(),
|
|
14326
|
+
claim_types_supported: arrayType(stringType()).optional(),
|
|
14327
|
+
claims_supported: arrayType(stringType()).optional(),
|
|
14328
|
+
service_documentation: stringType().optional(),
|
|
14329
|
+
claims_locales_supported: arrayType(stringType()).optional(),
|
|
14330
|
+
ui_locales_supported: arrayType(stringType()).optional(),
|
|
14331
|
+
claims_parameter_supported: booleanType().optional(),
|
|
14332
|
+
request_parameter_supported: booleanType().optional(),
|
|
14333
|
+
request_uri_parameter_supported: booleanType().optional(),
|
|
14334
|
+
require_request_uri_registration: booleanType().optional(),
|
|
14335
|
+
op_policy_uri: SafeUrlSchema.optional(),
|
|
14336
|
+
op_tos_uri: SafeUrlSchema.optional(),
|
|
14337
|
+
})
|
|
14338
|
+
.passthrough();
|
|
14339
|
+
/**
|
|
14340
|
+
* OpenID Connect Discovery metadata that may include OAuth 2.0 fields
|
|
14341
|
+
* This schema represents the real-world scenario where OIDC providers
|
|
14342
|
+
* return a mix of OpenID Connect and OAuth 2.0 metadata fields
|
|
14343
|
+
*/
|
|
14344
|
+
const OpenIdProviderDiscoveryMetadataSchema = OpenIdProviderMetadataSchema.merge(OAuthMetadataSchema.pick({
|
|
14345
|
+
code_challenge_methods_supported: true,
|
|
14346
|
+
}));
|
|
14174
14347
|
/**
|
|
14175
14348
|
* OAuth 2.1 token response
|
|
14176
14349
|
*/
|
|
14177
14350
|
const OAuthTokensSchema = objectType({
|
|
14178
14351
|
access_token: stringType(),
|
|
14352
|
+
id_token: stringType().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect
|
|
14179
14353
|
token_type: stringType(),
|
|
14180
14354
|
expires_in: numberType().optional(),
|
|
14181
14355
|
scope: stringType().optional(),
|
|
@@ -14185,7 +14359,7 @@ const OAuthTokensSchema = objectType({
|
|
|
14185
14359
|
/**
|
|
14186
14360
|
* OAuth 2.1 error response
|
|
14187
14361
|
*/
|
|
14188
|
-
objectType({
|
|
14362
|
+
const OAuthErrorResponseSchema = objectType({
|
|
14189
14363
|
error: stringType(),
|
|
14190
14364
|
error_description: stringType().optional(),
|
|
14191
14365
|
error_uri: stringType().optional(),
|
|
@@ -14194,18 +14368,18 @@ objectType({
|
|
|
14194
14368
|
* RFC 7591 OAuth 2.0 Dynamic Client Registration metadata
|
|
14195
14369
|
*/
|
|
14196
14370
|
const OAuthClientMetadataSchema = objectType({
|
|
14197
|
-
redirect_uris: arrayType(
|
|
14371
|
+
redirect_uris: arrayType(SafeUrlSchema),
|
|
14198
14372
|
token_endpoint_auth_method: stringType().optional(),
|
|
14199
14373
|
grant_types: arrayType(stringType()).optional(),
|
|
14200
14374
|
response_types: arrayType(stringType()).optional(),
|
|
14201
14375
|
client_name: stringType().optional(),
|
|
14202
|
-
client_uri:
|
|
14203
|
-
logo_uri:
|
|
14376
|
+
client_uri: SafeUrlSchema.optional(),
|
|
14377
|
+
logo_uri: SafeUrlSchema.optional(),
|
|
14204
14378
|
scope: stringType().optional(),
|
|
14205
14379
|
contacts: arrayType(stringType()).optional(),
|
|
14206
|
-
tos_uri:
|
|
14380
|
+
tos_uri: SafeUrlSchema.optional(),
|
|
14207
14381
|
policy_uri: stringType().optional(),
|
|
14208
|
-
jwks_uri:
|
|
14382
|
+
jwks_uri: SafeUrlSchema.optional(),
|
|
14209
14383
|
jwks: anyType().optional(),
|
|
14210
14384
|
software_id: stringType().optional(),
|
|
14211
14385
|
software_version: stringType().optional(),
|
|
@@ -14283,22 +14457,313 @@ function checkResourceAllowed({ requestedResource, configuredResource }) {
|
|
|
14283
14457
|
return requestedPath.startsWith(configuredPath);
|
|
14284
14458
|
}
|
|
14285
14459
|
|
|
14460
|
+
/**
|
|
14461
|
+
* Base class for all OAuth errors
|
|
14462
|
+
*/
|
|
14463
|
+
class OAuthError extends Error {
|
|
14464
|
+
constructor(message, errorUri) {
|
|
14465
|
+
super(message);
|
|
14466
|
+
this.errorUri = errorUri;
|
|
14467
|
+
this.name = this.constructor.name;
|
|
14468
|
+
}
|
|
14469
|
+
/**
|
|
14470
|
+
* Converts the error to a standard OAuth error response object
|
|
14471
|
+
*/
|
|
14472
|
+
toResponseObject() {
|
|
14473
|
+
const response = {
|
|
14474
|
+
error: this.errorCode,
|
|
14475
|
+
error_description: this.message
|
|
14476
|
+
};
|
|
14477
|
+
if (this.errorUri) {
|
|
14478
|
+
response.error_uri = this.errorUri;
|
|
14479
|
+
}
|
|
14480
|
+
return response;
|
|
14481
|
+
}
|
|
14482
|
+
get errorCode() {
|
|
14483
|
+
return this.constructor.errorCode;
|
|
14484
|
+
}
|
|
14485
|
+
}
|
|
14486
|
+
/**
|
|
14487
|
+
* Invalid request error - The request is missing a required parameter,
|
|
14488
|
+
* includes an invalid parameter value, includes a parameter more than once,
|
|
14489
|
+
* or is otherwise malformed.
|
|
14490
|
+
*/
|
|
14491
|
+
class InvalidRequestError extends OAuthError {
|
|
14492
|
+
}
|
|
14493
|
+
InvalidRequestError.errorCode = "invalid_request";
|
|
14494
|
+
/**
|
|
14495
|
+
* Invalid client error - Client authentication failed (e.g., unknown client, no client
|
|
14496
|
+
* authentication included, or unsupported authentication method).
|
|
14497
|
+
*/
|
|
14498
|
+
class InvalidClientError extends OAuthError {
|
|
14499
|
+
}
|
|
14500
|
+
InvalidClientError.errorCode = "invalid_client";
|
|
14501
|
+
/**
|
|
14502
|
+
* Invalid grant error - The provided authorization grant or refresh token is
|
|
14503
|
+
* invalid, expired, revoked, does not match the redirection URI used in the
|
|
14504
|
+
* authorization request, or was issued to another client.
|
|
14505
|
+
*/
|
|
14506
|
+
class InvalidGrantError extends OAuthError {
|
|
14507
|
+
}
|
|
14508
|
+
InvalidGrantError.errorCode = "invalid_grant";
|
|
14509
|
+
/**
|
|
14510
|
+
* Unauthorized client error - The authenticated client is not authorized to use
|
|
14511
|
+
* this authorization grant type.
|
|
14512
|
+
*/
|
|
14513
|
+
class UnauthorizedClientError extends OAuthError {
|
|
14514
|
+
}
|
|
14515
|
+
UnauthorizedClientError.errorCode = "unauthorized_client";
|
|
14516
|
+
/**
|
|
14517
|
+
* Unsupported grant type error - The authorization grant type is not supported
|
|
14518
|
+
* by the authorization server.
|
|
14519
|
+
*/
|
|
14520
|
+
class UnsupportedGrantTypeError extends OAuthError {
|
|
14521
|
+
}
|
|
14522
|
+
UnsupportedGrantTypeError.errorCode = "unsupported_grant_type";
|
|
14523
|
+
/**
|
|
14524
|
+
* Invalid scope error - The requested scope is invalid, unknown, malformed, or
|
|
14525
|
+
* exceeds the scope granted by the resource owner.
|
|
14526
|
+
*/
|
|
14527
|
+
class InvalidScopeError extends OAuthError {
|
|
14528
|
+
}
|
|
14529
|
+
InvalidScopeError.errorCode = "invalid_scope";
|
|
14530
|
+
/**
|
|
14531
|
+
* Access denied error - The resource owner or authorization server denied the request.
|
|
14532
|
+
*/
|
|
14533
|
+
class AccessDeniedError extends OAuthError {
|
|
14534
|
+
}
|
|
14535
|
+
AccessDeniedError.errorCode = "access_denied";
|
|
14536
|
+
/**
|
|
14537
|
+
* Server error - The authorization server encountered an unexpected condition
|
|
14538
|
+
* that prevented it from fulfilling the request.
|
|
14539
|
+
*/
|
|
14540
|
+
class ServerError extends OAuthError {
|
|
14541
|
+
}
|
|
14542
|
+
ServerError.errorCode = "server_error";
|
|
14543
|
+
/**
|
|
14544
|
+
* Temporarily unavailable error - The authorization server is currently unable to
|
|
14545
|
+
* handle the request due to a temporary overloading or maintenance of the server.
|
|
14546
|
+
*/
|
|
14547
|
+
class TemporarilyUnavailableError extends OAuthError {
|
|
14548
|
+
}
|
|
14549
|
+
TemporarilyUnavailableError.errorCode = "temporarily_unavailable";
|
|
14550
|
+
/**
|
|
14551
|
+
* Unsupported response type error - The authorization server does not support
|
|
14552
|
+
* obtaining an authorization code using this method.
|
|
14553
|
+
*/
|
|
14554
|
+
class UnsupportedResponseTypeError extends OAuthError {
|
|
14555
|
+
}
|
|
14556
|
+
UnsupportedResponseTypeError.errorCode = "unsupported_response_type";
|
|
14557
|
+
/**
|
|
14558
|
+
* Unsupported token type error - The authorization server does not support
|
|
14559
|
+
* the requested token type.
|
|
14560
|
+
*/
|
|
14561
|
+
class UnsupportedTokenTypeError extends OAuthError {
|
|
14562
|
+
}
|
|
14563
|
+
UnsupportedTokenTypeError.errorCode = "unsupported_token_type";
|
|
14564
|
+
/**
|
|
14565
|
+
* Invalid token error - The access token provided is expired, revoked, malformed,
|
|
14566
|
+
* or invalid for other reasons.
|
|
14567
|
+
*/
|
|
14568
|
+
class InvalidTokenError extends OAuthError {
|
|
14569
|
+
}
|
|
14570
|
+
InvalidTokenError.errorCode = "invalid_token";
|
|
14571
|
+
/**
|
|
14572
|
+
* Method not allowed error - The HTTP method used is not allowed for this endpoint.
|
|
14573
|
+
* (Custom, non-standard error)
|
|
14574
|
+
*/
|
|
14575
|
+
class MethodNotAllowedError extends OAuthError {
|
|
14576
|
+
}
|
|
14577
|
+
MethodNotAllowedError.errorCode = "method_not_allowed";
|
|
14578
|
+
/**
|
|
14579
|
+
* Too many requests error - Rate limit exceeded.
|
|
14580
|
+
* (Custom, non-standard error based on RFC 6585)
|
|
14581
|
+
*/
|
|
14582
|
+
class TooManyRequestsError extends OAuthError {
|
|
14583
|
+
}
|
|
14584
|
+
TooManyRequestsError.errorCode = "too_many_requests";
|
|
14585
|
+
/**
|
|
14586
|
+
* Invalid client metadata error - The client metadata is invalid.
|
|
14587
|
+
* (Custom error for dynamic client registration - RFC 7591)
|
|
14588
|
+
*/
|
|
14589
|
+
class InvalidClientMetadataError extends OAuthError {
|
|
14590
|
+
}
|
|
14591
|
+
InvalidClientMetadataError.errorCode = "invalid_client_metadata";
|
|
14592
|
+
/**
|
|
14593
|
+
* Insufficient scope error - The request requires higher privileges than provided by the access token.
|
|
14594
|
+
*/
|
|
14595
|
+
class InsufficientScopeError extends OAuthError {
|
|
14596
|
+
}
|
|
14597
|
+
InsufficientScopeError.errorCode = "insufficient_scope";
|
|
14598
|
+
/**
|
|
14599
|
+
* A full list of all OAuthErrors, enabling parsing from error responses
|
|
14600
|
+
*/
|
|
14601
|
+
const OAUTH_ERRORS = {
|
|
14602
|
+
[InvalidRequestError.errorCode]: InvalidRequestError,
|
|
14603
|
+
[InvalidClientError.errorCode]: InvalidClientError,
|
|
14604
|
+
[InvalidGrantError.errorCode]: InvalidGrantError,
|
|
14605
|
+
[UnauthorizedClientError.errorCode]: UnauthorizedClientError,
|
|
14606
|
+
[UnsupportedGrantTypeError.errorCode]: UnsupportedGrantTypeError,
|
|
14607
|
+
[InvalidScopeError.errorCode]: InvalidScopeError,
|
|
14608
|
+
[AccessDeniedError.errorCode]: AccessDeniedError,
|
|
14609
|
+
[ServerError.errorCode]: ServerError,
|
|
14610
|
+
[TemporarilyUnavailableError.errorCode]: TemporarilyUnavailableError,
|
|
14611
|
+
[UnsupportedResponseTypeError.errorCode]: UnsupportedResponseTypeError,
|
|
14612
|
+
[UnsupportedTokenTypeError.errorCode]: UnsupportedTokenTypeError,
|
|
14613
|
+
[InvalidTokenError.errorCode]: InvalidTokenError,
|
|
14614
|
+
[MethodNotAllowedError.errorCode]: MethodNotAllowedError,
|
|
14615
|
+
[TooManyRequestsError.errorCode]: TooManyRequestsError,
|
|
14616
|
+
[InvalidClientMetadataError.errorCode]: InvalidClientMetadataError,
|
|
14617
|
+
[InsufficientScopeError.errorCode]: InsufficientScopeError,
|
|
14618
|
+
};
|
|
14619
|
+
|
|
14286
14620
|
class UnauthorizedError extends Error {
|
|
14287
14621
|
constructor(message) {
|
|
14288
14622
|
super(message !== null && message !== void 0 ? message : "Unauthorized");
|
|
14289
14623
|
}
|
|
14290
14624
|
}
|
|
14625
|
+
/**
|
|
14626
|
+
* Determines the best client authentication method to use based on server support and client configuration.
|
|
14627
|
+
*
|
|
14628
|
+
* Priority order (highest to lowest):
|
|
14629
|
+
* 1. client_secret_basic (if client secret is available)
|
|
14630
|
+
* 2. client_secret_post (if client secret is available)
|
|
14631
|
+
* 3. none (for public clients)
|
|
14632
|
+
*
|
|
14633
|
+
* @param clientInformation - OAuth client information containing credentials
|
|
14634
|
+
* @param supportedMethods - Authentication methods supported by the authorization server
|
|
14635
|
+
* @returns The selected authentication method
|
|
14636
|
+
*/
|
|
14637
|
+
function selectClientAuthMethod(clientInformation, supportedMethods) {
|
|
14638
|
+
const hasClientSecret = clientInformation.client_secret !== undefined;
|
|
14639
|
+
// If server doesn't specify supported methods, use RFC 6749 defaults
|
|
14640
|
+
if (supportedMethods.length === 0) {
|
|
14641
|
+
return hasClientSecret ? "client_secret_post" : "none";
|
|
14642
|
+
}
|
|
14643
|
+
// Try methods in priority order (most secure first)
|
|
14644
|
+
if (hasClientSecret && supportedMethods.includes("client_secret_basic")) {
|
|
14645
|
+
return "client_secret_basic";
|
|
14646
|
+
}
|
|
14647
|
+
if (hasClientSecret && supportedMethods.includes("client_secret_post")) {
|
|
14648
|
+
return "client_secret_post";
|
|
14649
|
+
}
|
|
14650
|
+
if (supportedMethods.includes("none")) {
|
|
14651
|
+
return "none";
|
|
14652
|
+
}
|
|
14653
|
+
// Fallback: use what we have
|
|
14654
|
+
return hasClientSecret ? "client_secret_post" : "none";
|
|
14655
|
+
}
|
|
14656
|
+
/**
|
|
14657
|
+
* Applies client authentication to the request based on the specified method.
|
|
14658
|
+
*
|
|
14659
|
+
* Implements OAuth 2.1 client authentication methods:
|
|
14660
|
+
* - client_secret_basic: HTTP Basic authentication (RFC 6749 Section 2.3.1)
|
|
14661
|
+
* - client_secret_post: Credentials in request body (RFC 6749 Section 2.3.1)
|
|
14662
|
+
* - none: Public client authentication (RFC 6749 Section 2.1)
|
|
14663
|
+
*
|
|
14664
|
+
* @param method - The authentication method to use
|
|
14665
|
+
* @param clientInformation - OAuth client information containing credentials
|
|
14666
|
+
* @param headers - HTTP headers object to modify
|
|
14667
|
+
* @param params - URL search parameters to modify
|
|
14668
|
+
* @throws {Error} When required credentials are missing
|
|
14669
|
+
*/
|
|
14670
|
+
function applyClientAuthentication(method, clientInformation, headers, params) {
|
|
14671
|
+
const { client_id, client_secret } = clientInformation;
|
|
14672
|
+
switch (method) {
|
|
14673
|
+
case "client_secret_basic":
|
|
14674
|
+
applyBasicAuth(client_id, client_secret, headers);
|
|
14675
|
+
return;
|
|
14676
|
+
case "client_secret_post":
|
|
14677
|
+
applyPostAuth(client_id, client_secret, params);
|
|
14678
|
+
return;
|
|
14679
|
+
case "none":
|
|
14680
|
+
applyPublicAuth(client_id, params);
|
|
14681
|
+
return;
|
|
14682
|
+
default:
|
|
14683
|
+
throw new Error(`Unsupported client authentication method: ${method}`);
|
|
14684
|
+
}
|
|
14685
|
+
}
|
|
14686
|
+
/**
|
|
14687
|
+
* Applies HTTP Basic authentication (RFC 6749 Section 2.3.1)
|
|
14688
|
+
*/
|
|
14689
|
+
function applyBasicAuth(clientId, clientSecret, headers) {
|
|
14690
|
+
if (!clientSecret) {
|
|
14691
|
+
throw new Error("client_secret_basic authentication requires a client_secret");
|
|
14692
|
+
}
|
|
14693
|
+
const credentials = btoa(`${clientId}:${clientSecret}`);
|
|
14694
|
+
headers.set("Authorization", `Basic ${credentials}`);
|
|
14695
|
+
}
|
|
14696
|
+
/**
|
|
14697
|
+
* Applies POST body authentication (RFC 6749 Section 2.3.1)
|
|
14698
|
+
*/
|
|
14699
|
+
function applyPostAuth(clientId, clientSecret, params) {
|
|
14700
|
+
params.set("client_id", clientId);
|
|
14701
|
+
if (clientSecret) {
|
|
14702
|
+
params.set("client_secret", clientSecret);
|
|
14703
|
+
}
|
|
14704
|
+
}
|
|
14705
|
+
/**
|
|
14706
|
+
* Applies public client authentication (RFC 6749 Section 2.1)
|
|
14707
|
+
*/
|
|
14708
|
+
function applyPublicAuth(clientId, params) {
|
|
14709
|
+
params.set("client_id", clientId);
|
|
14710
|
+
}
|
|
14711
|
+
/**
|
|
14712
|
+
* Parses an OAuth error response from a string or Response object.
|
|
14713
|
+
*
|
|
14714
|
+
* If the input is a standard OAuth2.0 error response, it will be parsed according to the spec
|
|
14715
|
+
* and an instance of the appropriate OAuthError subclass will be returned.
|
|
14716
|
+
* If parsing fails, it falls back to a generic ServerError that includes
|
|
14717
|
+
* the response status (if available) and original content.
|
|
14718
|
+
*
|
|
14719
|
+
* @param input - A Response object or string containing the error response
|
|
14720
|
+
* @returns A Promise that resolves to an OAuthError instance
|
|
14721
|
+
*/
|
|
14722
|
+
async function parseErrorResponse(input) {
|
|
14723
|
+
const statusCode = input instanceof Response ? input.status : undefined;
|
|
14724
|
+
const body = input instanceof Response ? await input.text() : input;
|
|
14725
|
+
try {
|
|
14726
|
+
const result = OAuthErrorResponseSchema.parse(JSON.parse(body));
|
|
14727
|
+
const { error, error_description, error_uri } = result;
|
|
14728
|
+
const errorClass = OAUTH_ERRORS[error] || ServerError;
|
|
14729
|
+
return new errorClass(error_description || '', error_uri);
|
|
14730
|
+
}
|
|
14731
|
+
catch (error) {
|
|
14732
|
+
// Not a valid OAuth error response, but try to inform the user of the raw data anyway
|
|
14733
|
+
const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ''}Invalid OAuth error response: ${error}. Raw body: ${body}`;
|
|
14734
|
+
return new ServerError(errorMessage);
|
|
14735
|
+
}
|
|
14736
|
+
}
|
|
14291
14737
|
/**
|
|
14292
14738
|
* Orchestrates the full auth flow with a server.
|
|
14293
14739
|
*
|
|
14294
14740
|
* This can be used as a single entry point for all authorization functionality,
|
|
14295
14741
|
* instead of linking together the other lower-level functions in this module.
|
|
14296
14742
|
*/
|
|
14297
|
-
async function auth(provider,
|
|
14743
|
+
async function auth(provider, options) {
|
|
14744
|
+
var _a, _b;
|
|
14745
|
+
try {
|
|
14746
|
+
return await authInternal(provider, options);
|
|
14747
|
+
}
|
|
14748
|
+
catch (error) {
|
|
14749
|
+
// Handle recoverable error types by invalidating credentials and retrying
|
|
14750
|
+
if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) {
|
|
14751
|
+
await ((_a = provider.invalidateCredentials) === null || _a === void 0 ? void 0 : _a.call(provider, 'all'));
|
|
14752
|
+
return await authInternal(provider, options);
|
|
14753
|
+
}
|
|
14754
|
+
else if (error instanceof InvalidGrantError) {
|
|
14755
|
+
await ((_b = provider.invalidateCredentials) === null || _b === void 0 ? void 0 : _b.call(provider, 'tokens'));
|
|
14756
|
+
return await authInternal(provider, options);
|
|
14757
|
+
}
|
|
14758
|
+
// Throw otherwise
|
|
14759
|
+
throw error;
|
|
14760
|
+
}
|
|
14761
|
+
}
|
|
14762
|
+
async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn, }) {
|
|
14298
14763
|
let resourceMetadata;
|
|
14299
|
-
let authorizationServerUrl
|
|
14764
|
+
let authorizationServerUrl;
|
|
14300
14765
|
try {
|
|
14301
|
-
resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl });
|
|
14766
|
+
resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl }, fetchFn);
|
|
14302
14767
|
if (resourceMetadata.authorization_servers && resourceMetadata.authorization_servers.length > 0) {
|
|
14303
14768
|
authorizationServerUrl = resourceMetadata.authorization_servers[0];
|
|
14304
14769
|
}
|
|
@@ -14306,8 +14771,17 @@ async function auth(provider, { serverUrl, authorizationCode, scope, resourceMet
|
|
|
14306
14771
|
catch (_a) {
|
|
14307
14772
|
// Ignore errors and fall back to /.well-known/oauth-authorization-server
|
|
14308
14773
|
}
|
|
14774
|
+
/**
|
|
14775
|
+
* If we don't get a valid authorization server metadata from protected resource metadata,
|
|
14776
|
+
* fallback to the legacy MCP spec's implementation (version 2025-03-26): MCP server acts as the Authorization server.
|
|
14777
|
+
*/
|
|
14778
|
+
if (!authorizationServerUrl) {
|
|
14779
|
+
authorizationServerUrl = serverUrl;
|
|
14780
|
+
}
|
|
14309
14781
|
const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
|
|
14310
|
-
const metadata = await
|
|
14782
|
+
const metadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
14783
|
+
fetchFn,
|
|
14784
|
+
});
|
|
14311
14785
|
// Handle client registration if needed
|
|
14312
14786
|
let clientInformation = await Promise.resolve(provider.clientInformation());
|
|
14313
14787
|
if (!clientInformation) {
|
|
@@ -14320,6 +14794,7 @@ async function auth(provider, { serverUrl, authorizationCode, scope, resourceMet
|
|
|
14320
14794
|
const fullInformation = await registerClient(authorizationServerUrl, {
|
|
14321
14795
|
metadata,
|
|
14322
14796
|
clientMetadata: provider.clientMetadata,
|
|
14797
|
+
fetchFn,
|
|
14323
14798
|
});
|
|
14324
14799
|
await provider.saveClientInformation(fullInformation);
|
|
14325
14800
|
clientInformation = fullInformation;
|
|
@@ -14334,6 +14809,8 @@ async function auth(provider, { serverUrl, authorizationCode, scope, resourceMet
|
|
|
14334
14809
|
codeVerifier,
|
|
14335
14810
|
redirectUri: provider.redirectUrl,
|
|
14336
14811
|
resource,
|
|
14812
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
14813
|
+
fetchFn: fetchFn,
|
|
14337
14814
|
});
|
|
14338
14815
|
await provider.saveTokens(tokens);
|
|
14339
14816
|
return "AUTHORIZED";
|
|
@@ -14348,12 +14825,19 @@ async function auth(provider, { serverUrl, authorizationCode, scope, resourceMet
|
|
|
14348
14825
|
clientInformation,
|
|
14349
14826
|
refreshToken: tokens.refresh_token,
|
|
14350
14827
|
resource,
|
|
14828
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
14829
|
+
fetchFn,
|
|
14351
14830
|
});
|
|
14352
14831
|
await provider.saveTokens(newTokens);
|
|
14353
14832
|
return "AUTHORIZED";
|
|
14354
14833
|
}
|
|
14355
|
-
catch (
|
|
14356
|
-
//
|
|
14834
|
+
catch (error) {
|
|
14835
|
+
// If this is a ServerError, or an unknown type, log it out and try to continue. Otherwise, escalate so we can fix things and retry.
|
|
14836
|
+
if (!(error instanceof OAuthError) || error instanceof ServerError) ;
|
|
14837
|
+
else {
|
|
14838
|
+
// Refresh failed for another reason, re-throw
|
|
14839
|
+
throw error;
|
|
14840
|
+
}
|
|
14357
14841
|
}
|
|
14358
14842
|
}
|
|
14359
14843
|
const state = provider.state ? await provider.state() : undefined;
|
|
@@ -14417,33 +14901,12 @@ function extractResourceMetadataUrl(res) {
|
|
|
14417
14901
|
* If the server returns a 404 for the well-known endpoint, this function will
|
|
14418
14902
|
* return `undefined`. Any other errors will be thrown as exceptions.
|
|
14419
14903
|
*/
|
|
14420
|
-
async function discoverOAuthProtectedResourceMetadata(serverUrl, opts) {
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
else {
|
|
14427
|
-
url = new URL("/.well-known/oauth-protected-resource", serverUrl);
|
|
14428
|
-
}
|
|
14429
|
-
let response;
|
|
14430
|
-
try {
|
|
14431
|
-
response = await fetch(url, {
|
|
14432
|
-
headers: {
|
|
14433
|
-
"MCP-Protocol-Version": (_a = opts === null || opts === void 0 ? void 0 : opts.protocolVersion) !== null && _a !== void 0 ? _a : LATEST_PROTOCOL_VERSION
|
|
14434
|
-
}
|
|
14435
|
-
});
|
|
14436
|
-
}
|
|
14437
|
-
catch (error) {
|
|
14438
|
-
// CORS errors come back as TypeError
|
|
14439
|
-
if (error instanceof TypeError) {
|
|
14440
|
-
response = await fetch(url);
|
|
14441
|
-
}
|
|
14442
|
-
else {
|
|
14443
|
-
throw error;
|
|
14444
|
-
}
|
|
14445
|
-
}
|
|
14446
|
-
if (response.status === 404) {
|
|
14904
|
+
async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) {
|
|
14905
|
+
const response = await discoverMetadataWithFallback(serverUrl, 'oauth-protected-resource', fetchFn, {
|
|
14906
|
+
protocolVersion: opts === null || opts === void 0 ? void 0 : opts.protocolVersion,
|
|
14907
|
+
metadataUrl: opts === null || opts === void 0 ? void 0 : opts.resourceMetadataUrl,
|
|
14908
|
+
});
|
|
14909
|
+
if (!response || response.status === 404) {
|
|
14447
14910
|
throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
|
|
14448
14911
|
}
|
|
14449
14912
|
if (!response.ok) {
|
|
@@ -14454,15 +14917,15 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts) {
|
|
|
14454
14917
|
/**
|
|
14455
14918
|
* Helper function to handle fetch with CORS retry logic
|
|
14456
14919
|
*/
|
|
14457
|
-
async function fetchWithCorsRetry(url, headers) {
|
|
14920
|
+
async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
|
|
14458
14921
|
try {
|
|
14459
|
-
return await
|
|
14922
|
+
return await fetchFn(url, { headers });
|
|
14460
14923
|
}
|
|
14461
14924
|
catch (error) {
|
|
14462
14925
|
if (error instanceof TypeError) {
|
|
14463
14926
|
if (headers) {
|
|
14464
14927
|
// CORS errors come back as TypeError, retry without headers
|
|
14465
|
-
return fetchWithCorsRetry(url);
|
|
14928
|
+
return fetchWithCorsRetry(url, undefined, fetchFn);
|
|
14466
14929
|
}
|
|
14467
14930
|
else {
|
|
14468
14931
|
// We're getting CORS errors on retry too, return undefined
|
|
@@ -14473,57 +14936,162 @@ async function fetchWithCorsRetry(url, headers) {
|
|
|
14473
14936
|
}
|
|
14474
14937
|
}
|
|
14475
14938
|
/**
|
|
14476
|
-
* Constructs the well-known path for
|
|
14939
|
+
* Constructs the well-known path for auth-related metadata discovery
|
|
14477
14940
|
*/
|
|
14478
|
-
function buildWellKnownPath(pathname) {
|
|
14479
|
-
|
|
14941
|
+
function buildWellKnownPath(wellKnownPrefix, pathname = '', options = {}) {
|
|
14942
|
+
// Strip trailing slash from pathname to avoid double slashes
|
|
14480
14943
|
if (pathname.endsWith('/')) {
|
|
14481
|
-
|
|
14482
|
-
wellKnownPath = wellKnownPath.slice(0, -1);
|
|
14944
|
+
pathname = pathname.slice(0, -1);
|
|
14483
14945
|
}
|
|
14484
|
-
return
|
|
14946
|
+
return options.prependPathname
|
|
14947
|
+
? `${pathname}/.well-known/${wellKnownPrefix}`
|
|
14948
|
+
: `/.well-known/${wellKnownPrefix}${pathname}`;
|
|
14485
14949
|
}
|
|
14486
14950
|
/**
|
|
14487
14951
|
* Tries to discover OAuth metadata at a specific URL
|
|
14488
14952
|
*/
|
|
14489
|
-
async function tryMetadataDiscovery(url, protocolVersion) {
|
|
14953
|
+
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
|
|
14490
14954
|
const headers = {
|
|
14491
14955
|
"MCP-Protocol-Version": protocolVersion
|
|
14492
14956
|
};
|
|
14493
|
-
return await fetchWithCorsRetry(url, headers);
|
|
14957
|
+
return await fetchWithCorsRetry(url, headers, fetchFn);
|
|
14494
14958
|
}
|
|
14495
14959
|
/**
|
|
14496
14960
|
* Determines if fallback to root discovery should be attempted
|
|
14497
14961
|
*/
|
|
14498
14962
|
function shouldAttemptFallback(response, pathname) {
|
|
14499
|
-
return !response || response.status
|
|
14963
|
+
return !response || (response.status >= 400 && response.status < 500) && pathname !== '/';
|
|
14500
14964
|
}
|
|
14501
14965
|
/**
|
|
14502
|
-
*
|
|
14503
|
-
*
|
|
14504
|
-
* If the server returns a 404 for the well-known endpoint, this function will
|
|
14505
|
-
* return `undefined`. Any other errors will be thrown as exceptions.
|
|
14966
|
+
* Generic function for discovering OAuth metadata with fallback support
|
|
14506
14967
|
*/
|
|
14507
|
-
async function
|
|
14508
|
-
var _a;
|
|
14509
|
-
const issuer = new URL(
|
|
14510
|
-
const protocolVersion = (_a = void 0 ) !== null && _a !== void 0 ? _a : LATEST_PROTOCOL_VERSION;
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
let response = await tryMetadataDiscovery(pathAwareUrl, protocolVersion);
|
|
14515
|
-
// If path-aware discovery fails with 404, try fallback to root discovery
|
|
14516
|
-
if (shouldAttemptFallback(response, issuer.pathname)) {
|
|
14517
|
-
const rootUrl = new URL("/.well-known/oauth-authorization-server", issuer);
|
|
14518
|
-
response = await tryMetadataDiscovery(rootUrl, protocolVersion);
|
|
14968
|
+
async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
|
|
14969
|
+
var _a, _b;
|
|
14970
|
+
const issuer = new URL(serverUrl);
|
|
14971
|
+
const protocolVersion = (_a = opts === null || opts === void 0 ? void 0 : opts.protocolVersion) !== null && _a !== void 0 ? _a : LATEST_PROTOCOL_VERSION;
|
|
14972
|
+
let url;
|
|
14973
|
+
if (opts === null || opts === void 0 ? void 0 : opts.metadataUrl) {
|
|
14974
|
+
url = new URL(opts.metadataUrl);
|
|
14519
14975
|
}
|
|
14520
|
-
|
|
14521
|
-
|
|
14976
|
+
else {
|
|
14977
|
+
// Try path-aware discovery first
|
|
14978
|
+
const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname);
|
|
14979
|
+
url = new URL(wellKnownPath, (_b = opts === null || opts === void 0 ? void 0 : opts.metadataServerUrl) !== null && _b !== void 0 ? _b : issuer);
|
|
14980
|
+
url.search = issuer.search;
|
|
14522
14981
|
}
|
|
14523
|
-
|
|
14524
|
-
|
|
14982
|
+
let response = await tryMetadataDiscovery(url, protocolVersion, fetchFn);
|
|
14983
|
+
// If path-aware discovery fails with 404 and we're not already at root, try fallback to root discovery
|
|
14984
|
+
if (!(opts === null || opts === void 0 ? void 0 : opts.metadataUrl) && shouldAttemptFallback(response, issuer.pathname)) {
|
|
14985
|
+
const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
|
|
14986
|
+
response = await tryMetadataDiscovery(rootUrl, protocolVersion, fetchFn);
|
|
14525
14987
|
}
|
|
14526
|
-
return
|
|
14988
|
+
return response;
|
|
14989
|
+
}
|
|
14990
|
+
/**
|
|
14991
|
+
* Builds a list of discovery URLs to try for authorization server metadata.
|
|
14992
|
+
* URLs are returned in priority order:
|
|
14993
|
+
* 1. OAuth metadata at the given URL
|
|
14994
|
+
* 2. OAuth metadata at root (if URL has path)
|
|
14995
|
+
* 3. OIDC metadata endpoints
|
|
14996
|
+
*/
|
|
14997
|
+
function buildDiscoveryUrls(authorizationServerUrl) {
|
|
14998
|
+
const url = typeof authorizationServerUrl === 'string' ? new URL(authorizationServerUrl) : authorizationServerUrl;
|
|
14999
|
+
const hasPath = url.pathname !== '/';
|
|
15000
|
+
const urlsToTry = [];
|
|
15001
|
+
if (!hasPath) {
|
|
15002
|
+
// Root path: https://example.com/.well-known/oauth-authorization-server
|
|
15003
|
+
urlsToTry.push({
|
|
15004
|
+
url: new URL('/.well-known/oauth-authorization-server', url.origin),
|
|
15005
|
+
type: 'oauth'
|
|
15006
|
+
});
|
|
15007
|
+
// OIDC: https://example.com/.well-known/openid-configuration
|
|
15008
|
+
urlsToTry.push({
|
|
15009
|
+
url: new URL(`/.well-known/openid-configuration`, url.origin),
|
|
15010
|
+
type: 'oidc'
|
|
15011
|
+
});
|
|
15012
|
+
return urlsToTry;
|
|
15013
|
+
}
|
|
15014
|
+
// Strip trailing slash from pathname to avoid double slashes
|
|
15015
|
+
let pathname = url.pathname;
|
|
15016
|
+
if (pathname.endsWith('/')) {
|
|
15017
|
+
pathname = pathname.slice(0, -1);
|
|
15018
|
+
}
|
|
15019
|
+
// 1. OAuth metadata at the given URL
|
|
15020
|
+
// Insert well-known before the path: https://example.com/.well-known/oauth-authorization-server/tenant1
|
|
15021
|
+
urlsToTry.push({
|
|
15022
|
+
url: new URL(`/.well-known/oauth-authorization-server${pathname}`, url.origin),
|
|
15023
|
+
type: 'oauth'
|
|
15024
|
+
});
|
|
15025
|
+
// Root path: https://example.com/.well-known/oauth-authorization-server
|
|
15026
|
+
urlsToTry.push({
|
|
15027
|
+
url: new URL('/.well-known/oauth-authorization-server', url.origin),
|
|
15028
|
+
type: 'oauth'
|
|
15029
|
+
});
|
|
15030
|
+
// 3. OIDC metadata endpoints
|
|
15031
|
+
// RFC 8414 style: Insert /.well-known/openid-configuration before the path
|
|
15032
|
+
urlsToTry.push({
|
|
15033
|
+
url: new URL(`/.well-known/openid-configuration${pathname}`, url.origin),
|
|
15034
|
+
type: 'oidc'
|
|
15035
|
+
});
|
|
15036
|
+
// OIDC Discovery 1.0 style: Append /.well-known/openid-configuration after the path
|
|
15037
|
+
urlsToTry.push({
|
|
15038
|
+
url: new URL(`${pathname}/.well-known/openid-configuration`, url.origin),
|
|
15039
|
+
type: 'oidc'
|
|
15040
|
+
});
|
|
15041
|
+
return urlsToTry;
|
|
15042
|
+
}
|
|
15043
|
+
/**
|
|
15044
|
+
* Discovers authorization server metadata with support for RFC 8414 OAuth 2.0 Authorization Server Metadata
|
|
15045
|
+
* and OpenID Connect Discovery 1.0 specifications.
|
|
15046
|
+
*
|
|
15047
|
+
* This function implements a fallback strategy for authorization server discovery:
|
|
15048
|
+
* 1. Attempts RFC 8414 OAuth metadata discovery first
|
|
15049
|
+
* 2. If OAuth discovery fails, falls back to OpenID Connect Discovery
|
|
15050
|
+
*
|
|
15051
|
+
* @param authorizationServerUrl - The authorization server URL obtained from the MCP Server's
|
|
15052
|
+
* protected resource metadata, or the MCP server's URL if the
|
|
15053
|
+
* metadata was not found.
|
|
15054
|
+
* @param options - Configuration options
|
|
15055
|
+
* @param options.fetchFn - Optional fetch function for making HTTP requests, defaults to global fetch
|
|
15056
|
+
* @param options.protocolVersion - MCP protocol version to use, defaults to LATEST_PROTOCOL_VERSION
|
|
15057
|
+
* @returns Promise resolving to authorization server metadata, or undefined if discovery fails
|
|
15058
|
+
*/
|
|
15059
|
+
async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = LATEST_PROTOCOL_VERSION, } = {}) {
|
|
15060
|
+
var _a;
|
|
15061
|
+
const headers = { 'MCP-Protocol-Version': protocolVersion };
|
|
15062
|
+
// Get the list of URLs to try
|
|
15063
|
+
const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
|
|
15064
|
+
// Try each URL in order
|
|
15065
|
+
for (const { url: endpointUrl, type } of urlsToTry) {
|
|
15066
|
+
const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn);
|
|
15067
|
+
if (!response) {
|
|
15068
|
+
/**
|
|
15069
|
+
* CORS error occurred - don't throw as the endpoint may not allow CORS,
|
|
15070
|
+
* continue trying other possible endpoints
|
|
15071
|
+
*/
|
|
15072
|
+
continue;
|
|
15073
|
+
}
|
|
15074
|
+
if (!response.ok) {
|
|
15075
|
+
// Continue looking for any 4xx response code.
|
|
15076
|
+
if (response.status >= 400 && response.status < 500) {
|
|
15077
|
+
continue; // Try next URL
|
|
15078
|
+
}
|
|
15079
|
+
throw new Error(`HTTP ${response.status} trying to load ${type === 'oauth' ? 'OAuth' : 'OpenID provider'} metadata from ${endpointUrl}`);
|
|
15080
|
+
}
|
|
15081
|
+
// Parse and validate based on type
|
|
15082
|
+
if (type === 'oauth') {
|
|
15083
|
+
return OAuthMetadataSchema.parse(await response.json());
|
|
15084
|
+
}
|
|
15085
|
+
else {
|
|
15086
|
+
const metadata = OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
|
|
15087
|
+
// MCP spec requires OIDC providers to support S256 PKCE
|
|
15088
|
+
if (!((_a = metadata.code_challenge_methods_supported) === null || _a === void 0 ? void 0 : _a.includes('S256'))) {
|
|
15089
|
+
throw new Error(`Incompatible OIDC provider at ${endpointUrl}: does not support S256 code challenge method required by MCP specification`);
|
|
15090
|
+
}
|
|
15091
|
+
return metadata;
|
|
15092
|
+
}
|
|
15093
|
+
}
|
|
15094
|
+
return undefined;
|
|
14527
15095
|
}
|
|
14528
15096
|
/**
|
|
14529
15097
|
* Begins the authorization flow with the given server, by generating a PKCE challenge and constructing the authorization URL.
|
|
@@ -14560,6 +15128,12 @@ async function startAuthorization(authorizationServerUrl, { metadata, clientInfo
|
|
|
14560
15128
|
if (scope) {
|
|
14561
15129
|
authorizationUrl.searchParams.set("scope", scope);
|
|
14562
15130
|
}
|
|
15131
|
+
if (scope === null || scope === void 0 ? void 0 : scope.includes("offline_access")) {
|
|
15132
|
+
// if the request includes the OIDC-only "offline_access" scope,
|
|
15133
|
+
// we need to set the prompt to "consent" to ensure the user is prompted to grant offline access
|
|
15134
|
+
// https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
|
15135
|
+
authorizationUrl.searchParams.append("prompt", "consent");
|
|
15136
|
+
}
|
|
14563
15137
|
if (resource) {
|
|
14564
15138
|
authorizationUrl.searchParams.set("resource", resource.href);
|
|
14565
15139
|
}
|
|
@@ -14567,50 +15141,73 @@ async function startAuthorization(authorizationServerUrl, { metadata, clientInfo
|
|
|
14567
15141
|
}
|
|
14568
15142
|
/**
|
|
14569
15143
|
* Exchanges an authorization code for an access token with the given server.
|
|
15144
|
+
*
|
|
15145
|
+
* Supports multiple client authentication methods as specified in OAuth 2.1:
|
|
15146
|
+
* - Automatically selects the best authentication method based on server support
|
|
15147
|
+
* - Falls back to appropriate defaults when server metadata is unavailable
|
|
15148
|
+
*
|
|
15149
|
+
* @param authorizationServerUrl - The authorization server's base URL
|
|
15150
|
+
* @param options - Configuration object containing client info, auth code, etc.
|
|
15151
|
+
* @returns Promise resolving to OAuth tokens
|
|
15152
|
+
* @throws {Error} When token exchange fails or authentication is invalid
|
|
14570
15153
|
*/
|
|
14571
|
-
async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, }) {
|
|
15154
|
+
async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn, }) {
|
|
15155
|
+
var _a;
|
|
14572
15156
|
const grantType = "authorization_code";
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
}
|
|
14580
|
-
}
|
|
14581
|
-
else {
|
|
14582
|
-
tokenUrl = new URL("/token", authorizationServerUrl);
|
|
15157
|
+
const tokenUrl = (metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint)
|
|
15158
|
+
? new URL(metadata.token_endpoint)
|
|
15159
|
+
: new URL("/token", authorizationServerUrl);
|
|
15160
|
+
if ((metadata === null || metadata === void 0 ? void 0 : metadata.grant_types_supported) &&
|
|
15161
|
+
!metadata.grant_types_supported.includes(grantType)) {
|
|
15162
|
+
throw new Error(`Incompatible auth server: does not support grant type ${grantType}`);
|
|
14583
15163
|
}
|
|
14584
15164
|
// Exchange code for tokens
|
|
15165
|
+
const headers = new Headers({
|
|
15166
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
15167
|
+
"Accept": "application/json",
|
|
15168
|
+
});
|
|
14585
15169
|
const params = new URLSearchParams({
|
|
14586
15170
|
grant_type: grantType,
|
|
14587
|
-
client_id: clientInformation.client_id,
|
|
14588
15171
|
code: authorizationCode,
|
|
14589
15172
|
code_verifier: codeVerifier,
|
|
14590
15173
|
redirect_uri: String(redirectUri),
|
|
14591
15174
|
});
|
|
14592
|
-
if (
|
|
14593
|
-
|
|
15175
|
+
if (addClientAuthentication) {
|
|
15176
|
+
addClientAuthentication(headers, params, authorizationServerUrl, metadata);
|
|
15177
|
+
}
|
|
15178
|
+
else {
|
|
15179
|
+
// Determine and apply client authentication method
|
|
15180
|
+
const supportedMethods = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint_auth_methods_supported) !== null && _a !== void 0 ? _a : [];
|
|
15181
|
+
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
|
|
15182
|
+
applyClientAuthentication(authMethod, clientInformation, headers, params);
|
|
14594
15183
|
}
|
|
14595
15184
|
if (resource) {
|
|
14596
15185
|
params.set("resource", resource.href);
|
|
14597
15186
|
}
|
|
14598
|
-
const response = await fetch(tokenUrl, {
|
|
15187
|
+
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(tokenUrl, {
|
|
14599
15188
|
method: "POST",
|
|
14600
|
-
headers
|
|
14601
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
14602
|
-
},
|
|
15189
|
+
headers,
|
|
14603
15190
|
body: params,
|
|
14604
15191
|
});
|
|
14605
15192
|
if (!response.ok) {
|
|
14606
|
-
throw
|
|
15193
|
+
throw await parseErrorResponse(response);
|
|
14607
15194
|
}
|
|
14608
15195
|
return OAuthTokensSchema.parse(await response.json());
|
|
14609
15196
|
}
|
|
14610
15197
|
/**
|
|
14611
15198
|
* Exchange a refresh token for an updated access token.
|
|
15199
|
+
*
|
|
15200
|
+
* Supports multiple client authentication methods as specified in OAuth 2.1:
|
|
15201
|
+
* - Automatically selects the best authentication method based on server support
|
|
15202
|
+
* - Preserves the original refresh token if a new one is not returned
|
|
15203
|
+
*
|
|
15204
|
+
* @param authorizationServerUrl - The authorization server's base URL
|
|
15205
|
+
* @param options - Configuration object containing client info, refresh token, etc.
|
|
15206
|
+
* @returns Promise resolving to OAuth tokens (preserves original refresh_token if not replaced)
|
|
15207
|
+
* @throws {Error} When token refresh fails or authentication is invalid
|
|
14612
15208
|
*/
|
|
14613
|
-
async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, }) {
|
|
15209
|
+
async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn, }) {
|
|
15210
|
+
var _a;
|
|
14614
15211
|
const grantType = "refresh_token";
|
|
14615
15212
|
let tokenUrl;
|
|
14616
15213
|
if (metadata) {
|
|
@@ -14624,33 +15221,39 @@ async function refreshAuthorization(authorizationServerUrl, { metadata, clientIn
|
|
|
14624
15221
|
tokenUrl = new URL("/token", authorizationServerUrl);
|
|
14625
15222
|
}
|
|
14626
15223
|
// Exchange refresh token
|
|
15224
|
+
const headers = new Headers({
|
|
15225
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
15226
|
+
});
|
|
14627
15227
|
const params = new URLSearchParams({
|
|
14628
15228
|
grant_type: grantType,
|
|
14629
|
-
client_id: clientInformation.client_id,
|
|
14630
15229
|
refresh_token: refreshToken,
|
|
14631
15230
|
});
|
|
14632
|
-
if (
|
|
14633
|
-
|
|
15231
|
+
if (addClientAuthentication) {
|
|
15232
|
+
addClientAuthentication(headers, params, authorizationServerUrl, metadata);
|
|
15233
|
+
}
|
|
15234
|
+
else {
|
|
15235
|
+
// Determine and apply client authentication method
|
|
15236
|
+
const supportedMethods = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint_auth_methods_supported) !== null && _a !== void 0 ? _a : [];
|
|
15237
|
+
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
|
|
15238
|
+
applyClientAuthentication(authMethod, clientInformation, headers, params);
|
|
14634
15239
|
}
|
|
14635
15240
|
if (resource) {
|
|
14636
15241
|
params.set("resource", resource.href);
|
|
14637
15242
|
}
|
|
14638
|
-
const response = await fetch(tokenUrl, {
|
|
15243
|
+
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(tokenUrl, {
|
|
14639
15244
|
method: "POST",
|
|
14640
|
-
headers
|
|
14641
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
14642
|
-
},
|
|
15245
|
+
headers,
|
|
14643
15246
|
body: params,
|
|
14644
15247
|
});
|
|
14645
15248
|
if (!response.ok) {
|
|
14646
|
-
throw
|
|
15249
|
+
throw await parseErrorResponse(response);
|
|
14647
15250
|
}
|
|
14648
15251
|
return OAuthTokensSchema.parse({ refresh_token: refreshToken, ...(await response.json()) });
|
|
14649
15252
|
}
|
|
14650
15253
|
/**
|
|
14651
15254
|
* Performs OAuth 2.0 Dynamic Client Registration according to RFC 7591.
|
|
14652
15255
|
*/
|
|
14653
|
-
async function registerClient(authorizationServerUrl, { metadata, clientMetadata, }) {
|
|
15256
|
+
async function registerClient(authorizationServerUrl, { metadata, clientMetadata, fetchFn, }) {
|
|
14654
15257
|
let registrationUrl;
|
|
14655
15258
|
if (metadata) {
|
|
14656
15259
|
if (!metadata.registration_endpoint) {
|
|
@@ -14661,7 +15264,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
|
|
|
14661
15264
|
else {
|
|
14662
15265
|
registrationUrl = new URL("/register", authorizationServerUrl);
|
|
14663
15266
|
}
|
|
14664
|
-
const response = await fetch(registrationUrl, {
|
|
15267
|
+
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(registrationUrl, {
|
|
14665
15268
|
method: "POST",
|
|
14666
15269
|
headers: {
|
|
14667
15270
|
"Content-Type": "application/json",
|
|
@@ -14669,7 +15272,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
|
|
|
14669
15272
|
body: JSON.stringify(clientMetadata),
|
|
14670
15273
|
});
|
|
14671
15274
|
if (!response.ok) {
|
|
14672
|
-
throw
|
|
15275
|
+
throw await parseErrorResponse(response);
|
|
14673
15276
|
}
|
|
14674
15277
|
return OAuthClientInformationFullSchema.parse(await response.json());
|
|
14675
15278
|
}
|
|
@@ -14764,7 +15367,7 @@ function splitLines(chunk) {
|
|
|
14764
15367
|
const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
|
|
14765
15368
|
`, searchIndex);
|
|
14766
15369
|
let lineEnd = -1;
|
|
14767
|
-
if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = Math.min(crIndex, lfIndex) : crIndex !== -1 ? lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) {
|
|
15370
|
+
if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = Math.min(crIndex, lfIndex) : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) {
|
|
14768
15371
|
incompleteLine = chunk.slice(searchIndex);
|
|
14769
15372
|
break;
|
|
14770
15373
|
} else {
|
|
@@ -14835,7 +15438,7 @@ class StreamableHTTPClientTransport {
|
|
|
14835
15438
|
}
|
|
14836
15439
|
let result;
|
|
14837
15440
|
try {
|
|
14838
|
-
result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl });
|
|
15441
|
+
result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
|
|
14839
15442
|
}
|
|
14840
15443
|
catch (error) {
|
|
14841
15444
|
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
@@ -14896,7 +15499,7 @@ class StreamableHTTPClientTransport {
|
|
|
14896
15499
|
}
|
|
14897
15500
|
throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
|
|
14898
15501
|
}
|
|
14899
|
-
this._handleSseStream(response.body, options);
|
|
15502
|
+
this._handleSseStream(response.body, options, true);
|
|
14900
15503
|
}
|
|
14901
15504
|
catch (error) {
|
|
14902
15505
|
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
|
|
@@ -14956,7 +15559,7 @@ class StreamableHTTPClientTransport {
|
|
|
14956
15559
|
});
|
|
14957
15560
|
}, delay);
|
|
14958
15561
|
}
|
|
14959
|
-
_handleSseStream(stream, options) {
|
|
15562
|
+
_handleSseStream(stream, options, isReconnectable) {
|
|
14960
15563
|
if (!stream) {
|
|
14961
15564
|
return;
|
|
14962
15565
|
}
|
|
@@ -15000,19 +15603,19 @@ class StreamableHTTPClientTransport {
|
|
|
15000
15603
|
// Handle stream errors - likely a network disconnect
|
|
15001
15604
|
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, new Error(`SSE stream disconnected: ${error}`));
|
|
15002
15605
|
// Attempt to reconnect if the stream disconnects unexpectedly and we aren't closing
|
|
15003
|
-
if (
|
|
15606
|
+
if (isReconnectable &&
|
|
15607
|
+
this._abortController &&
|
|
15608
|
+
!this._abortController.signal.aborted) {
|
|
15004
15609
|
// Use the exponential backoff reconnection strategy
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
|
|
15015
|
-
}
|
|
15610
|
+
try {
|
|
15611
|
+
this._scheduleReconnection({
|
|
15612
|
+
resumptionToken: lastEventId,
|
|
15613
|
+
onresumptiontoken,
|
|
15614
|
+
replayMessageId
|
|
15615
|
+
}, 0);
|
|
15616
|
+
}
|
|
15617
|
+
catch (error) {
|
|
15618
|
+
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
|
|
15016
15619
|
}
|
|
15017
15620
|
}
|
|
15018
15621
|
}
|
|
@@ -15032,7 +15635,7 @@ class StreamableHTTPClientTransport {
|
|
|
15032
15635
|
if (!this._authProvider) {
|
|
15033
15636
|
throw new UnauthorizedError("No auth provider");
|
|
15034
15637
|
}
|
|
15035
|
-
const result = await auth(this._authProvider, { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl });
|
|
15638
|
+
const result = await auth(this._authProvider, { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
|
|
15036
15639
|
if (result !== "AUTHORIZED") {
|
|
15037
15640
|
throw new UnauthorizedError("Failed to authorize");
|
|
15038
15641
|
}
|
|
@@ -15071,7 +15674,7 @@ class StreamableHTTPClientTransport {
|
|
|
15071
15674
|
if (!response.ok) {
|
|
15072
15675
|
if (response.status === 401 && this._authProvider) {
|
|
15073
15676
|
this._resourceMetadataUrl = extractResourceMetadataUrl(response);
|
|
15074
|
-
const result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl });
|
|
15677
|
+
const result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
|
|
15075
15678
|
if (result !== "AUTHORIZED") {
|
|
15076
15679
|
throw new UnauthorizedError();
|
|
15077
15680
|
}
|
|
@@ -15101,7 +15704,7 @@ class StreamableHTTPClientTransport {
|
|
|
15101
15704
|
// Handle SSE stream responses for requests
|
|
15102
15705
|
// We use the same handler as standalone streams, which now supports
|
|
15103
15706
|
// reconnection with the last event ID
|
|
15104
|
-
this._handleSseStream(response.body, { onresumptiontoken });
|
|
15707
|
+
this._handleSseStream(response.body, { onresumptiontoken }, false);
|
|
15105
15708
|
}
|
|
15106
15709
|
else if (contentType === null || contentType === void 0 ? void 0 : contentType.includes("application/json")) {
|
|
15107
15710
|
// For non-streaming servers, we might get direct JSON responses
|