@codelitdev/oauth-server-kit 0.1.0-alpha.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.
@@ -0,0 +1,17 @@
1
+ import { O as OAuthResourceClient, A as AuthenticationResult } from './types-DJxCmhYu.js';
2
+ export { a as AuthenticatedIdentity } from './types-DJxCmhYu.js';
3
+ import '@better-auth/oauth-provider/resource-client';
4
+
5
+ interface VerifyOAuthAccessTokenOptions {
6
+ oauthResourceClient: OAuthResourceClient;
7
+ issuer: string;
8
+ audiences: readonly string[];
9
+ resourceMetadataMappings?: Readonly<Record<string, string>>;
10
+ }
11
+ /**
12
+ * Verifies a Better Auth OAuth access token and returns protocol identity only.
13
+ * Product claims are deliberately discarded.
14
+ */
15
+ declare function verifyOAuthAccessToken(options: VerifyOAuthAccessTokenOptions, token: string): Promise<AuthenticationResult>;
16
+
17
+ export { AuthenticationResult, OAuthResourceClient, type VerifyOAuthAccessTokenOptions, verifyOAuthAccessToken };
package/dist/index.js ADDED
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
9
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
+ var __spreadValues = (a, b) => {
11
+ for (var prop in b || (b = {}))
12
+ if (__hasOwnProp.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ if (__getOwnPropSymbols)
15
+ for (var prop of __getOwnPropSymbols(b)) {
16
+ if (__propIsEnum.call(b, prop))
17
+ __defNormalProp(a, prop, b[prop]);
18
+ }
19
+ return a;
20
+ };
21
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
22
+
23
+ // src/verify-access-token.ts
24
+ function requireAbsoluteUrl(value, field) {
25
+ let url;
26
+ try {
27
+ url = new URL(value);
28
+ } catch (e) {
29
+ throw new Error(`${field} must be an absolute URL`);
30
+ }
31
+ if (!["http:", "https:"].includes(url.protocol)) {
32
+ throw new Error(`${field} must use http or https`);
33
+ }
34
+ if (process.env.NODE_ENV === "production" && url.protocol !== "https:") {
35
+ throw new Error(`${field} must use https in production`);
36
+ }
37
+ return url.toString().replace(/\/$/, "");
38
+ }
39
+ function normalizedUniqueStrings(values) {
40
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
41
+ }
42
+ function claimStrings(value) {
43
+ if (typeof value === "string") return [value];
44
+ if (!Array.isArray(value)) return [];
45
+ return value.filter((item) => typeof item === "string");
46
+ }
47
+ function optionalString(value) {
48
+ return typeof value === "string" && value.length > 0 ? value : void 0;
49
+ }
50
+ function validateOAuthVerificationOptions(options) {
51
+ var _a;
52
+ requireAbsoluteUrl(options.issuer, "issuer");
53
+ if (normalizedUniqueStrings(options.audiences).length === 0) {
54
+ throw new Error("audiences must contain at least one value");
55
+ }
56
+ for (const value of Object.values((_a = options.resourceMetadataMappings) != null ? _a : {})) {
57
+ requireAbsoluteUrl(value, "resourceMetadataMappings");
58
+ }
59
+ }
60
+ async function verifyOAuthAccessToken(options, token) {
61
+ var _a, _b;
62
+ validateOAuthVerificationOptions(options);
63
+ const issuer = requireAbsoluteUrl(options.issuer, "issuer");
64
+ const audiences = normalizedUniqueStrings(options.audiences);
65
+ if (!token.trim()) return { status: "invalid_token" };
66
+ try {
67
+ const claims = await options.oauthResourceClient.getActions().verifyAccessToken(token, {
68
+ verifyOptions: { issuer, audience: audiences },
69
+ resourceMetadataMappings: (_a = options.resourceMetadataMappings) != null ? _a : {}
70
+ });
71
+ const subject = optionalString(claims.sub);
72
+ const claimIssuer = optionalString(claims.iss);
73
+ const clientId = (_b = optionalString(claims.azp)) != null ? _b : optionalString(claims.client_id);
74
+ const tokenAudiences = normalizedUniqueStrings(
75
+ claimStrings(claims.aud)
76
+ );
77
+ const validatedAudiences = tokenAudiences.filter(
78
+ (audience) => audiences.includes(audience)
79
+ );
80
+ if (!subject || (claimIssuer == null ? void 0 : claimIssuer.replace(/\/$/, "")) !== issuer || !clientId || validatedAudiences.length === 0) {
81
+ return { status: "invalid_token" };
82
+ }
83
+ const scopeValue = claims.scope;
84
+ const scopes = normalizedUniqueStrings(
85
+ Array.isArray(scopeValue) ? claimStrings(scopeValue) : typeof scopeValue === "string" ? scopeValue.split(/\s+/) : []
86
+ );
87
+ const email = optionalString(claims.email);
88
+ const name = optionalString(claims.name);
89
+ return {
90
+ status: "authenticated",
91
+ identity: __spreadProps(__spreadValues(__spreadValues({
92
+ method: "oauth",
93
+ issuer,
94
+ subject
95
+ }, email ? { email } : {}), name ? { name } : {}), {
96
+ clientId,
97
+ scopes,
98
+ audiences: validatedAudiences
99
+ })
100
+ };
101
+ } catch (e) {
102
+ return { status: "invalid_token" };
103
+ }
104
+ }
105
+
106
+ exports.verifyOAuthAccessToken = verifyOAuthAccessToken;
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ export { verifyOAuthAccessToken } from './chunk-RJ4ABQXG.mjs';
2
+ import './chunk-I4E63NIC.mjs';
package/dist/mcp.d.mts ADDED
@@ -0,0 +1,24 @@
1
+ import { Router } from 'express';
2
+ import { O as OAuthResourceClient } from './types-DJxCmhYu.mjs';
3
+ import '@better-auth/oauth-provider/resource-client';
4
+
5
+ interface BetterAuthMetadataApi {
6
+ options: {
7
+ baseURL?: string;
8
+ basePath?: string;
9
+ };
10
+ api: {
11
+ getOAuthServerConfig: (...args: never[]) => unknown;
12
+ getOpenIdConfig: (...args: never[]) => unknown;
13
+ };
14
+ }
15
+ interface CreateMcpOAuthDiscoveryRoutesOptions {
16
+ auth: BetterAuthMetadataApi;
17
+ oauthResourceClient: OAuthResourceClient;
18
+ resourceUrl: string;
19
+ scopesSupported: readonly string[];
20
+ allowedOrigins: readonly string[] | "*";
21
+ }
22
+ declare function createMcpOAuthDiscoveryRoutes(rawOptions: CreateMcpOAuthDiscoveryRoutesOptions): Router;
23
+
24
+ export { type BetterAuthMetadataApi, type CreateMcpOAuthDiscoveryRoutesOptions, createMcpOAuthDiscoveryRoutes };
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { Router } from 'express';
2
+ import { O as OAuthResourceClient } from './types-DJxCmhYu.js';
3
+ import '@better-auth/oauth-provider/resource-client';
4
+
5
+ interface BetterAuthMetadataApi {
6
+ options: {
7
+ baseURL?: string;
8
+ basePath?: string;
9
+ };
10
+ api: {
11
+ getOAuthServerConfig: (...args: never[]) => unknown;
12
+ getOpenIdConfig: (...args: never[]) => unknown;
13
+ };
14
+ }
15
+ interface CreateMcpOAuthDiscoveryRoutesOptions {
16
+ auth: BetterAuthMetadataApi;
17
+ oauthResourceClient: OAuthResourceClient;
18
+ resourceUrl: string;
19
+ scopesSupported: readonly string[];
20
+ allowedOrigins: readonly string[] | "*";
21
+ }
22
+ declare function createMcpOAuthDiscoveryRoutes(rawOptions: CreateMcpOAuthDiscoveryRoutesOptions): Router;
23
+
24
+ export { type BetterAuthMetadataApi, type CreateMcpOAuthDiscoveryRoutesOptions, createMcpOAuthDiscoveryRoutes };
package/dist/mcp.js ADDED
@@ -0,0 +1,228 @@
1
+ 'use strict';
2
+
3
+ var oauthProvider = require('@better-auth/oauth-provider');
4
+ var express = require('express');
5
+
6
+ var __defProp = Object.defineProperty;
7
+ var __defProps = Object.defineProperties;
8
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
9
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ function normalizeOptions(options) {
26
+ let resource;
27
+ try {
28
+ resource = new URL(options.resourceUrl);
29
+ } catch (e) {
30
+ throw new Error("resourceUrl must be an absolute URL");
31
+ }
32
+ if (!["http:", "https:"].includes(resource.protocol)) {
33
+ throw new Error("resourceUrl must use http or https");
34
+ }
35
+ if (resource.username || resource.password || resource.search || resource.hash) {
36
+ throw new Error(
37
+ "resourceUrl must not include credentials, query, or fragment"
38
+ );
39
+ }
40
+ if (process.env.NODE_ENV === "production" && resource.protocol !== "https:") {
41
+ throw new Error("resourceUrl must use https in production");
42
+ }
43
+ if (!options.auth.options.baseURL) {
44
+ throw new Error("auth.options.baseURL must be configured");
45
+ }
46
+ let authorizationServerBase;
47
+ try {
48
+ authorizationServerBase = new URL(options.auth.options.baseURL);
49
+ } catch (e) {
50
+ throw new Error("auth.options.baseURL must be an absolute URL");
51
+ }
52
+ if (!["http:", "https:"].includes(authorizationServerBase.protocol)) {
53
+ throw new Error("auth.options.baseURL must use http or https");
54
+ }
55
+ if (authorizationServerBase.username || authorizationServerBase.password || authorizationServerBase.pathname !== "/" || authorizationServerBase.search || authorizationServerBase.hash) {
56
+ throw new Error("auth.options.baseURL must be an origin URL");
57
+ }
58
+ if (process.env.NODE_ENV === "production" && authorizationServerBase.protocol !== "https:") {
59
+ throw new Error("auth.options.baseURL must use https in production");
60
+ }
61
+ const rawAuthorizationServerPath = options.auth.options.basePath || "/api/auth";
62
+ if (!rawAuthorizationServerPath.startsWith("/") || rawAuthorizationServerPath.startsWith("//") || rawAuthorizationServerPath.includes("?") || rawAuthorizationServerPath.includes("#") || /\s/.test(rawAuthorizationServerPath)) {
63
+ throw new Error("auth.options.basePath must be an absolute path");
64
+ }
65
+ const authorizationServerPath = rawAuthorizationServerPath === "/" ? "" : rawAuthorizationServerPath.replace(/\/+$/, "");
66
+ const authorizationServerUrl = `${authorizationServerBase.toString().replace(/\/$/, "")}${authorizationServerPath}`;
67
+ const scopesSupported = [
68
+ ...new Set(
69
+ options.scopesSupported.map((scope) => scope.trim()).filter(Boolean)
70
+ )
71
+ ];
72
+ const allowedOrigins = options.allowedOrigins === "*" ? "*" : new Set(
73
+ options.allowedOrigins.map((origin) => {
74
+ let url;
75
+ try {
76
+ url = new URL(origin);
77
+ } catch (e) {
78
+ throw new Error(
79
+ "allowedOrigins must contain absolute origins"
80
+ );
81
+ }
82
+ if (!["http:", "https:"].includes(url.protocol)) {
83
+ throw new Error(
84
+ "allowedOrigins must use http or https"
85
+ );
86
+ }
87
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
88
+ throw new Error(
89
+ "allowedOrigins must contain origins without paths"
90
+ );
91
+ }
92
+ if (process.env.NODE_ENV === "production" && url.protocol !== "https:") {
93
+ throw new Error(
94
+ "allowedOrigins must use https in production"
95
+ );
96
+ }
97
+ return url.origin;
98
+ })
99
+ );
100
+ if (allowedOrigins !== "*" && allowedOrigins.size === 0) {
101
+ throw new Error("allowedOrigins must not be empty");
102
+ }
103
+ return __spreadProps(__spreadValues({}, options), {
104
+ resourceUrl: resource.toString(),
105
+ scopesSupported,
106
+ allowedOrigins,
107
+ authorizationServerUrl,
108
+ authorizationServerPath
109
+ });
110
+ }
111
+ function applyCors(request, response, allowedOrigins) {
112
+ const origin = request.headers.origin;
113
+ if (allowedOrigins === "*") {
114
+ response.header("Access-Control-Allow-Origin", "*");
115
+ } else if (origin && allowedOrigins.has(origin)) {
116
+ response.header("Access-Control-Allow-Origin", origin);
117
+ response.header("Vary", "Origin");
118
+ } else if (origin) {
119
+ return false;
120
+ }
121
+ response.header("Access-Control-Allow-Methods", "GET, OPTIONS");
122
+ response.header(
123
+ "Access-Control-Allow-Headers",
124
+ "Accept, Authorization, Content-Type, Mcp-Protocol-Version, Mcp-Session-Id"
125
+ );
126
+ response.header("Access-Control-Expose-Headers", "Mcp-Session-Id");
127
+ return true;
128
+ }
129
+ async function sendFetchResponse(response, fetchResponse) {
130
+ response.status(fetchResponse.status);
131
+ fetchResponse.headers.forEach(
132
+ (value, key) => response.setHeader(key, value)
133
+ );
134
+ response.send(await fetchResponse.text());
135
+ }
136
+ function asyncHandler(handler) {
137
+ return (request, response, next) => {
138
+ void handler(request, response).catch(next);
139
+ };
140
+ }
141
+ function createMcpOAuthDiscoveryRoutes(rawOptions) {
142
+ const options = normalizeOptions(rawOptions);
143
+ const router = express.Router();
144
+ const authorizationServerPaths = /* @__PURE__ */ new Set([
145
+ "/.well-known/oauth-authorization-server",
146
+ `/.well-known/oauth-authorization-server${options.authorizationServerPath}`
147
+ ]);
148
+ const openIdPaths = /* @__PURE__ */ new Set([
149
+ "/.well-known/openid-configuration",
150
+ `${options.authorizationServerPath}/.well-known/openid-configuration`
151
+ ]);
152
+ const resourcePath = new URL(options.resourceUrl).pathname.replace(
153
+ /\/$/,
154
+ ""
155
+ );
156
+ const protectedResourcePaths = /* @__PURE__ */ new Set([
157
+ "/.well-known/oauth-protected-resource",
158
+ `/.well-known/oauth-protected-resource${resourcePath}`
159
+ ]);
160
+ const metadataPaths = /* @__PURE__ */ new Set([
161
+ ...authorizationServerPaths,
162
+ ...openIdPaths,
163
+ ...protectedResourcePaths
164
+ ]);
165
+ router.use((request, response, next) => {
166
+ if (!metadataPaths.has(request.path)) {
167
+ next();
168
+ return;
169
+ }
170
+ if (!applyCors(request, response, options.allowedOrigins)) {
171
+ response.status(403).json({ error: "origin_not_allowed" });
172
+ return;
173
+ }
174
+ if (request.method === "OPTIONS") {
175
+ response.status(204).end();
176
+ return;
177
+ }
178
+ next();
179
+ });
180
+ async function authorizationServerMetadata(request, response) {
181
+ const handler = oauthProvider.oauthProviderAuthServerMetadata(options.auth);
182
+ await sendFetchResponse(
183
+ response,
184
+ await handler(
185
+ new Request(
186
+ `${request.protocol}://${request.get("host")}${request.originalUrl}`,
187
+ { headers: request.headers }
188
+ )
189
+ )
190
+ );
191
+ }
192
+ async function openIdMetadata(request, response) {
193
+ const handler = oauthProvider.oauthProviderOpenIdConfigMetadata(options.auth);
194
+ await sendFetchResponse(
195
+ response,
196
+ await handler(
197
+ new Request(
198
+ `${request.protocol}://${request.get("host")}${request.originalUrl}`,
199
+ { headers: request.headers }
200
+ )
201
+ )
202
+ );
203
+ }
204
+ for (const path of authorizationServerPaths) {
205
+ router.get(path, asyncHandler(authorizationServerMetadata));
206
+ }
207
+ for (const path of openIdPaths) {
208
+ router.get(path, asyncHandler(openIdMetadata));
209
+ }
210
+ async function protectedResourceMetadata(_request, response) {
211
+ const metadata = await options.oauthResourceClient.getActions().getProtectedResourceMetadata(
212
+ {
213
+ resource: options.resourceUrl,
214
+ authorization_servers: [options.authorizationServerUrl],
215
+ scopes_supported: options.scopesSupported,
216
+ bearer_methods_supported: ["header"]
217
+ },
218
+ { silenceWarnings: { oidcScopes: true } }
219
+ );
220
+ response.json(metadata);
221
+ }
222
+ for (const path of protectedResourcePaths) {
223
+ router.get(path, asyncHandler(protectedResourceMetadata));
224
+ }
225
+ return router;
226
+ }
227
+
228
+ exports.createMcpOAuthDiscoveryRoutes = createMcpOAuthDiscoveryRoutes;
package/dist/mcp.mjs ADDED
@@ -0,0 +1,208 @@
1
+ import { __spreadProps, __spreadValues } from './chunk-I4E63NIC.mjs';
2
+ import { oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata } from '@better-auth/oauth-provider';
3
+ import { Router } from 'express';
4
+
5
+ function normalizeOptions(options) {
6
+ let resource;
7
+ try {
8
+ resource = new URL(options.resourceUrl);
9
+ } catch (e) {
10
+ throw new Error("resourceUrl must be an absolute URL");
11
+ }
12
+ if (!["http:", "https:"].includes(resource.protocol)) {
13
+ throw new Error("resourceUrl must use http or https");
14
+ }
15
+ if (resource.username || resource.password || resource.search || resource.hash) {
16
+ throw new Error(
17
+ "resourceUrl must not include credentials, query, or fragment"
18
+ );
19
+ }
20
+ if (process.env.NODE_ENV === "production" && resource.protocol !== "https:") {
21
+ throw new Error("resourceUrl must use https in production");
22
+ }
23
+ if (!options.auth.options.baseURL) {
24
+ throw new Error("auth.options.baseURL must be configured");
25
+ }
26
+ let authorizationServerBase;
27
+ try {
28
+ authorizationServerBase = new URL(options.auth.options.baseURL);
29
+ } catch (e) {
30
+ throw new Error("auth.options.baseURL must be an absolute URL");
31
+ }
32
+ if (!["http:", "https:"].includes(authorizationServerBase.protocol)) {
33
+ throw new Error("auth.options.baseURL must use http or https");
34
+ }
35
+ if (authorizationServerBase.username || authorizationServerBase.password || authorizationServerBase.pathname !== "/" || authorizationServerBase.search || authorizationServerBase.hash) {
36
+ throw new Error("auth.options.baseURL must be an origin URL");
37
+ }
38
+ if (process.env.NODE_ENV === "production" && authorizationServerBase.protocol !== "https:") {
39
+ throw new Error("auth.options.baseURL must use https in production");
40
+ }
41
+ const rawAuthorizationServerPath = options.auth.options.basePath || "/api/auth";
42
+ if (!rawAuthorizationServerPath.startsWith("/") || rawAuthorizationServerPath.startsWith("//") || rawAuthorizationServerPath.includes("?") || rawAuthorizationServerPath.includes("#") || /\s/.test(rawAuthorizationServerPath)) {
43
+ throw new Error("auth.options.basePath must be an absolute path");
44
+ }
45
+ const authorizationServerPath = rawAuthorizationServerPath === "/" ? "" : rawAuthorizationServerPath.replace(/\/+$/, "");
46
+ const authorizationServerUrl = `${authorizationServerBase.toString().replace(/\/$/, "")}${authorizationServerPath}`;
47
+ const scopesSupported = [
48
+ ...new Set(
49
+ options.scopesSupported.map((scope) => scope.trim()).filter(Boolean)
50
+ )
51
+ ];
52
+ const allowedOrigins = options.allowedOrigins === "*" ? "*" : new Set(
53
+ options.allowedOrigins.map((origin) => {
54
+ let url;
55
+ try {
56
+ url = new URL(origin);
57
+ } catch (e) {
58
+ throw new Error(
59
+ "allowedOrigins must contain absolute origins"
60
+ );
61
+ }
62
+ if (!["http:", "https:"].includes(url.protocol)) {
63
+ throw new Error(
64
+ "allowedOrigins must use http or https"
65
+ );
66
+ }
67
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
68
+ throw new Error(
69
+ "allowedOrigins must contain origins without paths"
70
+ );
71
+ }
72
+ if (process.env.NODE_ENV === "production" && url.protocol !== "https:") {
73
+ throw new Error(
74
+ "allowedOrigins must use https in production"
75
+ );
76
+ }
77
+ return url.origin;
78
+ })
79
+ );
80
+ if (allowedOrigins !== "*" && allowedOrigins.size === 0) {
81
+ throw new Error("allowedOrigins must not be empty");
82
+ }
83
+ return __spreadProps(__spreadValues({}, options), {
84
+ resourceUrl: resource.toString(),
85
+ scopesSupported,
86
+ allowedOrigins,
87
+ authorizationServerUrl,
88
+ authorizationServerPath
89
+ });
90
+ }
91
+ function applyCors(request, response, allowedOrigins) {
92
+ const origin = request.headers.origin;
93
+ if (allowedOrigins === "*") {
94
+ response.header("Access-Control-Allow-Origin", "*");
95
+ } else if (origin && allowedOrigins.has(origin)) {
96
+ response.header("Access-Control-Allow-Origin", origin);
97
+ response.header("Vary", "Origin");
98
+ } else if (origin) {
99
+ return false;
100
+ }
101
+ response.header("Access-Control-Allow-Methods", "GET, OPTIONS");
102
+ response.header(
103
+ "Access-Control-Allow-Headers",
104
+ "Accept, Authorization, Content-Type, Mcp-Protocol-Version, Mcp-Session-Id"
105
+ );
106
+ response.header("Access-Control-Expose-Headers", "Mcp-Session-Id");
107
+ return true;
108
+ }
109
+ async function sendFetchResponse(response, fetchResponse) {
110
+ response.status(fetchResponse.status);
111
+ fetchResponse.headers.forEach(
112
+ (value, key) => response.setHeader(key, value)
113
+ );
114
+ response.send(await fetchResponse.text());
115
+ }
116
+ function asyncHandler(handler) {
117
+ return (request, response, next) => {
118
+ void handler(request, response).catch(next);
119
+ };
120
+ }
121
+ function createMcpOAuthDiscoveryRoutes(rawOptions) {
122
+ const options = normalizeOptions(rawOptions);
123
+ const router = Router();
124
+ const authorizationServerPaths = /* @__PURE__ */ new Set([
125
+ "/.well-known/oauth-authorization-server",
126
+ `/.well-known/oauth-authorization-server${options.authorizationServerPath}`
127
+ ]);
128
+ const openIdPaths = /* @__PURE__ */ new Set([
129
+ "/.well-known/openid-configuration",
130
+ `${options.authorizationServerPath}/.well-known/openid-configuration`
131
+ ]);
132
+ const resourcePath = new URL(options.resourceUrl).pathname.replace(
133
+ /\/$/,
134
+ ""
135
+ );
136
+ const protectedResourcePaths = /* @__PURE__ */ new Set([
137
+ "/.well-known/oauth-protected-resource",
138
+ `/.well-known/oauth-protected-resource${resourcePath}`
139
+ ]);
140
+ const metadataPaths = /* @__PURE__ */ new Set([
141
+ ...authorizationServerPaths,
142
+ ...openIdPaths,
143
+ ...protectedResourcePaths
144
+ ]);
145
+ router.use((request, response, next) => {
146
+ if (!metadataPaths.has(request.path)) {
147
+ next();
148
+ return;
149
+ }
150
+ if (!applyCors(request, response, options.allowedOrigins)) {
151
+ response.status(403).json({ error: "origin_not_allowed" });
152
+ return;
153
+ }
154
+ if (request.method === "OPTIONS") {
155
+ response.status(204).end();
156
+ return;
157
+ }
158
+ next();
159
+ });
160
+ async function authorizationServerMetadata(request, response) {
161
+ const handler = oauthProviderAuthServerMetadata(options.auth);
162
+ await sendFetchResponse(
163
+ response,
164
+ await handler(
165
+ new Request(
166
+ `${request.protocol}://${request.get("host")}${request.originalUrl}`,
167
+ { headers: request.headers }
168
+ )
169
+ )
170
+ );
171
+ }
172
+ async function openIdMetadata(request, response) {
173
+ const handler = oauthProviderOpenIdConfigMetadata(options.auth);
174
+ await sendFetchResponse(
175
+ response,
176
+ await handler(
177
+ new Request(
178
+ `${request.protocol}://${request.get("host")}${request.originalUrl}`,
179
+ { headers: request.headers }
180
+ )
181
+ )
182
+ );
183
+ }
184
+ for (const path of authorizationServerPaths) {
185
+ router.get(path, asyncHandler(authorizationServerMetadata));
186
+ }
187
+ for (const path of openIdPaths) {
188
+ router.get(path, asyncHandler(openIdMetadata));
189
+ }
190
+ async function protectedResourceMetadata(_request, response) {
191
+ const metadata = await options.oauthResourceClient.getActions().getProtectedResourceMetadata(
192
+ {
193
+ resource: options.resourceUrl,
194
+ authorization_servers: [options.authorizationServerUrl],
195
+ scopes_supported: options.scopesSupported,
196
+ bearer_methods_supported: ["header"]
197
+ },
198
+ { silenceWarnings: { oidcScopes: true } }
199
+ );
200
+ response.json(metadata);
201
+ }
202
+ for (const path of protectedResourcePaths) {
203
+ router.get(path, asyncHandler(protectedResourceMetadata));
204
+ }
205
+ return router;
206
+ }
207
+
208
+ export { createMcpOAuthDiscoveryRoutes };
@@ -0,0 +1,33 @@
1
+ import { oauthProviderResourceClient } from '@better-auth/oauth-provider/resource-client';
2
+
3
+ type AuthenticatedIdentity = {
4
+ method: "session";
5
+ issuer: string;
6
+ subject: string;
7
+ email: string;
8
+ name?: string | null;
9
+ scopes: [];
10
+ } | {
11
+ method: "oauth";
12
+ issuer: string;
13
+ subject: string;
14
+ email?: string;
15
+ name?: string | null;
16
+ clientId: string;
17
+ scopes: string[];
18
+ audiences: string[];
19
+ };
20
+ type AuthenticationResult = {
21
+ status: "authenticated";
22
+ identity: AuthenticatedIdentity;
23
+ } | {
24
+ status: "missing";
25
+ } | {
26
+ status: "invalid_token";
27
+ } | {
28
+ status: "unavailable";
29
+ cause?: unknown;
30
+ };
31
+ type OAuthResourceClient = ReturnType<typeof oauthProviderResourceClient<ConfiguredResourceClientAuth>>;
32
+
33
+ export type { AuthenticationResult as A, OAuthResourceClient as O, AuthenticatedIdentity as a };
@@ -0,0 +1,33 @@
1
+ import { oauthProviderResourceClient } from '@better-auth/oauth-provider/resource-client';
2
+
3
+ type AuthenticatedIdentity = {
4
+ method: "session";
5
+ issuer: string;
6
+ subject: string;
7
+ email: string;
8
+ name?: string | null;
9
+ scopes: [];
10
+ } | {
11
+ method: "oauth";
12
+ issuer: string;
13
+ subject: string;
14
+ email?: string;
15
+ name?: string | null;
16
+ clientId: string;
17
+ scopes: string[];
18
+ audiences: string[];
19
+ };
20
+ type AuthenticationResult = {
21
+ status: "authenticated";
22
+ identity: AuthenticatedIdentity;
23
+ } | {
24
+ status: "missing";
25
+ } | {
26
+ status: "invalid_token";
27
+ } | {
28
+ status: "unavailable";
29
+ cause?: unknown;
30
+ };
31
+ type OAuthResourceClient = ReturnType<typeof oauthProviderResourceClient<ConfiguredResourceClientAuth>>;
32
+
33
+ export type { AuthenticationResult as A, OAuthResourceClient as O, AuthenticatedIdentity as a };