@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.js
CHANGED
|
@@ -915,8 +915,9 @@ class ZodError extends Error {
|
|
|
915
915
|
const formErrors = [];
|
|
916
916
|
for (const sub of this.issues) {
|
|
917
917
|
if (sub.path.length > 0) {
|
|
918
|
-
|
|
919
|
-
fieldErrors[
|
|
918
|
+
const firstEl = sub.path[0];
|
|
919
|
+
fieldErrors[firstEl] = fieldErrors[firstEl] || [];
|
|
920
|
+
fieldErrors[firstEl].push(mapper(sub));
|
|
920
921
|
}
|
|
921
922
|
else {
|
|
922
923
|
formErrors.push(mapper(sub));
|
|
@@ -1000,6 +1001,8 @@ const errorMap = (issue, _ctx) => {
|
|
|
1000
1001
|
message = `String must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`;
|
|
1001
1002
|
else if (issue.type === "number")
|
|
1002
1003
|
message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;
|
|
1004
|
+
else if (issue.type === "bigint")
|
|
1005
|
+
message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;
|
|
1003
1006
|
else if (issue.type === "date")
|
|
1004
1007
|
message = `Date must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${new Date(Number(issue.minimum))}`;
|
|
1005
1008
|
else
|
|
@@ -1597,6 +1600,8 @@ function isValidJWT(jwt, alg) {
|
|
|
1597
1600
|
return false;
|
|
1598
1601
|
try {
|
|
1599
1602
|
const [header] = jwt.split(".");
|
|
1603
|
+
if (!header)
|
|
1604
|
+
return false;
|
|
1600
1605
|
// Convert base64url to base64
|
|
1601
1606
|
const base64 = header
|
|
1602
1607
|
.replace(/-/g, "+")
|
|
@@ -4637,6 +4642,7 @@ const enumType = ZodEnum.create;
|
|
|
4637
4642
|
ZodPromise.create;
|
|
4638
4643
|
const optionalType = ZodOptional.create;
|
|
4639
4644
|
ZodNullable.create;
|
|
4645
|
+
const NEVER = INVALID;
|
|
4640
4646
|
|
|
4641
4647
|
const LATEST_PROTOCOL_VERSION = "2025-06-18";
|
|
4642
4648
|
const SUPPORTED_PROTOCOL_VERSIONS = [
|
|
@@ -4798,6 +4804,24 @@ const CancelledNotificationSchema = NotificationSchema.extend({
|
|
|
4798
4804
|
}),
|
|
4799
4805
|
});
|
|
4800
4806
|
/* Base Metadata */
|
|
4807
|
+
/**
|
|
4808
|
+
* Icon schema for use in tools, prompts, resources, and implementations.
|
|
4809
|
+
*/
|
|
4810
|
+
const IconSchema = objectType({
|
|
4811
|
+
/**
|
|
4812
|
+
* URL or data URI for the icon.
|
|
4813
|
+
*/
|
|
4814
|
+
src: stringType(),
|
|
4815
|
+
/**
|
|
4816
|
+
* Optional MIME type for the icon.
|
|
4817
|
+
*/
|
|
4818
|
+
mimeType: optionalType(stringType()),
|
|
4819
|
+
/**
|
|
4820
|
+
* Optional string specifying icon dimensions (e.g., "48x48 96x96").
|
|
4821
|
+
*/
|
|
4822
|
+
sizes: optionalType(stringType()),
|
|
4823
|
+
})
|
|
4824
|
+
.passthrough();
|
|
4801
4825
|
/**
|
|
4802
4826
|
* Base metadata interface for common properties across resources, tools, prompts, and implementations.
|
|
4803
4827
|
*/
|
|
@@ -4821,6 +4845,19 @@ const BaseMetadataSchema = objectType({
|
|
|
4821
4845
|
*/
|
|
4822
4846
|
const ImplementationSchema = BaseMetadataSchema.extend({
|
|
4823
4847
|
version: stringType(),
|
|
4848
|
+
/**
|
|
4849
|
+
* An optional URL of the website for this implementation.
|
|
4850
|
+
*/
|
|
4851
|
+
websiteUrl: optionalType(stringType()),
|
|
4852
|
+
/**
|
|
4853
|
+
* An optional list of icons for this implementation.
|
|
4854
|
+
* This can be used by clients to display the implementation in a user interface.
|
|
4855
|
+
* 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.
|
|
4856
|
+
* The `mimeType` property should be a valid MIME type for the icon file, such as "image/png" or "image/svg+xml".
|
|
4857
|
+
* 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.
|
|
4858
|
+
* The `sizes` property is optional, and if not provided, the client should assume that the icon can be used at any size.
|
|
4859
|
+
*/
|
|
4860
|
+
icons: optionalType(arrayType(IconSchema)),
|
|
4824
4861
|
});
|
|
4825
4862
|
/**
|
|
4826
4863
|
* 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.
|
|
@@ -5018,11 +5055,27 @@ const TextResourceContentsSchema = ResourceContentsSchema.extend({
|
|
|
5018
5055
|
*/
|
|
5019
5056
|
text: stringType(),
|
|
5020
5057
|
});
|
|
5058
|
+
/**
|
|
5059
|
+
* A Zod schema for validating Base64 strings that is more performant and
|
|
5060
|
+
* robust for very large inputs than the default regex-based check. It avoids
|
|
5061
|
+
* stack overflows by using the native `atob` function for validation.
|
|
5062
|
+
*/
|
|
5063
|
+
const Base64Schema = stringType().refine((val) => {
|
|
5064
|
+
try {
|
|
5065
|
+
// atob throws a DOMException if the string contains characters
|
|
5066
|
+
// that are not part of the Base64 character set.
|
|
5067
|
+
atob(val);
|
|
5068
|
+
return true;
|
|
5069
|
+
}
|
|
5070
|
+
catch (_a) {
|
|
5071
|
+
return false;
|
|
5072
|
+
}
|
|
5073
|
+
}, { message: "Invalid Base64 string" });
|
|
5021
5074
|
const BlobResourceContentsSchema = ResourceContentsSchema.extend({
|
|
5022
5075
|
/**
|
|
5023
5076
|
* A base64-encoded string representing the binary data of the item.
|
|
5024
5077
|
*/
|
|
5025
|
-
blob:
|
|
5078
|
+
blob: Base64Schema,
|
|
5026
5079
|
});
|
|
5027
5080
|
/**
|
|
5028
5081
|
* A known resource that the server is capable of reading.
|
|
@@ -5042,6 +5095,10 @@ const ResourceSchema = BaseMetadataSchema.extend({
|
|
|
5042
5095
|
* The MIME type of this resource, if known.
|
|
5043
5096
|
*/
|
|
5044
5097
|
mimeType: optionalType(stringType()),
|
|
5098
|
+
/**
|
|
5099
|
+
* An optional list of icons for this resource.
|
|
5100
|
+
*/
|
|
5101
|
+
icons: optionalType(arrayType(IconSchema)),
|
|
5045
5102
|
/**
|
|
5046
5103
|
* See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
|
5047
5104
|
* for notes on _meta usage.
|
|
@@ -5187,6 +5244,10 @@ const PromptSchema = BaseMetadataSchema.extend({
|
|
|
5187
5244
|
* A list of arguments to use for templating the prompt.
|
|
5188
5245
|
*/
|
|
5189
5246
|
arguments: optionalType(arrayType(PromptArgumentSchema)),
|
|
5247
|
+
/**
|
|
5248
|
+
* An optional list of icons for this prompt.
|
|
5249
|
+
*/
|
|
5250
|
+
icons: optionalType(arrayType(IconSchema)),
|
|
5190
5251
|
/**
|
|
5191
5252
|
* See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
|
5192
5253
|
* for notes on _meta usage.
|
|
@@ -5245,7 +5306,7 @@ const ImageContentSchema = objectType({
|
|
|
5245
5306
|
/**
|
|
5246
5307
|
* The base64-encoded image data.
|
|
5247
5308
|
*/
|
|
5248
|
-
data:
|
|
5309
|
+
data: Base64Schema,
|
|
5249
5310
|
/**
|
|
5250
5311
|
* The MIME type of the image. Different providers may support different image types.
|
|
5251
5312
|
*/
|
|
@@ -5265,7 +5326,7 @@ const AudioContentSchema = objectType({
|
|
|
5265
5326
|
/**
|
|
5266
5327
|
* The base64-encoded audio data.
|
|
5267
5328
|
*/
|
|
5268
|
-
data:
|
|
5329
|
+
data: Base64Schema,
|
|
5269
5330
|
/**
|
|
5270
5331
|
* The MIME type of the audio. Different providers may support different audio types.
|
|
5271
5332
|
*/
|
|
@@ -5414,6 +5475,10 @@ const ToolSchema = BaseMetadataSchema.extend({
|
|
|
5414
5475
|
* Optional additional tool information.
|
|
5415
5476
|
*/
|
|
5416
5477
|
annotations: optionalType(ToolAnnotationsSchema),
|
|
5478
|
+
/**
|
|
5479
|
+
* An optional list of icons for this tool.
|
|
5480
|
+
*/
|
|
5481
|
+
icons: optionalType(arrayType(IconSchema)),
|
|
5417
5482
|
/**
|
|
5418
5483
|
* See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
|
5419
5484
|
* for notes on _meta usage.
|
|
@@ -5906,6 +5971,7 @@ class Protocol {
|
|
|
5906
5971
|
this._responseHandlers = new Map();
|
|
5907
5972
|
this._progressHandlers = new Map();
|
|
5908
5973
|
this._timeoutInfo = new Map();
|
|
5974
|
+
this._pendingDebouncedNotifications = new Set();
|
|
5909
5975
|
this.setNotificationHandler(CancelledNotificationSchema, (notification) => {
|
|
5910
5976
|
const controller = this._requestHandlerAbortControllers.get(notification.params.requestId);
|
|
5911
5977
|
controller === null || controller === void 0 ? void 0 : controller.abort(notification.params.reason);
|
|
@@ -5988,6 +6054,7 @@ class Protocol {
|
|
|
5988
6054
|
const responseHandlers = this._responseHandlers;
|
|
5989
6055
|
this._responseHandlers = new Map();
|
|
5990
6056
|
this._progressHandlers.clear();
|
|
6057
|
+
this._pendingDebouncedNotifications.clear();
|
|
5991
6058
|
this._transport = undefined;
|
|
5992
6059
|
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
5993
6060
|
const error = new McpError(ErrorCode.ConnectionClosed, "Connection closed");
|
|
@@ -6012,10 +6079,12 @@ class Protocol {
|
|
|
6012
6079
|
.catch((error) => this._onerror(new Error(`Uncaught error in notification handler: ${error}`)));
|
|
6013
6080
|
}
|
|
6014
6081
|
_onrequest(request, extra) {
|
|
6015
|
-
var _a, _b
|
|
6082
|
+
var _a, _b;
|
|
6016
6083
|
const handler = (_a = this._requestHandlers.get(request.method)) !== null && _a !== void 0 ? _a : this.fallbackRequestHandler;
|
|
6084
|
+
// Capture the current transport at request time to ensure responses go to the correct client
|
|
6085
|
+
const capturedTransport = this._transport;
|
|
6017
6086
|
if (handler === undefined) {
|
|
6018
|
-
|
|
6087
|
+
capturedTransport === null || capturedTransport === void 0 ? void 0 : capturedTransport.send({
|
|
6019
6088
|
jsonrpc: "2.0",
|
|
6020
6089
|
id: request.id,
|
|
6021
6090
|
error: {
|
|
@@ -6029,8 +6098,8 @@ class Protocol {
|
|
|
6029
6098
|
this._requestHandlerAbortControllers.set(request.id, abortController);
|
|
6030
6099
|
const fullExtra = {
|
|
6031
6100
|
signal: abortController.signal,
|
|
6032
|
-
sessionId:
|
|
6033
|
-
_meta: (
|
|
6101
|
+
sessionId: capturedTransport === null || capturedTransport === void 0 ? void 0 : capturedTransport.sessionId,
|
|
6102
|
+
_meta: (_b = request.params) === null || _b === void 0 ? void 0 : _b._meta,
|
|
6034
6103
|
sendNotification: (notification) => this.notification(notification, { relatedRequestId: request.id }),
|
|
6035
6104
|
sendRequest: (r, resultSchema, options) => this.request(r, resultSchema, { ...options, relatedRequestId: request.id }),
|
|
6036
6105
|
authInfo: extra === null || extra === void 0 ? void 0 : extra.authInfo,
|
|
@@ -6041,28 +6110,27 @@ class Protocol {
|
|
|
6041
6110
|
Promise.resolve()
|
|
6042
6111
|
.then(() => handler(request, fullExtra))
|
|
6043
6112
|
.then((result) => {
|
|
6044
|
-
var _a;
|
|
6045
6113
|
if (abortController.signal.aborted) {
|
|
6046
6114
|
return;
|
|
6047
6115
|
}
|
|
6048
|
-
return
|
|
6116
|
+
return capturedTransport === null || capturedTransport === void 0 ? void 0 : capturedTransport.send({
|
|
6049
6117
|
result,
|
|
6050
6118
|
jsonrpc: "2.0",
|
|
6051
6119
|
id: request.id,
|
|
6052
6120
|
});
|
|
6053
6121
|
}, (error) => {
|
|
6054
|
-
var _a
|
|
6122
|
+
var _a;
|
|
6055
6123
|
if (abortController.signal.aborted) {
|
|
6056
6124
|
return;
|
|
6057
6125
|
}
|
|
6058
|
-
return
|
|
6126
|
+
return capturedTransport === null || capturedTransport === void 0 ? void 0 : capturedTransport.send({
|
|
6059
6127
|
jsonrpc: "2.0",
|
|
6060
6128
|
id: request.id,
|
|
6061
6129
|
error: {
|
|
6062
6130
|
code: Number.isSafeInteger(error["code"])
|
|
6063
6131
|
? error["code"]
|
|
6064
6132
|
: ErrorCode.InternalError,
|
|
6065
|
-
message: (
|
|
6133
|
+
message: (_a = error.message) !== null && _a !== void 0 ? _a : "Internal error",
|
|
6066
6134
|
},
|
|
6067
6135
|
});
|
|
6068
6136
|
})
|
|
@@ -6201,10 +6269,45 @@ class Protocol {
|
|
|
6201
6269
|
* Emits a notification, which is a one-way message that does not expect a response.
|
|
6202
6270
|
*/
|
|
6203
6271
|
async notification(notification, options) {
|
|
6272
|
+
var _a, _b;
|
|
6204
6273
|
if (!this._transport) {
|
|
6205
6274
|
throw new Error("Not connected");
|
|
6206
6275
|
}
|
|
6207
6276
|
this.assertNotificationCapability(notification.method);
|
|
6277
|
+
const debouncedMethods = (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.debouncedNotificationMethods) !== null && _b !== void 0 ? _b : [];
|
|
6278
|
+
// A notification can only be debounced if it's in the list AND it's "simple"
|
|
6279
|
+
// (i.e., has no parameters and no related request ID that could be lost).
|
|
6280
|
+
const canDebounce = debouncedMethods.includes(notification.method)
|
|
6281
|
+
&& !notification.params
|
|
6282
|
+
&& !(options === null || options === void 0 ? void 0 : options.relatedRequestId);
|
|
6283
|
+
if (canDebounce) {
|
|
6284
|
+
// If a notification of this type is already scheduled, do nothing.
|
|
6285
|
+
if (this._pendingDebouncedNotifications.has(notification.method)) {
|
|
6286
|
+
return;
|
|
6287
|
+
}
|
|
6288
|
+
// Mark this notification type as pending.
|
|
6289
|
+
this._pendingDebouncedNotifications.add(notification.method);
|
|
6290
|
+
// Schedule the actual send to happen in the next microtask.
|
|
6291
|
+
// This allows all synchronous calls in the current event loop tick to be coalesced.
|
|
6292
|
+
Promise.resolve().then(() => {
|
|
6293
|
+
var _a;
|
|
6294
|
+
// Un-mark the notification so the next one can be scheduled.
|
|
6295
|
+
this._pendingDebouncedNotifications.delete(notification.method);
|
|
6296
|
+
// SAFETY CHECK: If the connection was closed while this was pending, abort.
|
|
6297
|
+
if (!this._transport) {
|
|
6298
|
+
return;
|
|
6299
|
+
}
|
|
6300
|
+
const jsonrpcNotification = {
|
|
6301
|
+
...notification,
|
|
6302
|
+
jsonrpc: "2.0",
|
|
6303
|
+
};
|
|
6304
|
+
// Send the notification, but don't await it here to avoid blocking.
|
|
6305
|
+
// Handle potential errors with a .catch().
|
|
6306
|
+
(_a = this._transport) === null || _a === void 0 ? void 0 : _a.send(jsonrpcNotification, options).catch(error => this._onerror(error));
|
|
6307
|
+
});
|
|
6308
|
+
// Return immediately.
|
|
6309
|
+
return;
|
|
6310
|
+
}
|
|
6208
6311
|
const jsonrpcNotification = {
|
|
6209
6312
|
...notification,
|
|
6210
6313
|
jsonrpc: "2.0",
|
|
@@ -14102,12 +14205,29 @@ async function pkceChallenge(length) {
|
|
|
14102
14205
|
};
|
|
14103
14206
|
}
|
|
14104
14207
|
|
|
14208
|
+
/**
|
|
14209
|
+
* Reusable URL validation that disallows javascript: scheme
|
|
14210
|
+
*/
|
|
14211
|
+
const SafeUrlSchema = stringType().url()
|
|
14212
|
+
.superRefine((val, ctx) => {
|
|
14213
|
+
if (!URL.canParse(val)) {
|
|
14214
|
+
ctx.addIssue({
|
|
14215
|
+
code: ZodIssueCode.custom,
|
|
14216
|
+
message: "URL must be parseable",
|
|
14217
|
+
fatal: true,
|
|
14218
|
+
});
|
|
14219
|
+
return NEVER;
|
|
14220
|
+
}
|
|
14221
|
+
}).refine((url) => {
|
|
14222
|
+
const u = new URL(url);
|
|
14223
|
+
return u.protocol !== 'javascript:' && u.protocol !== 'data:' && u.protocol !== 'vbscript:';
|
|
14224
|
+
}, { message: "URL cannot use javascript:, data:, or vbscript: scheme" });
|
|
14105
14225
|
/**
|
|
14106
14226
|
* RFC 9728 OAuth Protected Resource Metadata
|
|
14107
14227
|
*/
|
|
14108
14228
|
const OAuthProtectedResourceMetadataSchema = objectType({
|
|
14109
14229
|
resource: stringType().url(),
|
|
14110
|
-
authorization_servers: arrayType(
|
|
14230
|
+
authorization_servers: arrayType(SafeUrlSchema).optional(),
|
|
14111
14231
|
jwks_uri: stringType().url().optional(),
|
|
14112
14232
|
scopes_supported: arrayType(stringType()).optional(),
|
|
14113
14233
|
bearer_methods_supported: arrayType(stringType()).optional(),
|
|
@@ -14127,9 +14247,9 @@ const OAuthProtectedResourceMetadataSchema = objectType({
|
|
|
14127
14247
|
*/
|
|
14128
14248
|
const OAuthMetadataSchema = objectType({
|
|
14129
14249
|
issuer: stringType(),
|
|
14130
|
-
authorization_endpoint:
|
|
14131
|
-
token_endpoint:
|
|
14132
|
-
registration_endpoint:
|
|
14250
|
+
authorization_endpoint: SafeUrlSchema,
|
|
14251
|
+
token_endpoint: SafeUrlSchema,
|
|
14252
|
+
registration_endpoint: SafeUrlSchema.optional(),
|
|
14133
14253
|
scopes_supported: arrayType(stringType()).optional(),
|
|
14134
14254
|
response_types_supported: arrayType(stringType()),
|
|
14135
14255
|
response_modes_supported: arrayType(stringType()).optional(),
|
|
@@ -14137,8 +14257,8 @@ const OAuthMetadataSchema = objectType({
|
|
|
14137
14257
|
token_endpoint_auth_methods_supported: arrayType(stringType()).optional(),
|
|
14138
14258
|
token_endpoint_auth_signing_alg_values_supported: arrayType(stringType())
|
|
14139
14259
|
.optional(),
|
|
14140
|
-
service_documentation:
|
|
14141
|
-
revocation_endpoint:
|
|
14260
|
+
service_documentation: SafeUrlSchema.optional(),
|
|
14261
|
+
revocation_endpoint: SafeUrlSchema.optional(),
|
|
14142
14262
|
revocation_endpoint_auth_methods_supported: arrayType(stringType()).optional(),
|
|
14143
14263
|
revocation_endpoint_auth_signing_alg_values_supported: arrayType(stringType())
|
|
14144
14264
|
.optional(),
|
|
@@ -14150,11 +14270,65 @@ const OAuthMetadataSchema = objectType({
|
|
|
14150
14270
|
code_challenge_methods_supported: arrayType(stringType()).optional(),
|
|
14151
14271
|
})
|
|
14152
14272
|
.passthrough();
|
|
14273
|
+
/**
|
|
14274
|
+
* OpenID Connect Discovery 1.0 Provider Metadata
|
|
14275
|
+
* see: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
|
14276
|
+
*/
|
|
14277
|
+
const OpenIdProviderMetadataSchema = objectType({
|
|
14278
|
+
issuer: stringType(),
|
|
14279
|
+
authorization_endpoint: SafeUrlSchema,
|
|
14280
|
+
token_endpoint: SafeUrlSchema,
|
|
14281
|
+
userinfo_endpoint: SafeUrlSchema.optional(),
|
|
14282
|
+
jwks_uri: SafeUrlSchema,
|
|
14283
|
+
registration_endpoint: SafeUrlSchema.optional(),
|
|
14284
|
+
scopes_supported: arrayType(stringType()).optional(),
|
|
14285
|
+
response_types_supported: arrayType(stringType()),
|
|
14286
|
+
response_modes_supported: arrayType(stringType()).optional(),
|
|
14287
|
+
grant_types_supported: arrayType(stringType()).optional(),
|
|
14288
|
+
acr_values_supported: arrayType(stringType()).optional(),
|
|
14289
|
+
subject_types_supported: arrayType(stringType()),
|
|
14290
|
+
id_token_signing_alg_values_supported: arrayType(stringType()),
|
|
14291
|
+
id_token_encryption_alg_values_supported: arrayType(stringType()).optional(),
|
|
14292
|
+
id_token_encryption_enc_values_supported: arrayType(stringType()).optional(),
|
|
14293
|
+
userinfo_signing_alg_values_supported: arrayType(stringType()).optional(),
|
|
14294
|
+
userinfo_encryption_alg_values_supported: arrayType(stringType()).optional(),
|
|
14295
|
+
userinfo_encryption_enc_values_supported: arrayType(stringType()).optional(),
|
|
14296
|
+
request_object_signing_alg_values_supported: arrayType(stringType()).optional(),
|
|
14297
|
+
request_object_encryption_alg_values_supported: arrayType(stringType())
|
|
14298
|
+
.optional(),
|
|
14299
|
+
request_object_encryption_enc_values_supported: arrayType(stringType())
|
|
14300
|
+
.optional(),
|
|
14301
|
+
token_endpoint_auth_methods_supported: arrayType(stringType()).optional(),
|
|
14302
|
+
token_endpoint_auth_signing_alg_values_supported: arrayType(stringType())
|
|
14303
|
+
.optional(),
|
|
14304
|
+
display_values_supported: arrayType(stringType()).optional(),
|
|
14305
|
+
claim_types_supported: arrayType(stringType()).optional(),
|
|
14306
|
+
claims_supported: arrayType(stringType()).optional(),
|
|
14307
|
+
service_documentation: stringType().optional(),
|
|
14308
|
+
claims_locales_supported: arrayType(stringType()).optional(),
|
|
14309
|
+
ui_locales_supported: arrayType(stringType()).optional(),
|
|
14310
|
+
claims_parameter_supported: booleanType().optional(),
|
|
14311
|
+
request_parameter_supported: booleanType().optional(),
|
|
14312
|
+
request_uri_parameter_supported: booleanType().optional(),
|
|
14313
|
+
require_request_uri_registration: booleanType().optional(),
|
|
14314
|
+
op_policy_uri: SafeUrlSchema.optional(),
|
|
14315
|
+
op_tos_uri: SafeUrlSchema.optional(),
|
|
14316
|
+
})
|
|
14317
|
+
.passthrough();
|
|
14318
|
+
/**
|
|
14319
|
+
* OpenID Connect Discovery metadata that may include OAuth 2.0 fields
|
|
14320
|
+
* This schema represents the real-world scenario where OIDC providers
|
|
14321
|
+
* return a mix of OpenID Connect and OAuth 2.0 metadata fields
|
|
14322
|
+
*/
|
|
14323
|
+
const OpenIdProviderDiscoveryMetadataSchema = OpenIdProviderMetadataSchema.merge(OAuthMetadataSchema.pick({
|
|
14324
|
+
code_challenge_methods_supported: true,
|
|
14325
|
+
}));
|
|
14153
14326
|
/**
|
|
14154
14327
|
* OAuth 2.1 token response
|
|
14155
14328
|
*/
|
|
14156
14329
|
const OAuthTokensSchema = objectType({
|
|
14157
14330
|
access_token: stringType(),
|
|
14331
|
+
id_token: stringType().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect
|
|
14158
14332
|
token_type: stringType(),
|
|
14159
14333
|
expires_in: numberType().optional(),
|
|
14160
14334
|
scope: stringType().optional(),
|
|
@@ -14164,7 +14338,7 @@ const OAuthTokensSchema = objectType({
|
|
|
14164
14338
|
/**
|
|
14165
14339
|
* OAuth 2.1 error response
|
|
14166
14340
|
*/
|
|
14167
|
-
objectType({
|
|
14341
|
+
const OAuthErrorResponseSchema = objectType({
|
|
14168
14342
|
error: stringType(),
|
|
14169
14343
|
error_description: stringType().optional(),
|
|
14170
14344
|
error_uri: stringType().optional(),
|
|
@@ -14173,18 +14347,18 @@ objectType({
|
|
|
14173
14347
|
* RFC 7591 OAuth 2.0 Dynamic Client Registration metadata
|
|
14174
14348
|
*/
|
|
14175
14349
|
const OAuthClientMetadataSchema = objectType({
|
|
14176
|
-
redirect_uris: arrayType(
|
|
14350
|
+
redirect_uris: arrayType(SafeUrlSchema),
|
|
14177
14351
|
token_endpoint_auth_method: stringType().optional(),
|
|
14178
14352
|
grant_types: arrayType(stringType()).optional(),
|
|
14179
14353
|
response_types: arrayType(stringType()).optional(),
|
|
14180
14354
|
client_name: stringType().optional(),
|
|
14181
|
-
client_uri:
|
|
14182
|
-
logo_uri:
|
|
14355
|
+
client_uri: SafeUrlSchema.optional(),
|
|
14356
|
+
logo_uri: SafeUrlSchema.optional(),
|
|
14183
14357
|
scope: stringType().optional(),
|
|
14184
14358
|
contacts: arrayType(stringType()).optional(),
|
|
14185
|
-
tos_uri:
|
|
14359
|
+
tos_uri: SafeUrlSchema.optional(),
|
|
14186
14360
|
policy_uri: stringType().optional(),
|
|
14187
|
-
jwks_uri:
|
|
14361
|
+
jwks_uri: SafeUrlSchema.optional(),
|
|
14188
14362
|
jwks: anyType().optional(),
|
|
14189
14363
|
software_id: stringType().optional(),
|
|
14190
14364
|
software_version: stringType().optional(),
|
|
@@ -14262,22 +14436,313 @@ function checkResourceAllowed({ requestedResource, configuredResource }) {
|
|
|
14262
14436
|
return requestedPath.startsWith(configuredPath);
|
|
14263
14437
|
}
|
|
14264
14438
|
|
|
14439
|
+
/**
|
|
14440
|
+
* Base class for all OAuth errors
|
|
14441
|
+
*/
|
|
14442
|
+
class OAuthError extends Error {
|
|
14443
|
+
constructor(message, errorUri) {
|
|
14444
|
+
super(message);
|
|
14445
|
+
this.errorUri = errorUri;
|
|
14446
|
+
this.name = this.constructor.name;
|
|
14447
|
+
}
|
|
14448
|
+
/**
|
|
14449
|
+
* Converts the error to a standard OAuth error response object
|
|
14450
|
+
*/
|
|
14451
|
+
toResponseObject() {
|
|
14452
|
+
const response = {
|
|
14453
|
+
error: this.errorCode,
|
|
14454
|
+
error_description: this.message
|
|
14455
|
+
};
|
|
14456
|
+
if (this.errorUri) {
|
|
14457
|
+
response.error_uri = this.errorUri;
|
|
14458
|
+
}
|
|
14459
|
+
return response;
|
|
14460
|
+
}
|
|
14461
|
+
get errorCode() {
|
|
14462
|
+
return this.constructor.errorCode;
|
|
14463
|
+
}
|
|
14464
|
+
}
|
|
14465
|
+
/**
|
|
14466
|
+
* Invalid request error - The request is missing a required parameter,
|
|
14467
|
+
* includes an invalid parameter value, includes a parameter more than once,
|
|
14468
|
+
* or is otherwise malformed.
|
|
14469
|
+
*/
|
|
14470
|
+
class InvalidRequestError extends OAuthError {
|
|
14471
|
+
}
|
|
14472
|
+
InvalidRequestError.errorCode = "invalid_request";
|
|
14473
|
+
/**
|
|
14474
|
+
* Invalid client error - Client authentication failed (e.g., unknown client, no client
|
|
14475
|
+
* authentication included, or unsupported authentication method).
|
|
14476
|
+
*/
|
|
14477
|
+
class InvalidClientError extends OAuthError {
|
|
14478
|
+
}
|
|
14479
|
+
InvalidClientError.errorCode = "invalid_client";
|
|
14480
|
+
/**
|
|
14481
|
+
* Invalid grant error - The provided authorization grant or refresh token is
|
|
14482
|
+
* invalid, expired, revoked, does not match the redirection URI used in the
|
|
14483
|
+
* authorization request, or was issued to another client.
|
|
14484
|
+
*/
|
|
14485
|
+
class InvalidGrantError extends OAuthError {
|
|
14486
|
+
}
|
|
14487
|
+
InvalidGrantError.errorCode = "invalid_grant";
|
|
14488
|
+
/**
|
|
14489
|
+
* Unauthorized client error - The authenticated client is not authorized to use
|
|
14490
|
+
* this authorization grant type.
|
|
14491
|
+
*/
|
|
14492
|
+
class UnauthorizedClientError extends OAuthError {
|
|
14493
|
+
}
|
|
14494
|
+
UnauthorizedClientError.errorCode = "unauthorized_client";
|
|
14495
|
+
/**
|
|
14496
|
+
* Unsupported grant type error - The authorization grant type is not supported
|
|
14497
|
+
* by the authorization server.
|
|
14498
|
+
*/
|
|
14499
|
+
class UnsupportedGrantTypeError extends OAuthError {
|
|
14500
|
+
}
|
|
14501
|
+
UnsupportedGrantTypeError.errorCode = "unsupported_grant_type";
|
|
14502
|
+
/**
|
|
14503
|
+
* Invalid scope error - The requested scope is invalid, unknown, malformed, or
|
|
14504
|
+
* exceeds the scope granted by the resource owner.
|
|
14505
|
+
*/
|
|
14506
|
+
class InvalidScopeError extends OAuthError {
|
|
14507
|
+
}
|
|
14508
|
+
InvalidScopeError.errorCode = "invalid_scope";
|
|
14509
|
+
/**
|
|
14510
|
+
* Access denied error - The resource owner or authorization server denied the request.
|
|
14511
|
+
*/
|
|
14512
|
+
class AccessDeniedError extends OAuthError {
|
|
14513
|
+
}
|
|
14514
|
+
AccessDeniedError.errorCode = "access_denied";
|
|
14515
|
+
/**
|
|
14516
|
+
* Server error - The authorization server encountered an unexpected condition
|
|
14517
|
+
* that prevented it from fulfilling the request.
|
|
14518
|
+
*/
|
|
14519
|
+
class ServerError extends OAuthError {
|
|
14520
|
+
}
|
|
14521
|
+
ServerError.errorCode = "server_error";
|
|
14522
|
+
/**
|
|
14523
|
+
* Temporarily unavailable error - The authorization server is currently unable to
|
|
14524
|
+
* handle the request due to a temporary overloading or maintenance of the server.
|
|
14525
|
+
*/
|
|
14526
|
+
class TemporarilyUnavailableError extends OAuthError {
|
|
14527
|
+
}
|
|
14528
|
+
TemporarilyUnavailableError.errorCode = "temporarily_unavailable";
|
|
14529
|
+
/**
|
|
14530
|
+
* Unsupported response type error - The authorization server does not support
|
|
14531
|
+
* obtaining an authorization code using this method.
|
|
14532
|
+
*/
|
|
14533
|
+
class UnsupportedResponseTypeError extends OAuthError {
|
|
14534
|
+
}
|
|
14535
|
+
UnsupportedResponseTypeError.errorCode = "unsupported_response_type";
|
|
14536
|
+
/**
|
|
14537
|
+
* Unsupported token type error - The authorization server does not support
|
|
14538
|
+
* the requested token type.
|
|
14539
|
+
*/
|
|
14540
|
+
class UnsupportedTokenTypeError extends OAuthError {
|
|
14541
|
+
}
|
|
14542
|
+
UnsupportedTokenTypeError.errorCode = "unsupported_token_type";
|
|
14543
|
+
/**
|
|
14544
|
+
* Invalid token error - The access token provided is expired, revoked, malformed,
|
|
14545
|
+
* or invalid for other reasons.
|
|
14546
|
+
*/
|
|
14547
|
+
class InvalidTokenError extends OAuthError {
|
|
14548
|
+
}
|
|
14549
|
+
InvalidTokenError.errorCode = "invalid_token";
|
|
14550
|
+
/**
|
|
14551
|
+
* Method not allowed error - The HTTP method used is not allowed for this endpoint.
|
|
14552
|
+
* (Custom, non-standard error)
|
|
14553
|
+
*/
|
|
14554
|
+
class MethodNotAllowedError extends OAuthError {
|
|
14555
|
+
}
|
|
14556
|
+
MethodNotAllowedError.errorCode = "method_not_allowed";
|
|
14557
|
+
/**
|
|
14558
|
+
* Too many requests error - Rate limit exceeded.
|
|
14559
|
+
* (Custom, non-standard error based on RFC 6585)
|
|
14560
|
+
*/
|
|
14561
|
+
class TooManyRequestsError extends OAuthError {
|
|
14562
|
+
}
|
|
14563
|
+
TooManyRequestsError.errorCode = "too_many_requests";
|
|
14564
|
+
/**
|
|
14565
|
+
* Invalid client metadata error - The client metadata is invalid.
|
|
14566
|
+
* (Custom error for dynamic client registration - RFC 7591)
|
|
14567
|
+
*/
|
|
14568
|
+
class InvalidClientMetadataError extends OAuthError {
|
|
14569
|
+
}
|
|
14570
|
+
InvalidClientMetadataError.errorCode = "invalid_client_metadata";
|
|
14571
|
+
/**
|
|
14572
|
+
* Insufficient scope error - The request requires higher privileges than provided by the access token.
|
|
14573
|
+
*/
|
|
14574
|
+
class InsufficientScopeError extends OAuthError {
|
|
14575
|
+
}
|
|
14576
|
+
InsufficientScopeError.errorCode = "insufficient_scope";
|
|
14577
|
+
/**
|
|
14578
|
+
* A full list of all OAuthErrors, enabling parsing from error responses
|
|
14579
|
+
*/
|
|
14580
|
+
const OAUTH_ERRORS = {
|
|
14581
|
+
[InvalidRequestError.errorCode]: InvalidRequestError,
|
|
14582
|
+
[InvalidClientError.errorCode]: InvalidClientError,
|
|
14583
|
+
[InvalidGrantError.errorCode]: InvalidGrantError,
|
|
14584
|
+
[UnauthorizedClientError.errorCode]: UnauthorizedClientError,
|
|
14585
|
+
[UnsupportedGrantTypeError.errorCode]: UnsupportedGrantTypeError,
|
|
14586
|
+
[InvalidScopeError.errorCode]: InvalidScopeError,
|
|
14587
|
+
[AccessDeniedError.errorCode]: AccessDeniedError,
|
|
14588
|
+
[ServerError.errorCode]: ServerError,
|
|
14589
|
+
[TemporarilyUnavailableError.errorCode]: TemporarilyUnavailableError,
|
|
14590
|
+
[UnsupportedResponseTypeError.errorCode]: UnsupportedResponseTypeError,
|
|
14591
|
+
[UnsupportedTokenTypeError.errorCode]: UnsupportedTokenTypeError,
|
|
14592
|
+
[InvalidTokenError.errorCode]: InvalidTokenError,
|
|
14593
|
+
[MethodNotAllowedError.errorCode]: MethodNotAllowedError,
|
|
14594
|
+
[TooManyRequestsError.errorCode]: TooManyRequestsError,
|
|
14595
|
+
[InvalidClientMetadataError.errorCode]: InvalidClientMetadataError,
|
|
14596
|
+
[InsufficientScopeError.errorCode]: InsufficientScopeError,
|
|
14597
|
+
};
|
|
14598
|
+
|
|
14265
14599
|
class UnauthorizedError extends Error {
|
|
14266
14600
|
constructor(message) {
|
|
14267
14601
|
super(message !== null && message !== void 0 ? message : "Unauthorized");
|
|
14268
14602
|
}
|
|
14269
14603
|
}
|
|
14604
|
+
/**
|
|
14605
|
+
* Determines the best client authentication method to use based on server support and client configuration.
|
|
14606
|
+
*
|
|
14607
|
+
* Priority order (highest to lowest):
|
|
14608
|
+
* 1. client_secret_basic (if client secret is available)
|
|
14609
|
+
* 2. client_secret_post (if client secret is available)
|
|
14610
|
+
* 3. none (for public clients)
|
|
14611
|
+
*
|
|
14612
|
+
* @param clientInformation - OAuth client information containing credentials
|
|
14613
|
+
* @param supportedMethods - Authentication methods supported by the authorization server
|
|
14614
|
+
* @returns The selected authentication method
|
|
14615
|
+
*/
|
|
14616
|
+
function selectClientAuthMethod(clientInformation, supportedMethods) {
|
|
14617
|
+
const hasClientSecret = clientInformation.client_secret !== undefined;
|
|
14618
|
+
// If server doesn't specify supported methods, use RFC 6749 defaults
|
|
14619
|
+
if (supportedMethods.length === 0) {
|
|
14620
|
+
return hasClientSecret ? "client_secret_post" : "none";
|
|
14621
|
+
}
|
|
14622
|
+
// Try methods in priority order (most secure first)
|
|
14623
|
+
if (hasClientSecret && supportedMethods.includes("client_secret_basic")) {
|
|
14624
|
+
return "client_secret_basic";
|
|
14625
|
+
}
|
|
14626
|
+
if (hasClientSecret && supportedMethods.includes("client_secret_post")) {
|
|
14627
|
+
return "client_secret_post";
|
|
14628
|
+
}
|
|
14629
|
+
if (supportedMethods.includes("none")) {
|
|
14630
|
+
return "none";
|
|
14631
|
+
}
|
|
14632
|
+
// Fallback: use what we have
|
|
14633
|
+
return hasClientSecret ? "client_secret_post" : "none";
|
|
14634
|
+
}
|
|
14635
|
+
/**
|
|
14636
|
+
* Applies client authentication to the request based on the specified method.
|
|
14637
|
+
*
|
|
14638
|
+
* Implements OAuth 2.1 client authentication methods:
|
|
14639
|
+
* - client_secret_basic: HTTP Basic authentication (RFC 6749 Section 2.3.1)
|
|
14640
|
+
* - client_secret_post: Credentials in request body (RFC 6749 Section 2.3.1)
|
|
14641
|
+
* - none: Public client authentication (RFC 6749 Section 2.1)
|
|
14642
|
+
*
|
|
14643
|
+
* @param method - The authentication method to use
|
|
14644
|
+
* @param clientInformation - OAuth client information containing credentials
|
|
14645
|
+
* @param headers - HTTP headers object to modify
|
|
14646
|
+
* @param params - URL search parameters to modify
|
|
14647
|
+
* @throws {Error} When required credentials are missing
|
|
14648
|
+
*/
|
|
14649
|
+
function applyClientAuthentication(method, clientInformation, headers, params) {
|
|
14650
|
+
const { client_id, client_secret } = clientInformation;
|
|
14651
|
+
switch (method) {
|
|
14652
|
+
case "client_secret_basic":
|
|
14653
|
+
applyBasicAuth(client_id, client_secret, headers);
|
|
14654
|
+
return;
|
|
14655
|
+
case "client_secret_post":
|
|
14656
|
+
applyPostAuth(client_id, client_secret, params);
|
|
14657
|
+
return;
|
|
14658
|
+
case "none":
|
|
14659
|
+
applyPublicAuth(client_id, params);
|
|
14660
|
+
return;
|
|
14661
|
+
default:
|
|
14662
|
+
throw new Error(`Unsupported client authentication method: ${method}`);
|
|
14663
|
+
}
|
|
14664
|
+
}
|
|
14665
|
+
/**
|
|
14666
|
+
* Applies HTTP Basic authentication (RFC 6749 Section 2.3.1)
|
|
14667
|
+
*/
|
|
14668
|
+
function applyBasicAuth(clientId, clientSecret, headers) {
|
|
14669
|
+
if (!clientSecret) {
|
|
14670
|
+
throw new Error("client_secret_basic authentication requires a client_secret");
|
|
14671
|
+
}
|
|
14672
|
+
const credentials = btoa(`${clientId}:${clientSecret}`);
|
|
14673
|
+
headers.set("Authorization", `Basic ${credentials}`);
|
|
14674
|
+
}
|
|
14675
|
+
/**
|
|
14676
|
+
* Applies POST body authentication (RFC 6749 Section 2.3.1)
|
|
14677
|
+
*/
|
|
14678
|
+
function applyPostAuth(clientId, clientSecret, params) {
|
|
14679
|
+
params.set("client_id", clientId);
|
|
14680
|
+
if (clientSecret) {
|
|
14681
|
+
params.set("client_secret", clientSecret);
|
|
14682
|
+
}
|
|
14683
|
+
}
|
|
14684
|
+
/**
|
|
14685
|
+
* Applies public client authentication (RFC 6749 Section 2.1)
|
|
14686
|
+
*/
|
|
14687
|
+
function applyPublicAuth(clientId, params) {
|
|
14688
|
+
params.set("client_id", clientId);
|
|
14689
|
+
}
|
|
14690
|
+
/**
|
|
14691
|
+
* Parses an OAuth error response from a string or Response object.
|
|
14692
|
+
*
|
|
14693
|
+
* If the input is a standard OAuth2.0 error response, it will be parsed according to the spec
|
|
14694
|
+
* and an instance of the appropriate OAuthError subclass will be returned.
|
|
14695
|
+
* If parsing fails, it falls back to a generic ServerError that includes
|
|
14696
|
+
* the response status (if available) and original content.
|
|
14697
|
+
*
|
|
14698
|
+
* @param input - A Response object or string containing the error response
|
|
14699
|
+
* @returns A Promise that resolves to an OAuthError instance
|
|
14700
|
+
*/
|
|
14701
|
+
async function parseErrorResponse(input) {
|
|
14702
|
+
const statusCode = input instanceof Response ? input.status : undefined;
|
|
14703
|
+
const body = input instanceof Response ? await input.text() : input;
|
|
14704
|
+
try {
|
|
14705
|
+
const result = OAuthErrorResponseSchema.parse(JSON.parse(body));
|
|
14706
|
+
const { error, error_description, error_uri } = result;
|
|
14707
|
+
const errorClass = OAUTH_ERRORS[error] || ServerError;
|
|
14708
|
+
return new errorClass(error_description || '', error_uri);
|
|
14709
|
+
}
|
|
14710
|
+
catch (error) {
|
|
14711
|
+
// Not a valid OAuth error response, but try to inform the user of the raw data anyway
|
|
14712
|
+
const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ''}Invalid OAuth error response: ${error}. Raw body: ${body}`;
|
|
14713
|
+
return new ServerError(errorMessage);
|
|
14714
|
+
}
|
|
14715
|
+
}
|
|
14270
14716
|
/**
|
|
14271
14717
|
* Orchestrates the full auth flow with a server.
|
|
14272
14718
|
*
|
|
14273
14719
|
* This can be used as a single entry point for all authorization functionality,
|
|
14274
14720
|
* instead of linking together the other lower-level functions in this module.
|
|
14275
14721
|
*/
|
|
14276
|
-
async function auth(provider,
|
|
14722
|
+
async function auth(provider, options) {
|
|
14723
|
+
var _a, _b;
|
|
14724
|
+
try {
|
|
14725
|
+
return await authInternal(provider, options);
|
|
14726
|
+
}
|
|
14727
|
+
catch (error) {
|
|
14728
|
+
// Handle recoverable error types by invalidating credentials and retrying
|
|
14729
|
+
if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) {
|
|
14730
|
+
await ((_a = provider.invalidateCredentials) === null || _a === void 0 ? void 0 : _a.call(provider, 'all'));
|
|
14731
|
+
return await authInternal(provider, options);
|
|
14732
|
+
}
|
|
14733
|
+
else if (error instanceof InvalidGrantError) {
|
|
14734
|
+
await ((_b = provider.invalidateCredentials) === null || _b === void 0 ? void 0 : _b.call(provider, 'tokens'));
|
|
14735
|
+
return await authInternal(provider, options);
|
|
14736
|
+
}
|
|
14737
|
+
// Throw otherwise
|
|
14738
|
+
throw error;
|
|
14739
|
+
}
|
|
14740
|
+
}
|
|
14741
|
+
async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn, }) {
|
|
14277
14742
|
let resourceMetadata;
|
|
14278
|
-
let authorizationServerUrl
|
|
14743
|
+
let authorizationServerUrl;
|
|
14279
14744
|
try {
|
|
14280
|
-
resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl });
|
|
14745
|
+
resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl }, fetchFn);
|
|
14281
14746
|
if (resourceMetadata.authorization_servers && resourceMetadata.authorization_servers.length > 0) {
|
|
14282
14747
|
authorizationServerUrl = resourceMetadata.authorization_servers[0];
|
|
14283
14748
|
}
|
|
@@ -14285,8 +14750,17 @@ async function auth(provider, { serverUrl, authorizationCode, scope, resourceMet
|
|
|
14285
14750
|
catch (_a) {
|
|
14286
14751
|
// Ignore errors and fall back to /.well-known/oauth-authorization-server
|
|
14287
14752
|
}
|
|
14753
|
+
/**
|
|
14754
|
+
* If we don't get a valid authorization server metadata from protected resource metadata,
|
|
14755
|
+
* fallback to the legacy MCP spec's implementation (version 2025-03-26): MCP server acts as the Authorization server.
|
|
14756
|
+
*/
|
|
14757
|
+
if (!authorizationServerUrl) {
|
|
14758
|
+
authorizationServerUrl = serverUrl;
|
|
14759
|
+
}
|
|
14288
14760
|
const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
|
|
14289
|
-
const metadata = await
|
|
14761
|
+
const metadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
14762
|
+
fetchFn,
|
|
14763
|
+
});
|
|
14290
14764
|
// Handle client registration if needed
|
|
14291
14765
|
let clientInformation = await Promise.resolve(provider.clientInformation());
|
|
14292
14766
|
if (!clientInformation) {
|
|
@@ -14299,6 +14773,7 @@ async function auth(provider, { serverUrl, authorizationCode, scope, resourceMet
|
|
|
14299
14773
|
const fullInformation = await registerClient(authorizationServerUrl, {
|
|
14300
14774
|
metadata,
|
|
14301
14775
|
clientMetadata: provider.clientMetadata,
|
|
14776
|
+
fetchFn,
|
|
14302
14777
|
});
|
|
14303
14778
|
await provider.saveClientInformation(fullInformation);
|
|
14304
14779
|
clientInformation = fullInformation;
|
|
@@ -14313,6 +14788,8 @@ async function auth(provider, { serverUrl, authorizationCode, scope, resourceMet
|
|
|
14313
14788
|
codeVerifier,
|
|
14314
14789
|
redirectUri: provider.redirectUrl,
|
|
14315
14790
|
resource,
|
|
14791
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
14792
|
+
fetchFn: fetchFn,
|
|
14316
14793
|
});
|
|
14317
14794
|
await provider.saveTokens(tokens);
|
|
14318
14795
|
return "AUTHORIZED";
|
|
@@ -14327,12 +14804,19 @@ async function auth(provider, { serverUrl, authorizationCode, scope, resourceMet
|
|
|
14327
14804
|
clientInformation,
|
|
14328
14805
|
refreshToken: tokens.refresh_token,
|
|
14329
14806
|
resource,
|
|
14807
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
14808
|
+
fetchFn,
|
|
14330
14809
|
});
|
|
14331
14810
|
await provider.saveTokens(newTokens);
|
|
14332
14811
|
return "AUTHORIZED";
|
|
14333
14812
|
}
|
|
14334
|
-
catch (
|
|
14335
|
-
//
|
|
14813
|
+
catch (error) {
|
|
14814
|
+
// 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.
|
|
14815
|
+
if (!(error instanceof OAuthError) || error instanceof ServerError) ;
|
|
14816
|
+
else {
|
|
14817
|
+
// Refresh failed for another reason, re-throw
|
|
14818
|
+
throw error;
|
|
14819
|
+
}
|
|
14336
14820
|
}
|
|
14337
14821
|
}
|
|
14338
14822
|
const state = provider.state ? await provider.state() : undefined;
|
|
@@ -14396,33 +14880,12 @@ function extractResourceMetadataUrl(res) {
|
|
|
14396
14880
|
* If the server returns a 404 for the well-known endpoint, this function will
|
|
14397
14881
|
* return `undefined`. Any other errors will be thrown as exceptions.
|
|
14398
14882
|
*/
|
|
14399
|
-
async function discoverOAuthProtectedResourceMetadata(serverUrl, opts) {
|
|
14400
|
-
|
|
14401
|
-
|
|
14402
|
-
|
|
14403
|
-
|
|
14404
|
-
|
|
14405
|
-
else {
|
|
14406
|
-
url = new URL("/.well-known/oauth-protected-resource", serverUrl);
|
|
14407
|
-
}
|
|
14408
|
-
let response;
|
|
14409
|
-
try {
|
|
14410
|
-
response = await fetch(url, {
|
|
14411
|
-
headers: {
|
|
14412
|
-
"MCP-Protocol-Version": (_a = opts === null || opts === void 0 ? void 0 : opts.protocolVersion) !== null && _a !== void 0 ? _a : LATEST_PROTOCOL_VERSION
|
|
14413
|
-
}
|
|
14414
|
-
});
|
|
14415
|
-
}
|
|
14416
|
-
catch (error) {
|
|
14417
|
-
// CORS errors come back as TypeError
|
|
14418
|
-
if (error instanceof TypeError) {
|
|
14419
|
-
response = await fetch(url);
|
|
14420
|
-
}
|
|
14421
|
-
else {
|
|
14422
|
-
throw error;
|
|
14423
|
-
}
|
|
14424
|
-
}
|
|
14425
|
-
if (response.status === 404) {
|
|
14883
|
+
async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) {
|
|
14884
|
+
const response = await discoverMetadataWithFallback(serverUrl, 'oauth-protected-resource', fetchFn, {
|
|
14885
|
+
protocolVersion: opts === null || opts === void 0 ? void 0 : opts.protocolVersion,
|
|
14886
|
+
metadataUrl: opts === null || opts === void 0 ? void 0 : opts.resourceMetadataUrl,
|
|
14887
|
+
});
|
|
14888
|
+
if (!response || response.status === 404) {
|
|
14426
14889
|
throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
|
|
14427
14890
|
}
|
|
14428
14891
|
if (!response.ok) {
|
|
@@ -14433,15 +14896,15 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts) {
|
|
|
14433
14896
|
/**
|
|
14434
14897
|
* Helper function to handle fetch with CORS retry logic
|
|
14435
14898
|
*/
|
|
14436
|
-
async function fetchWithCorsRetry(url, headers) {
|
|
14899
|
+
async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
|
|
14437
14900
|
try {
|
|
14438
|
-
return await
|
|
14901
|
+
return await fetchFn(url, { headers });
|
|
14439
14902
|
}
|
|
14440
14903
|
catch (error) {
|
|
14441
14904
|
if (error instanceof TypeError) {
|
|
14442
14905
|
if (headers) {
|
|
14443
14906
|
// CORS errors come back as TypeError, retry without headers
|
|
14444
|
-
return fetchWithCorsRetry(url);
|
|
14907
|
+
return fetchWithCorsRetry(url, undefined, fetchFn);
|
|
14445
14908
|
}
|
|
14446
14909
|
else {
|
|
14447
14910
|
// We're getting CORS errors on retry too, return undefined
|
|
@@ -14452,57 +14915,162 @@ async function fetchWithCorsRetry(url, headers) {
|
|
|
14452
14915
|
}
|
|
14453
14916
|
}
|
|
14454
14917
|
/**
|
|
14455
|
-
* Constructs the well-known path for
|
|
14918
|
+
* Constructs the well-known path for auth-related metadata discovery
|
|
14456
14919
|
*/
|
|
14457
|
-
function buildWellKnownPath(pathname) {
|
|
14458
|
-
|
|
14920
|
+
function buildWellKnownPath(wellKnownPrefix, pathname = '', options = {}) {
|
|
14921
|
+
// Strip trailing slash from pathname to avoid double slashes
|
|
14459
14922
|
if (pathname.endsWith('/')) {
|
|
14460
|
-
|
|
14461
|
-
wellKnownPath = wellKnownPath.slice(0, -1);
|
|
14923
|
+
pathname = pathname.slice(0, -1);
|
|
14462
14924
|
}
|
|
14463
|
-
return
|
|
14925
|
+
return options.prependPathname
|
|
14926
|
+
? `${pathname}/.well-known/${wellKnownPrefix}`
|
|
14927
|
+
: `/.well-known/${wellKnownPrefix}${pathname}`;
|
|
14464
14928
|
}
|
|
14465
14929
|
/**
|
|
14466
14930
|
* Tries to discover OAuth metadata at a specific URL
|
|
14467
14931
|
*/
|
|
14468
|
-
async function tryMetadataDiscovery(url, protocolVersion) {
|
|
14932
|
+
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
|
|
14469
14933
|
const headers = {
|
|
14470
14934
|
"MCP-Protocol-Version": protocolVersion
|
|
14471
14935
|
};
|
|
14472
|
-
return await fetchWithCorsRetry(url, headers);
|
|
14936
|
+
return await fetchWithCorsRetry(url, headers, fetchFn);
|
|
14473
14937
|
}
|
|
14474
14938
|
/**
|
|
14475
14939
|
* Determines if fallback to root discovery should be attempted
|
|
14476
14940
|
*/
|
|
14477
14941
|
function shouldAttemptFallback(response, pathname) {
|
|
14478
|
-
return !response || response.status
|
|
14942
|
+
return !response || (response.status >= 400 && response.status < 500) && pathname !== '/';
|
|
14479
14943
|
}
|
|
14480
14944
|
/**
|
|
14481
|
-
*
|
|
14482
|
-
*
|
|
14483
|
-
* If the server returns a 404 for the well-known endpoint, this function will
|
|
14484
|
-
* return `undefined`. Any other errors will be thrown as exceptions.
|
|
14945
|
+
* Generic function for discovering OAuth metadata with fallback support
|
|
14485
14946
|
*/
|
|
14486
|
-
async function
|
|
14487
|
-
var _a;
|
|
14488
|
-
const issuer = new URL(
|
|
14489
|
-
const protocolVersion = (_a = void 0 ) !== null && _a !== void 0 ? _a : LATEST_PROTOCOL_VERSION;
|
|
14490
|
-
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
let response = await tryMetadataDiscovery(pathAwareUrl, protocolVersion);
|
|
14494
|
-
// If path-aware discovery fails with 404, try fallback to root discovery
|
|
14495
|
-
if (shouldAttemptFallback(response, issuer.pathname)) {
|
|
14496
|
-
const rootUrl = new URL("/.well-known/oauth-authorization-server", issuer);
|
|
14497
|
-
response = await tryMetadataDiscovery(rootUrl, protocolVersion);
|
|
14947
|
+
async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
|
|
14948
|
+
var _a, _b;
|
|
14949
|
+
const issuer = new URL(serverUrl);
|
|
14950
|
+
const protocolVersion = (_a = opts === null || opts === void 0 ? void 0 : opts.protocolVersion) !== null && _a !== void 0 ? _a : LATEST_PROTOCOL_VERSION;
|
|
14951
|
+
let url;
|
|
14952
|
+
if (opts === null || opts === void 0 ? void 0 : opts.metadataUrl) {
|
|
14953
|
+
url = new URL(opts.metadataUrl);
|
|
14498
14954
|
}
|
|
14499
|
-
|
|
14500
|
-
|
|
14955
|
+
else {
|
|
14956
|
+
// Try path-aware discovery first
|
|
14957
|
+
const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname);
|
|
14958
|
+
url = new URL(wellKnownPath, (_b = opts === null || opts === void 0 ? void 0 : opts.metadataServerUrl) !== null && _b !== void 0 ? _b : issuer);
|
|
14959
|
+
url.search = issuer.search;
|
|
14501
14960
|
}
|
|
14502
|
-
|
|
14503
|
-
|
|
14961
|
+
let response = await tryMetadataDiscovery(url, protocolVersion, fetchFn);
|
|
14962
|
+
// If path-aware discovery fails with 404 and we're not already at root, try fallback to root discovery
|
|
14963
|
+
if (!(opts === null || opts === void 0 ? void 0 : opts.metadataUrl) && shouldAttemptFallback(response, issuer.pathname)) {
|
|
14964
|
+
const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
|
|
14965
|
+
response = await tryMetadataDiscovery(rootUrl, protocolVersion, fetchFn);
|
|
14504
14966
|
}
|
|
14505
|
-
return
|
|
14967
|
+
return response;
|
|
14968
|
+
}
|
|
14969
|
+
/**
|
|
14970
|
+
* Builds a list of discovery URLs to try for authorization server metadata.
|
|
14971
|
+
* URLs are returned in priority order:
|
|
14972
|
+
* 1. OAuth metadata at the given URL
|
|
14973
|
+
* 2. OAuth metadata at root (if URL has path)
|
|
14974
|
+
* 3. OIDC metadata endpoints
|
|
14975
|
+
*/
|
|
14976
|
+
function buildDiscoveryUrls(authorizationServerUrl) {
|
|
14977
|
+
const url = typeof authorizationServerUrl === 'string' ? new URL(authorizationServerUrl) : authorizationServerUrl;
|
|
14978
|
+
const hasPath = url.pathname !== '/';
|
|
14979
|
+
const urlsToTry = [];
|
|
14980
|
+
if (!hasPath) {
|
|
14981
|
+
// Root path: https://example.com/.well-known/oauth-authorization-server
|
|
14982
|
+
urlsToTry.push({
|
|
14983
|
+
url: new URL('/.well-known/oauth-authorization-server', url.origin),
|
|
14984
|
+
type: 'oauth'
|
|
14985
|
+
});
|
|
14986
|
+
// OIDC: https://example.com/.well-known/openid-configuration
|
|
14987
|
+
urlsToTry.push({
|
|
14988
|
+
url: new URL(`/.well-known/openid-configuration`, url.origin),
|
|
14989
|
+
type: 'oidc'
|
|
14990
|
+
});
|
|
14991
|
+
return urlsToTry;
|
|
14992
|
+
}
|
|
14993
|
+
// Strip trailing slash from pathname to avoid double slashes
|
|
14994
|
+
let pathname = url.pathname;
|
|
14995
|
+
if (pathname.endsWith('/')) {
|
|
14996
|
+
pathname = pathname.slice(0, -1);
|
|
14997
|
+
}
|
|
14998
|
+
// 1. OAuth metadata at the given URL
|
|
14999
|
+
// Insert well-known before the path: https://example.com/.well-known/oauth-authorization-server/tenant1
|
|
15000
|
+
urlsToTry.push({
|
|
15001
|
+
url: new URL(`/.well-known/oauth-authorization-server${pathname}`, url.origin),
|
|
15002
|
+
type: 'oauth'
|
|
15003
|
+
});
|
|
15004
|
+
// Root path: https://example.com/.well-known/oauth-authorization-server
|
|
15005
|
+
urlsToTry.push({
|
|
15006
|
+
url: new URL('/.well-known/oauth-authorization-server', url.origin),
|
|
15007
|
+
type: 'oauth'
|
|
15008
|
+
});
|
|
15009
|
+
// 3. OIDC metadata endpoints
|
|
15010
|
+
// RFC 8414 style: Insert /.well-known/openid-configuration before the path
|
|
15011
|
+
urlsToTry.push({
|
|
15012
|
+
url: new URL(`/.well-known/openid-configuration${pathname}`, url.origin),
|
|
15013
|
+
type: 'oidc'
|
|
15014
|
+
});
|
|
15015
|
+
// OIDC Discovery 1.0 style: Append /.well-known/openid-configuration after the path
|
|
15016
|
+
urlsToTry.push({
|
|
15017
|
+
url: new URL(`${pathname}/.well-known/openid-configuration`, url.origin),
|
|
15018
|
+
type: 'oidc'
|
|
15019
|
+
});
|
|
15020
|
+
return urlsToTry;
|
|
15021
|
+
}
|
|
15022
|
+
/**
|
|
15023
|
+
* Discovers authorization server metadata with support for RFC 8414 OAuth 2.0 Authorization Server Metadata
|
|
15024
|
+
* and OpenID Connect Discovery 1.0 specifications.
|
|
15025
|
+
*
|
|
15026
|
+
* This function implements a fallback strategy for authorization server discovery:
|
|
15027
|
+
* 1. Attempts RFC 8414 OAuth metadata discovery first
|
|
15028
|
+
* 2. If OAuth discovery fails, falls back to OpenID Connect Discovery
|
|
15029
|
+
*
|
|
15030
|
+
* @param authorizationServerUrl - The authorization server URL obtained from the MCP Server's
|
|
15031
|
+
* protected resource metadata, or the MCP server's URL if the
|
|
15032
|
+
* metadata was not found.
|
|
15033
|
+
* @param options - Configuration options
|
|
15034
|
+
* @param options.fetchFn - Optional fetch function for making HTTP requests, defaults to global fetch
|
|
15035
|
+
* @param options.protocolVersion - MCP protocol version to use, defaults to LATEST_PROTOCOL_VERSION
|
|
15036
|
+
* @returns Promise resolving to authorization server metadata, or undefined if discovery fails
|
|
15037
|
+
*/
|
|
15038
|
+
async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = LATEST_PROTOCOL_VERSION, } = {}) {
|
|
15039
|
+
var _a;
|
|
15040
|
+
const headers = { 'MCP-Protocol-Version': protocolVersion };
|
|
15041
|
+
// Get the list of URLs to try
|
|
15042
|
+
const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
|
|
15043
|
+
// Try each URL in order
|
|
15044
|
+
for (const { url: endpointUrl, type } of urlsToTry) {
|
|
15045
|
+
const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn);
|
|
15046
|
+
if (!response) {
|
|
15047
|
+
/**
|
|
15048
|
+
* CORS error occurred - don't throw as the endpoint may not allow CORS,
|
|
15049
|
+
* continue trying other possible endpoints
|
|
15050
|
+
*/
|
|
15051
|
+
continue;
|
|
15052
|
+
}
|
|
15053
|
+
if (!response.ok) {
|
|
15054
|
+
// Continue looking for any 4xx response code.
|
|
15055
|
+
if (response.status >= 400 && response.status < 500) {
|
|
15056
|
+
continue; // Try next URL
|
|
15057
|
+
}
|
|
15058
|
+
throw new Error(`HTTP ${response.status} trying to load ${type === 'oauth' ? 'OAuth' : 'OpenID provider'} metadata from ${endpointUrl}`);
|
|
15059
|
+
}
|
|
15060
|
+
// Parse and validate based on type
|
|
15061
|
+
if (type === 'oauth') {
|
|
15062
|
+
return OAuthMetadataSchema.parse(await response.json());
|
|
15063
|
+
}
|
|
15064
|
+
else {
|
|
15065
|
+
const metadata = OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
|
|
15066
|
+
// MCP spec requires OIDC providers to support S256 PKCE
|
|
15067
|
+
if (!((_a = metadata.code_challenge_methods_supported) === null || _a === void 0 ? void 0 : _a.includes('S256'))) {
|
|
15068
|
+
throw new Error(`Incompatible OIDC provider at ${endpointUrl}: does not support S256 code challenge method required by MCP specification`);
|
|
15069
|
+
}
|
|
15070
|
+
return metadata;
|
|
15071
|
+
}
|
|
15072
|
+
}
|
|
15073
|
+
return undefined;
|
|
14506
15074
|
}
|
|
14507
15075
|
/**
|
|
14508
15076
|
* Begins the authorization flow with the given server, by generating a PKCE challenge and constructing the authorization URL.
|
|
@@ -14539,6 +15107,12 @@ async function startAuthorization(authorizationServerUrl, { metadata, clientInfo
|
|
|
14539
15107
|
if (scope) {
|
|
14540
15108
|
authorizationUrl.searchParams.set("scope", scope);
|
|
14541
15109
|
}
|
|
15110
|
+
if (scope === null || scope === void 0 ? void 0 : scope.includes("offline_access")) {
|
|
15111
|
+
// if the request includes the OIDC-only "offline_access" scope,
|
|
15112
|
+
// we need to set the prompt to "consent" to ensure the user is prompted to grant offline access
|
|
15113
|
+
// https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
|
15114
|
+
authorizationUrl.searchParams.append("prompt", "consent");
|
|
15115
|
+
}
|
|
14542
15116
|
if (resource) {
|
|
14543
15117
|
authorizationUrl.searchParams.set("resource", resource.href);
|
|
14544
15118
|
}
|
|
@@ -14546,50 +15120,73 @@ async function startAuthorization(authorizationServerUrl, { metadata, clientInfo
|
|
|
14546
15120
|
}
|
|
14547
15121
|
/**
|
|
14548
15122
|
* Exchanges an authorization code for an access token with the given server.
|
|
15123
|
+
*
|
|
15124
|
+
* Supports multiple client authentication methods as specified in OAuth 2.1:
|
|
15125
|
+
* - Automatically selects the best authentication method based on server support
|
|
15126
|
+
* - Falls back to appropriate defaults when server metadata is unavailable
|
|
15127
|
+
*
|
|
15128
|
+
* @param authorizationServerUrl - The authorization server's base URL
|
|
15129
|
+
* @param options - Configuration object containing client info, auth code, etc.
|
|
15130
|
+
* @returns Promise resolving to OAuth tokens
|
|
15131
|
+
* @throws {Error} When token exchange fails or authentication is invalid
|
|
14549
15132
|
*/
|
|
14550
|
-
async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, }) {
|
|
15133
|
+
async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn, }) {
|
|
15134
|
+
var _a;
|
|
14551
15135
|
const grantType = "authorization_code";
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
14556
|
-
|
|
14557
|
-
|
|
14558
|
-
}
|
|
14559
|
-
}
|
|
14560
|
-
else {
|
|
14561
|
-
tokenUrl = new URL("/token", authorizationServerUrl);
|
|
15136
|
+
const tokenUrl = (metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint)
|
|
15137
|
+
? new URL(metadata.token_endpoint)
|
|
15138
|
+
: new URL("/token", authorizationServerUrl);
|
|
15139
|
+
if ((metadata === null || metadata === void 0 ? void 0 : metadata.grant_types_supported) &&
|
|
15140
|
+
!metadata.grant_types_supported.includes(grantType)) {
|
|
15141
|
+
throw new Error(`Incompatible auth server: does not support grant type ${grantType}`);
|
|
14562
15142
|
}
|
|
14563
15143
|
// Exchange code for tokens
|
|
15144
|
+
const headers = new Headers({
|
|
15145
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
15146
|
+
"Accept": "application/json",
|
|
15147
|
+
});
|
|
14564
15148
|
const params = new URLSearchParams({
|
|
14565
15149
|
grant_type: grantType,
|
|
14566
|
-
client_id: clientInformation.client_id,
|
|
14567
15150
|
code: authorizationCode,
|
|
14568
15151
|
code_verifier: codeVerifier,
|
|
14569
15152
|
redirect_uri: String(redirectUri),
|
|
14570
15153
|
});
|
|
14571
|
-
if (
|
|
14572
|
-
|
|
15154
|
+
if (addClientAuthentication) {
|
|
15155
|
+
addClientAuthentication(headers, params, authorizationServerUrl, metadata);
|
|
15156
|
+
}
|
|
15157
|
+
else {
|
|
15158
|
+
// Determine and apply client authentication method
|
|
15159
|
+
const supportedMethods = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint_auth_methods_supported) !== null && _a !== void 0 ? _a : [];
|
|
15160
|
+
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
|
|
15161
|
+
applyClientAuthentication(authMethod, clientInformation, headers, params);
|
|
14573
15162
|
}
|
|
14574
15163
|
if (resource) {
|
|
14575
15164
|
params.set("resource", resource.href);
|
|
14576
15165
|
}
|
|
14577
|
-
const response = await fetch(tokenUrl, {
|
|
15166
|
+
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(tokenUrl, {
|
|
14578
15167
|
method: "POST",
|
|
14579
|
-
headers
|
|
14580
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
14581
|
-
},
|
|
15168
|
+
headers,
|
|
14582
15169
|
body: params,
|
|
14583
15170
|
});
|
|
14584
15171
|
if (!response.ok) {
|
|
14585
|
-
throw
|
|
15172
|
+
throw await parseErrorResponse(response);
|
|
14586
15173
|
}
|
|
14587
15174
|
return OAuthTokensSchema.parse(await response.json());
|
|
14588
15175
|
}
|
|
14589
15176
|
/**
|
|
14590
15177
|
* Exchange a refresh token for an updated access token.
|
|
15178
|
+
*
|
|
15179
|
+
* Supports multiple client authentication methods as specified in OAuth 2.1:
|
|
15180
|
+
* - Automatically selects the best authentication method based on server support
|
|
15181
|
+
* - Preserves the original refresh token if a new one is not returned
|
|
15182
|
+
*
|
|
15183
|
+
* @param authorizationServerUrl - The authorization server's base URL
|
|
15184
|
+
* @param options - Configuration object containing client info, refresh token, etc.
|
|
15185
|
+
* @returns Promise resolving to OAuth tokens (preserves original refresh_token if not replaced)
|
|
15186
|
+
* @throws {Error} When token refresh fails or authentication is invalid
|
|
14591
15187
|
*/
|
|
14592
|
-
async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, }) {
|
|
15188
|
+
async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn, }) {
|
|
15189
|
+
var _a;
|
|
14593
15190
|
const grantType = "refresh_token";
|
|
14594
15191
|
let tokenUrl;
|
|
14595
15192
|
if (metadata) {
|
|
@@ -14603,33 +15200,39 @@ async function refreshAuthorization(authorizationServerUrl, { metadata, clientIn
|
|
|
14603
15200
|
tokenUrl = new URL("/token", authorizationServerUrl);
|
|
14604
15201
|
}
|
|
14605
15202
|
// Exchange refresh token
|
|
15203
|
+
const headers = new Headers({
|
|
15204
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
15205
|
+
});
|
|
14606
15206
|
const params = new URLSearchParams({
|
|
14607
15207
|
grant_type: grantType,
|
|
14608
|
-
client_id: clientInformation.client_id,
|
|
14609
15208
|
refresh_token: refreshToken,
|
|
14610
15209
|
});
|
|
14611
|
-
if (
|
|
14612
|
-
|
|
15210
|
+
if (addClientAuthentication) {
|
|
15211
|
+
addClientAuthentication(headers, params, authorizationServerUrl, metadata);
|
|
15212
|
+
}
|
|
15213
|
+
else {
|
|
15214
|
+
// Determine and apply client authentication method
|
|
15215
|
+
const supportedMethods = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint_auth_methods_supported) !== null && _a !== void 0 ? _a : [];
|
|
15216
|
+
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
|
|
15217
|
+
applyClientAuthentication(authMethod, clientInformation, headers, params);
|
|
14613
15218
|
}
|
|
14614
15219
|
if (resource) {
|
|
14615
15220
|
params.set("resource", resource.href);
|
|
14616
15221
|
}
|
|
14617
|
-
const response = await fetch(tokenUrl, {
|
|
15222
|
+
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(tokenUrl, {
|
|
14618
15223
|
method: "POST",
|
|
14619
|
-
headers
|
|
14620
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
14621
|
-
},
|
|
15224
|
+
headers,
|
|
14622
15225
|
body: params,
|
|
14623
15226
|
});
|
|
14624
15227
|
if (!response.ok) {
|
|
14625
|
-
throw
|
|
15228
|
+
throw await parseErrorResponse(response);
|
|
14626
15229
|
}
|
|
14627
15230
|
return OAuthTokensSchema.parse({ refresh_token: refreshToken, ...(await response.json()) });
|
|
14628
15231
|
}
|
|
14629
15232
|
/**
|
|
14630
15233
|
* Performs OAuth 2.0 Dynamic Client Registration according to RFC 7591.
|
|
14631
15234
|
*/
|
|
14632
|
-
async function registerClient(authorizationServerUrl, { metadata, clientMetadata, }) {
|
|
15235
|
+
async function registerClient(authorizationServerUrl, { metadata, clientMetadata, fetchFn, }) {
|
|
14633
15236
|
let registrationUrl;
|
|
14634
15237
|
if (metadata) {
|
|
14635
15238
|
if (!metadata.registration_endpoint) {
|
|
@@ -14640,7 +15243,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
|
|
|
14640
15243
|
else {
|
|
14641
15244
|
registrationUrl = new URL("/register", authorizationServerUrl);
|
|
14642
15245
|
}
|
|
14643
|
-
const response = await fetch(registrationUrl, {
|
|
15246
|
+
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(registrationUrl, {
|
|
14644
15247
|
method: "POST",
|
|
14645
15248
|
headers: {
|
|
14646
15249
|
"Content-Type": "application/json",
|
|
@@ -14648,7 +15251,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
|
|
|
14648
15251
|
body: JSON.stringify(clientMetadata),
|
|
14649
15252
|
});
|
|
14650
15253
|
if (!response.ok) {
|
|
14651
|
-
throw
|
|
15254
|
+
throw await parseErrorResponse(response);
|
|
14652
15255
|
}
|
|
14653
15256
|
return OAuthClientInformationFullSchema.parse(await response.json());
|
|
14654
15257
|
}
|
|
@@ -14743,7 +15346,7 @@ function splitLines(chunk) {
|
|
|
14743
15346
|
const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
|
|
14744
15347
|
`, searchIndex);
|
|
14745
15348
|
let lineEnd = -1;
|
|
14746
|
-
if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = Math.min(crIndex, lfIndex) : crIndex !== -1 ? lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) {
|
|
15349
|
+
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) {
|
|
14747
15350
|
incompleteLine = chunk.slice(searchIndex);
|
|
14748
15351
|
break;
|
|
14749
15352
|
} else {
|
|
@@ -14814,7 +15417,7 @@ class StreamableHTTPClientTransport {
|
|
|
14814
15417
|
}
|
|
14815
15418
|
let result;
|
|
14816
15419
|
try {
|
|
14817
|
-
result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl });
|
|
15420
|
+
result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
|
|
14818
15421
|
}
|
|
14819
15422
|
catch (error) {
|
|
14820
15423
|
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
@@ -14875,7 +15478,7 @@ class StreamableHTTPClientTransport {
|
|
|
14875
15478
|
}
|
|
14876
15479
|
throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
|
|
14877
15480
|
}
|
|
14878
|
-
this._handleSseStream(response.body, options);
|
|
15481
|
+
this._handleSseStream(response.body, options, true);
|
|
14879
15482
|
}
|
|
14880
15483
|
catch (error) {
|
|
14881
15484
|
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
|
|
@@ -14935,7 +15538,7 @@ class StreamableHTTPClientTransport {
|
|
|
14935
15538
|
});
|
|
14936
15539
|
}, delay);
|
|
14937
15540
|
}
|
|
14938
|
-
_handleSseStream(stream, options) {
|
|
15541
|
+
_handleSseStream(stream, options, isReconnectable) {
|
|
14939
15542
|
if (!stream) {
|
|
14940
15543
|
return;
|
|
14941
15544
|
}
|
|
@@ -14979,19 +15582,19 @@ class StreamableHTTPClientTransport {
|
|
|
14979
15582
|
// Handle stream errors - likely a network disconnect
|
|
14980
15583
|
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, new Error(`SSE stream disconnected: ${error}`));
|
|
14981
15584
|
// Attempt to reconnect if the stream disconnects unexpectedly and we aren't closing
|
|
14982
|
-
if (
|
|
15585
|
+
if (isReconnectable &&
|
|
15586
|
+
this._abortController &&
|
|
15587
|
+
!this._abortController.signal.aborted) {
|
|
14983
15588
|
// Use the exponential backoff reconnection strategy
|
|
14984
|
-
|
|
14985
|
-
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
|
|
14994
|
-
}
|
|
15589
|
+
try {
|
|
15590
|
+
this._scheduleReconnection({
|
|
15591
|
+
resumptionToken: lastEventId,
|
|
15592
|
+
onresumptiontoken,
|
|
15593
|
+
replayMessageId
|
|
15594
|
+
}, 0);
|
|
15595
|
+
}
|
|
15596
|
+
catch (error) {
|
|
15597
|
+
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
|
|
14995
15598
|
}
|
|
14996
15599
|
}
|
|
14997
15600
|
}
|
|
@@ -15011,7 +15614,7 @@ class StreamableHTTPClientTransport {
|
|
|
15011
15614
|
if (!this._authProvider) {
|
|
15012
15615
|
throw new UnauthorizedError("No auth provider");
|
|
15013
15616
|
}
|
|
15014
|
-
const result = await auth(this._authProvider, { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl });
|
|
15617
|
+
const result = await auth(this._authProvider, { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
|
|
15015
15618
|
if (result !== "AUTHORIZED") {
|
|
15016
15619
|
throw new UnauthorizedError("Failed to authorize");
|
|
15017
15620
|
}
|
|
@@ -15050,7 +15653,7 @@ class StreamableHTTPClientTransport {
|
|
|
15050
15653
|
if (!response.ok) {
|
|
15051
15654
|
if (response.status === 401 && this._authProvider) {
|
|
15052
15655
|
this._resourceMetadataUrl = extractResourceMetadataUrl(response);
|
|
15053
|
-
const result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl });
|
|
15656
|
+
const result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
|
|
15054
15657
|
if (result !== "AUTHORIZED") {
|
|
15055
15658
|
throw new UnauthorizedError();
|
|
15056
15659
|
}
|
|
@@ -15080,7 +15683,7 @@ class StreamableHTTPClientTransport {
|
|
|
15080
15683
|
// Handle SSE stream responses for requests
|
|
15081
15684
|
// We use the same handler as standalone streams, which now supports
|
|
15082
15685
|
// reconnection with the last event ID
|
|
15083
|
-
this._handleSseStream(response.body, { onresumptiontoken });
|
|
15686
|
+
this._handleSseStream(response.body, { onresumptiontoken }, false);
|
|
15084
15687
|
}
|
|
15085
15688
|
else if (contentType === null || contentType === void 0 ? void 0 : contentType.includes("application/json")) {
|
|
15086
15689
|
// For non-streaming servers, we might get direct JSON responses
|