@kya-os/mcp-i 0.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +406 -71
- package/dist/149.js +1 -0
- package/dist/189.js +1 -0
- package/dist/261.js +1 -0
- package/dist/28.js +1 -0
- package/dist/295.js +1 -0
- package/dist/460.js +1 -0
- package/dist/570.js +1 -0
- package/dist/634.js +1 -0
- package/dist/647.js +1 -0
- package/dist/67.js +1 -0
- package/dist/739.js +1 -0
- package/dist/742.js +1 -0
- package/dist/904.js +1 -0
- package/dist/938.js +1 -0
- package/dist/auth/api-key.d.ts +16 -0
- package/dist/auth/api-key.js +82 -0
- package/dist/auth/jwt.d.ts +43 -0
- package/dist/auth/jwt.js +51 -0
- package/dist/auth/oauth/factory.d.ts +12 -0
- package/dist/auth/oauth/factory.js +36 -0
- package/dist/auth/oauth/index.d.ts +5 -0
- package/dist/auth/oauth/index.js +27 -0
- package/dist/auth/oauth/providers/proxy-provider.d.ts +13 -0
- package/dist/auth/oauth/providers/proxy-provider.js +159 -0
- package/dist/auth/oauth/router.d.ts +4 -0
- package/dist/auth/oauth/router.js +294 -0
- package/dist/auth/oauth/storage/memory-storage.d.ts +12 -0
- package/dist/auth/oauth/storage/memory-storage.js +40 -0
- package/dist/auth/oauth/types.d.ts +112 -0
- package/dist/auth/oauth/types.js +2 -0
- package/dist/cache/__tests__/cloudflare-kv-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/cloudflare-kv-nonce-cache.test.js +176 -0
- package/dist/cache/__tests__/concurrency.test.d.ts +5 -0
- package/dist/cache/__tests__/concurrency.test.js +300 -0
- package/dist/cache/__tests__/dynamodb-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/dynamodb-nonce-cache.test.js +176 -0
- package/dist/cache/__tests__/memory-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/memory-nonce-cache.test.js +132 -0
- package/dist/cache/__tests__/nonce-cache-factory-simple.test.d.ts +4 -0
- package/dist/cache/__tests__/nonce-cache-factory-simple.test.js +133 -0
- package/dist/cache/__tests__/nonce-cache-factory.test.d.ts +4 -0
- package/dist/cache/__tests__/nonce-cache-factory.test.js +252 -0
- package/dist/cache/__tests__/redis-nonce-cache.test.d.ts +4 -0
- package/dist/cache/__tests__/redis-nonce-cache.test.js +95 -0
- package/dist/cache/cloudflare-kv-nonce-cache.d.ts +14 -0
- package/dist/cache/cloudflare-kv-nonce-cache.js +93 -0
- package/dist/cache/dynamodb-nonce-cache.d.ts +15 -0
- package/dist/cache/dynamodb-nonce-cache.js +92 -0
- package/dist/cache/index.d.ts +16 -0
- package/dist/cache/index.js +32 -0
- package/dist/cache/memory-nonce-cache.d.ts +44 -0
- package/dist/cache/memory-nonce-cache.js +105 -0
- package/dist/cache/nonce-cache-factory.d.ts +20 -0
- package/dist/cache/nonce-cache-factory.js +208 -0
- package/dist/cache/redis-nonce-cache.d.ts +14 -0
- package/dist/cache/redis-nonce-cache.js +53 -0
- package/dist/compiler/compiler-context.d.ts +23 -0
- package/dist/compiler/compiler-context.js +24 -0
- package/dist/compiler/config/constants.d.ts +41 -0
- package/dist/compiler/config/constants.js +45 -0
- package/dist/compiler/config/index.d.ts +252 -0
- package/dist/compiler/config/index.js +15 -0
- package/dist/compiler/config/injection.d.ts +26 -0
- package/dist/compiler/config/injection.js +58 -0
- package/dist/compiler/config/schemas/experimental/index.d.ts +91 -0
- package/dist/compiler/config/schemas/experimental/index.js +16 -0
- package/dist/compiler/config/schemas/experimental/oauth.d.ts +74 -0
- package/dist/compiler/config/schemas/experimental/oauth.js +25 -0
- package/dist/compiler/config/schemas/index.d.ts +6 -0
- package/dist/compiler/config/schemas/index.js +17 -0
- package/dist/compiler/config/schemas/paths.d.ts +9 -0
- package/dist/compiler/config/schemas/paths.js +12 -0
- package/dist/compiler/config/schemas/transport/http.d.ts +82 -0
- package/dist/compiler/config/schemas/transport/http.js +33 -0
- package/dist/compiler/config/schemas/transport/stdio.d.ts +9 -0
- package/dist/compiler/config/schemas/transport/stdio.js +15 -0
- package/dist/compiler/config/schemas/webpack.d.ts +3 -0
- package/dist/compiler/config/schemas/webpack.js +15 -0
- package/dist/compiler/config/types.d.ts +1 -0
- package/dist/compiler/config/types.js +2 -0
- package/dist/compiler/config/utils.d.ts +20 -0
- package/dist/compiler/config/utils.js +36 -0
- package/dist/compiler/generate-env-code.d.ts +1 -0
- package/dist/compiler/generate-env-code.js +8 -0
- package/dist/compiler/generate-import-code.d.ts +1 -0
- package/dist/compiler/generate-import-code.js +24 -0
- package/dist/compiler/get-webpack-config/get-entries.d.ts +3 -0
- package/dist/compiler/get-webpack-config/get-entries.js +29 -0
- package/dist/compiler/get-webpack-config/get-externals.d.ts +7 -0
- package/dist/compiler/get-webpack-config/get-externals.js +88 -0
- package/dist/compiler/get-webpack-config/get-injected-variables.d.ts +8 -0
- package/dist/compiler/get-webpack-config/get-injected-variables.js +25 -0
- package/dist/compiler/get-webpack-config/index.d.ts +4 -0
- package/dist/compiler/get-webpack-config/index.js +101 -0
- package/dist/compiler/get-webpack-config/plugins.d.ts +8 -0
- package/dist/compiler/get-webpack-config/plugins.js +132 -0
- package/dist/compiler/get-webpack-config/resolve-tsconfig-paths.d.ts +9 -0
- package/dist/compiler/get-webpack-config/resolve-tsconfig-paths.js +40 -0
- package/dist/compiler/index.d.ts +6 -0
- package/dist/compiler/index.js +194 -0
- package/dist/compiler/on-first-build.d.ts +3 -0
- package/dist/compiler/on-first-build.js +58 -0
- package/dist/compiler/parse-xmcp-config.d.ts +9 -0
- package/dist/compiler/parse-xmcp-config.js +155 -0
- package/dist/compiler/start-http-server.d.ts +1 -0
- package/dist/compiler/start-http-server.js +34 -0
- package/dist/index.d.ts +12 -54
- package/dist/index.js +22 -190
- package/dist/index.js.LICENSE.txt +49 -0
- package/dist/runtime/__tests__/audit.test.d.ts +4 -0
- package/dist/runtime/__tests__/audit.test.js +328 -0
- package/dist/runtime/__tests__/identity.test.d.ts +4 -0
- package/dist/runtime/__tests__/identity.test.js +164 -0
- package/dist/runtime/__tests__/mcpi-runtime.test.d.ts +4 -0
- package/dist/runtime/__tests__/mcpi-runtime.test.js +372 -0
- package/dist/runtime/__tests__/proof.test.d.ts +4 -0
- package/dist/runtime/__tests__/proof.test.js +302 -0
- package/dist/runtime/__tests__/session.test.d.ts +4 -0
- package/dist/runtime/__tests__/session.test.js +254 -0
- package/dist/runtime/__tests__/well-known.test.d.ts +4 -0
- package/dist/runtime/__tests__/well-known.test.js +312 -0
- package/dist/runtime/adapter-express.js +2 -0
- package/dist/runtime/adapter-express.js.LICENSE.txt +252 -0
- package/dist/runtime/adapter-nextjs.js +2 -0
- package/dist/runtime/adapter-nextjs.js.LICENSE.txt +53 -0
- package/dist/runtime/adapters/express/index.d.ts +2 -0
- package/dist/runtime/adapters/express/index.js +48 -0
- package/dist/runtime/adapters/nextjs/index.d.ts +8 -0
- package/dist/runtime/adapters/nextjs/index.js +18 -0
- package/dist/runtime/audit.d.ts +93 -0
- package/dist/runtime/audit.js +212 -0
- package/dist/runtime/debug.d.ts +118 -0
- package/dist/runtime/debug.js +612 -0
- package/dist/runtime/delegation-hooks.d.ts +85 -0
- package/dist/runtime/delegation-hooks.js +116 -0
- package/dist/runtime/demo.d.ts +71 -0
- package/dist/runtime/demo.js +135 -0
- package/dist/runtime/headers.d.ts +1 -0
- package/dist/runtime/headers.js +9 -0
- package/dist/runtime/http.js +2 -0
- package/dist/runtime/http.js.LICENSE.txt +252 -0
- package/dist/runtime/identity.d.ts +105 -0
- package/dist/runtime/identity.js +232 -0
- package/dist/runtime/index.d.ts +16 -0
- package/dist/runtime/index.js +56 -0
- package/dist/runtime/mcpi-runtime.d.ts +164 -0
- package/dist/runtime/mcpi-runtime.js +352 -0
- package/dist/runtime/proof.d.ts +87 -0
- package/dist/runtime/proof.js +223 -0
- package/dist/runtime/session.d.ts +88 -0
- package/dist/runtime/session.js +216 -0
- package/dist/runtime/stdio.js +2 -0
- package/dist/runtime/stdio.js.LICENSE.txt +1 -0
- package/dist/runtime/templates/home.d.ts +2 -0
- package/dist/runtime/templates/home.js +50 -0
- package/dist/runtime/transports/http/base-streamable-http.d.ts +25 -0
- package/dist/runtime/transports/http/base-streamable-http.js +16 -0
- package/dist/runtime/transports/http/http-context.d.ts +9 -0
- package/dist/runtime/transports/http/http-context.js +8 -0
- package/dist/runtime/transports/http/index.d.ts +1 -0
- package/dist/runtime/transports/http/index.js +55 -0
- package/dist/runtime/transports/http/setup-cors.d.ts +4 -0
- package/dist/runtime/transports/http/setup-cors.js +24 -0
- package/dist/runtime/transports/http/stateless-streamable-http.d.ts +39 -0
- package/dist/runtime/transports/http/stateless-streamable-http.js +331 -0
- package/dist/runtime/transports/stdio/index.d.ts +1 -0
- package/dist/runtime/transports/stdio/index.js +51 -0
- package/dist/runtime/utils/server.d.ts +42 -0
- package/dist/runtime/utils/server.js +39 -0
- package/dist/runtime/utils/tools.d.ts +8 -0
- package/dist/runtime/utils/tools.js +115 -0
- package/dist/runtime/verifier-middleware.d.ts +76 -0
- package/dist/runtime/verifier-middleware.js +322 -0
- package/dist/runtime/well-known.d.ts +151 -0
- package/dist/runtime/well-known.js +258 -0
- package/dist/storage/config.d.ts +28 -0
- package/dist/storage/config.js +79 -0
- package/dist/storage/delegation.d.ts +59 -0
- package/dist/storage/delegation.js +130 -0
- package/dist/storage/merkle-verifier.d.ts +84 -0
- package/dist/storage/merkle-verifier.js +261 -0
- package/dist/test/__tests__/nonce-cache-integration.test.d.ts +1 -0
- package/dist/test/__tests__/nonce-cache-integration.test.js +116 -0
- package/dist/test/__tests__/nonce-cache.test.d.ts +1 -0
- package/dist/test/__tests__/nonce-cache.test.js +122 -0
- package/dist/test/__tests__/runtime-integration.test.d.ts +4 -0
- package/dist/test/__tests__/runtime-integration.test.js +192 -0
- package/dist/test/__tests__/test-infrastructure.test.d.ts +4 -0
- package/dist/test/__tests__/test-infrastructure.test.js +178 -0
- package/dist/test/deterministic-keys.d.ts +31 -0
- package/dist/test/deterministic-keys.js +108 -0
- package/dist/test/examples/test-usage-example.d.ts +140 -0
- package/dist/test/examples/test-usage-example.js +175 -0
- package/dist/test/index.d.ts +11 -0
- package/dist/test/index.js +27 -0
- package/dist/test/local-verification.d.ts +28 -0
- package/dist/test/local-verification.js +342 -0
- package/dist/test/mock-identity-provider.d.ts +96 -0
- package/dist/test/mock-identity-provider.js +243 -0
- package/dist/test/runtime-integration.d.ts +63 -0
- package/dist/test/runtime-integration.js +140 -0
- package/dist/test/test-environment.d.ts +26 -0
- package/dist/test/test-environment.js +50 -0
- package/dist/types/declarations.d.ts +1 -0
- package/dist/types/declarations.js +6 -0
- package/dist/types/middleware.d.ts +2 -0
- package/dist/types/middleware.js +2 -0
- package/dist/types/tool.d.ts +80 -0
- package/dist/types/tool.js +2 -0
- package/dist/utils/cli-icons.d.ts +3 -0
- package/dist/utils/cli-icons.js +7 -0
- package/dist/utils/constants.d.ts +6 -0
- package/dist/utils/constants.js +13 -0
- package/dist/utils/context.d.ts +33 -0
- package/dist/utils/context.js +58 -0
- package/dist/utils/file-watcher.d.ts +19 -0
- package/dist/utils/file-watcher.js +49 -0
- package/dist/utils/fs-utils.d.ts +2 -0
- package/dist/utils/fs-utils.js +22 -0
- package/dist/utils/path-validation.d.ts +3 -0
- package/dist/utils/path-validation.js +56 -0
- package/dist/utils/spawn-process.d.ts +9 -0
- package/dist/utils/spawn-process.js +50 -0
- package/dist/utils/subscribable.d.ts +12 -0
- package/dist/utils/subscribable.js +44 -0
- package/package.json +99 -21
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProxyOAuthServerProvider = void 0;
|
|
4
|
+
const memory_storage_1 = require("../storage/memory-storage");
|
|
5
|
+
class ProxyOAuthServerProvider {
|
|
6
|
+
config;
|
|
7
|
+
storage;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
// fallback to memory storage
|
|
11
|
+
// ideally this would be used in a development environment, could be set with a flag
|
|
12
|
+
// since we are providing storage on Redis (to do) we may want to fallback to this in dev
|
|
13
|
+
// does not scale to prod tho, so we should be validating that we prevent that from happening
|
|
14
|
+
this.storage = config.storage || new memory_storage_1.MemoryOAuthStorage();
|
|
15
|
+
}
|
|
16
|
+
// Expose config for router access
|
|
17
|
+
get endpoints() {
|
|
18
|
+
return this.config.endpoints;
|
|
19
|
+
}
|
|
20
|
+
async verifyAccessToken(token) {
|
|
21
|
+
if (this.config.verifyAccessToken) {
|
|
22
|
+
return await this.config.verifyAccessToken(token);
|
|
23
|
+
}
|
|
24
|
+
// Fallback to storage lookup or external verification
|
|
25
|
+
const storedToken = await this.storage.tokens.getToken(token);
|
|
26
|
+
if (storedToken) {
|
|
27
|
+
return storedToken;
|
|
28
|
+
}
|
|
29
|
+
// If not found in storage, verify with external provider
|
|
30
|
+
return await this.verifyTokenWithProvider(token);
|
|
31
|
+
}
|
|
32
|
+
async authorize(params) {
|
|
33
|
+
const { client_id, redirect_uri, response_type, scope, state } = params;
|
|
34
|
+
// Let Auth0 handle all client validation - just build the authorization URL
|
|
35
|
+
const authUrl = new URL(this.config.endpoints.authorizationUrl);
|
|
36
|
+
authUrl.searchParams.set("response_type", response_type);
|
|
37
|
+
authUrl.searchParams.set("client_id", client_id);
|
|
38
|
+
authUrl.searchParams.set("redirect_uri", redirect_uri);
|
|
39
|
+
if (scope) {
|
|
40
|
+
authUrl.searchParams.set("scope", scope);
|
|
41
|
+
}
|
|
42
|
+
else if (this.config.defaultScopes) {
|
|
43
|
+
authUrl.searchParams.set("scope", this.config.defaultScopes.join(" "));
|
|
44
|
+
}
|
|
45
|
+
if (state) {
|
|
46
|
+
authUrl.searchParams.set("state", state);
|
|
47
|
+
}
|
|
48
|
+
return authUrl.toString();
|
|
49
|
+
}
|
|
50
|
+
async token(params) {
|
|
51
|
+
const { grant_type, client_id, client_secret, code, redirect_uri, refresh_token, } = params;
|
|
52
|
+
try {
|
|
53
|
+
// Forward token request to external provider (let Auth0 handle client validation)
|
|
54
|
+
const response = await fetch(this.config.endpoints.tokenUrl, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
58
|
+
Accept: "application/json",
|
|
59
|
+
},
|
|
60
|
+
body: new URLSearchParams({
|
|
61
|
+
grant_type,
|
|
62
|
+
client_id,
|
|
63
|
+
...(client_secret && { client_secret }),
|
|
64
|
+
...(code && { code }),
|
|
65
|
+
...(redirect_uri && { redirect_uri }),
|
|
66
|
+
...(refresh_token && { refresh_token }),
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
const tokenData = await response.json();
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw this.createOAuthError(tokenData.error || "server_error", tokenData.error_description || "Token exchange failed");
|
|
72
|
+
}
|
|
73
|
+
// Store token in our storage if we have access_token
|
|
74
|
+
if (tokenData.access_token) {
|
|
75
|
+
const accessToken = {
|
|
76
|
+
token: tokenData.access_token,
|
|
77
|
+
clientId: client_id,
|
|
78
|
+
scopes: tokenData.scope ? tokenData.scope.split(" ") : [],
|
|
79
|
+
expiresAt: tokenData.expires_in
|
|
80
|
+
? new Date(Date.now() + tokenData.expires_in * 1000)
|
|
81
|
+
: undefined,
|
|
82
|
+
refreshToken: tokenData.refresh_token,
|
|
83
|
+
};
|
|
84
|
+
await this.storage.tokens.saveToken(accessToken);
|
|
85
|
+
}
|
|
86
|
+
return tokenData;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof Error && error.message.includes("invalid_")) {
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
throw this.createOAuthError("server_error", "Failed to exchange token");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async revoke(params) {
|
|
96
|
+
const { token, token_type_hint, client_id, client_secret } = params;
|
|
97
|
+
// Remove from our storage first
|
|
98
|
+
await this.storage.tokens.deleteToken(token);
|
|
99
|
+
// If external provider supports revocation, forward the request
|
|
100
|
+
if (this.config.endpoints.revocationUrl) {
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(this.config.endpoints.revocationUrl, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: {
|
|
105
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
106
|
+
Accept: "application/json",
|
|
107
|
+
},
|
|
108
|
+
body: new URLSearchParams({
|
|
109
|
+
token,
|
|
110
|
+
...(token_type_hint && { token_type_hint }),
|
|
111
|
+
...(client_id && { client_id }),
|
|
112
|
+
...(client_secret && { client_secret }),
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
console.warn("Failed to revoke token with external provider:", response.statusText);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
console.warn("Error revoking token with external provider:", error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async verifyTokenWithProvider(token) {
|
|
125
|
+
// If external provider has a userinfo endpoint, we can use it to verify the token
|
|
126
|
+
if (this.config.endpoints.userInfoUrl) {
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetch(this.config.endpoints.userInfoUrl, {
|
|
129
|
+
headers: {
|
|
130
|
+
Authorization: `Bearer ${token}`,
|
|
131
|
+
Accept: "application/json",
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
if (response.ok) {
|
|
135
|
+
// Token is valid, create a basic AccessToken object
|
|
136
|
+
return {
|
|
137
|
+
token,
|
|
138
|
+
clientId: "external", // We might not know the exact client ID
|
|
139
|
+
scopes: [], // We might not know the exact scopes
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.warn("Error verifying token with external provider:", error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
throw this.createOAuthError("invalid_token", "Token verification failed");
|
|
148
|
+
}
|
|
149
|
+
createOAuthError(error, description) {
|
|
150
|
+
const oauthError = {
|
|
151
|
+
error,
|
|
152
|
+
error_description: description,
|
|
153
|
+
};
|
|
154
|
+
const errorObj = new Error(description || error);
|
|
155
|
+
errorObj.oauth = oauthError;
|
|
156
|
+
return errorObj;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.ProxyOAuthServerProvider = ProxyOAuthServerProvider;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Router, Request, Response, NextFunction } from "express";
|
|
2
|
+
import { OAuthRouterConfig } from "./types";
|
|
3
|
+
export declare function createOAuthRouter(config: OAuthRouterConfig): Router;
|
|
4
|
+
export declare function createOAuthMiddleware(provider: any): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOAuthRouter = createOAuthRouter;
|
|
4
|
+
exports.createOAuthMiddleware = createOAuthMiddleware;
|
|
5
|
+
const express_1 = require("express");
|
|
6
|
+
function createOAuthRouter(config) {
|
|
7
|
+
const router = (0, express_1.Router)();
|
|
8
|
+
const { provider, issuerUrl, baseUrl, serviceDocumentationUrl, pathPrefix = "/oauth2", } = config;
|
|
9
|
+
router.use((req, res, next) => {
|
|
10
|
+
// to do check: cors config from ts file is overriding and failing to add headers
|
|
11
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
12
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
13
|
+
res.setHeader("Access-Control-Allow-Headers",
|
|
14
|
+
// should also pass the mcp-protocol-version header
|
|
15
|
+
// probably only that one since the rest can be set from the config side
|
|
16
|
+
"Content-Type, Authorization, Accept, mcp-protocol-version");
|
|
17
|
+
res.setHeader("Access-Control-Expose-Headers", "Content-Type");
|
|
18
|
+
if (req.method === "OPTIONS") {
|
|
19
|
+
res.sendStatus(200);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
next();
|
|
23
|
+
});
|
|
24
|
+
// OAuth 2.0 Protected Resource Metadata (RFC 9728)
|
|
25
|
+
router.get("/.well-known/oauth-protected-resource", async (req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
const baseUrlStr = baseUrl.toString().replace(/\/$/, ""); // Remove trailing slash - formatting issues lol
|
|
28
|
+
// thinking of maybe removing the path prefix?
|
|
29
|
+
const metadata = {
|
|
30
|
+
resource: baseUrlStr,
|
|
31
|
+
authorization_servers: [issuerUrl.toString()],
|
|
32
|
+
bearer_methods_supported: ["header", "body"],
|
|
33
|
+
resource_documentation: serviceDocumentationUrl?.toString(),
|
|
34
|
+
introspection_endpoint: `${baseUrlStr}${pathPrefix}/introspect`,
|
|
35
|
+
revocation_endpoint: `${baseUrlStr}${pathPrefix}/revoke`,
|
|
36
|
+
};
|
|
37
|
+
res.json(metadata);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error("Error in protected resource metadata endpoint:", error);
|
|
41
|
+
res.status(500).json({
|
|
42
|
+
error: "server_error",
|
|
43
|
+
error_description: "Internal server error",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
// OAuth 2.0 Discovery endpoint - authorization server metadata RFC 8414
|
|
48
|
+
// maybe serve all the config as customizable? for example for scopes etc
|
|
49
|
+
router.get("/.well-known/oauth-authorization-server", async (req, res) => {
|
|
50
|
+
try {
|
|
51
|
+
const discovery = {
|
|
52
|
+
issuer: issuerUrl.toString(),
|
|
53
|
+
authorization_endpoint: provider.endpoints.authorizationUrl,
|
|
54
|
+
token_endpoint: provider.endpoints.tokenUrl,
|
|
55
|
+
revocation_endpoint: provider.endpoints.revocationUrl,
|
|
56
|
+
response_types_supported: ["code"],
|
|
57
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
58
|
+
token_endpoint_auth_methods_supported: [
|
|
59
|
+
"client_secret_post",
|
|
60
|
+
"client_secret_basic",
|
|
61
|
+
],
|
|
62
|
+
scopes_supported: ["openid", "profile", "email"],
|
|
63
|
+
// PKCE support (RFC 7636) - S256 mandatory for security
|
|
64
|
+
code_challenge_methods_supported: ["S256"],
|
|
65
|
+
// DCR is mandatory - all clients must register
|
|
66
|
+
// this is what MCP recommends doing to handle the entire OAuth flow
|
|
67
|
+
// cause we're not supporting manually setting up the client
|
|
68
|
+
registration_endpoint: `${baseUrl.toString().replace(/\/$/, "")}${pathPrefix}/register`,
|
|
69
|
+
...(serviceDocumentationUrl && {
|
|
70
|
+
service_documentation: serviceDocumentationUrl.toString(),
|
|
71
|
+
}),
|
|
72
|
+
};
|
|
73
|
+
res.json(discovery);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error("Error in discovery endpoint:", error);
|
|
77
|
+
res.status(500).json({
|
|
78
|
+
error: "server_error",
|
|
79
|
+
error_description: "Internal server error",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// following endpoints we're redirecting to the external authorization server
|
|
84
|
+
router.get(`${pathPrefix}/authorize`, async (req, res) => {
|
|
85
|
+
try {
|
|
86
|
+
const params = {
|
|
87
|
+
response_type: req.query.response_type,
|
|
88
|
+
client_id: req.query.client_id,
|
|
89
|
+
redirect_uri: req.query.redirect_uri,
|
|
90
|
+
scope: req.query.scope,
|
|
91
|
+
state: req.query.state,
|
|
92
|
+
// PKCE parameters (RFC 7636)
|
|
93
|
+
code_challenge: req.query.code_challenge,
|
|
94
|
+
code_challenge_method: req.query.code_challenge_method,
|
|
95
|
+
};
|
|
96
|
+
if (!params.response_type || !params.client_id || !params.redirect_uri) {
|
|
97
|
+
res.status(400).json({
|
|
98
|
+
error: "invalid_request",
|
|
99
|
+
error_description: "Missing required parameters",
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// PKCE params validation
|
|
104
|
+
if (!params.code_challenge || !params.code_challenge_method) {
|
|
105
|
+
res.status(400).json({
|
|
106
|
+
error: "invalid_request",
|
|
107
|
+
error_description: "PKCE parameters (code_challenge, code_challenge_method) are required",
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// only allow S256 method
|
|
112
|
+
if (params.code_challenge_method !== "S256") {
|
|
113
|
+
res.status(400).json({
|
|
114
|
+
error: "invalid_request",
|
|
115
|
+
error_description: "Only S256 code challenge method is supported",
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// let the provider handle the authorization
|
|
120
|
+
const authUrl = await provider.authorize(params);
|
|
121
|
+
res.redirect(authUrl);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error("Error in authorize endpoint:", error);
|
|
125
|
+
const oauthError = extractOAuthError(error);
|
|
126
|
+
res.status(400).json(oauthError);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
router.post(`${pathPrefix}/token`, async (req, res) => {
|
|
130
|
+
try {
|
|
131
|
+
const params = {
|
|
132
|
+
grant_type: req.body.grant_type,
|
|
133
|
+
client_id: req.body.client_id,
|
|
134
|
+
client_secret: req.body.client_secret,
|
|
135
|
+
code: req.body.code,
|
|
136
|
+
redirect_uri: req.body.redirect_uri,
|
|
137
|
+
refresh_token: req.body.refresh_token,
|
|
138
|
+
// PKCE parameter (RFC 7636)
|
|
139
|
+
code_verifier: req.body.code_verifier,
|
|
140
|
+
};
|
|
141
|
+
if (!params.grant_type || !params.client_id) {
|
|
142
|
+
res.status(400).json({
|
|
143
|
+
error: "invalid_request",
|
|
144
|
+
error_description: "Missing required parameters",
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// PKCE is mandatory for authorization_code grant
|
|
149
|
+
if (params.grant_type === "authorization_code" && !params.code_verifier) {
|
|
150
|
+
res.status(400).json({
|
|
151
|
+
error: "invalid_request",
|
|
152
|
+
error_description: "code_verifier is required for authorization_code grant (PKCE)",
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const tokenResponse = await provider.token(params);
|
|
157
|
+
res.json(tokenResponse);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
console.error("Error in token endpoint:", error);
|
|
161
|
+
const oauthError = extractOAuthError(error);
|
|
162
|
+
res.status(400).json(oauthError);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
router.post(`${pathPrefix}/revoke`, async (req, res) => {
|
|
166
|
+
try {
|
|
167
|
+
const params = {
|
|
168
|
+
token: req.body.token,
|
|
169
|
+
token_type_hint: req.body.token_type_hint,
|
|
170
|
+
client_id: req.body.client_id,
|
|
171
|
+
client_secret: req.body.client_secret,
|
|
172
|
+
};
|
|
173
|
+
if (!params.token) {
|
|
174
|
+
res.status(400).json({
|
|
175
|
+
error: "invalid_request",
|
|
176
|
+
error_description: "Missing token parameter",
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
await provider.revoke(params);
|
|
181
|
+
res.status(200).end();
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
console.error("Error in revoke endpoint:", error);
|
|
185
|
+
const oauthError = extractOAuthError(error);
|
|
186
|
+
res.status(400).json(oauthError);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
router.post(`${pathPrefix}/introspect`, async (req, res) => {
|
|
190
|
+
try {
|
|
191
|
+
const token = req.body.token;
|
|
192
|
+
if (!token) {
|
|
193
|
+
res.status(400).json({
|
|
194
|
+
error: "invalid_request",
|
|
195
|
+
error_description: "Missing token parameter",
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const accessToken = await provider.verifyAccessToken(token);
|
|
200
|
+
res.json({
|
|
201
|
+
active: true,
|
|
202
|
+
client_id: accessToken.clientId,
|
|
203
|
+
scope: accessToken.scopes.join(" "),
|
|
204
|
+
exp: accessToken.expiresAt
|
|
205
|
+
? Math.floor(accessToken.expiresAt.getTime() / 1000)
|
|
206
|
+
: undefined,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
console.error("Error in introspect endpoint:", error);
|
|
211
|
+
// return active: false for invalid tokens
|
|
212
|
+
res.json({ active: false });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
router.all(`${pathPrefix}/register`, async (req, res) => {
|
|
216
|
+
try {
|
|
217
|
+
if (req.method === "GET") {
|
|
218
|
+
// redirect to the external provider's registration page
|
|
219
|
+
res.redirect(provider.endpoints.registerUrl);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// proxy to the external provider's registration page
|
|
223
|
+
const response = await fetch(provider.endpoints.registerUrl, {
|
|
224
|
+
method: req.method,
|
|
225
|
+
headers: {
|
|
226
|
+
"Content-Type": "application/json",
|
|
227
|
+
Accept: "application/json",
|
|
228
|
+
...(req.headers["user-agent"] && {
|
|
229
|
+
"User-Agent": req.headers["user-agent"],
|
|
230
|
+
}),
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify(req.body),
|
|
233
|
+
});
|
|
234
|
+
const registrationData = await response.json();
|
|
235
|
+
res.status(response.status).json(registrationData);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.error("Error in registration endpoint:", error);
|
|
239
|
+
res.status(500).json({
|
|
240
|
+
error: "server_error",
|
|
241
|
+
error_description: "Failed to register client",
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
return router;
|
|
246
|
+
}
|
|
247
|
+
// create middleware for protecting routes
|
|
248
|
+
function createOAuthMiddleware(provider) {
|
|
249
|
+
return async (req, res, next) => {
|
|
250
|
+
try {
|
|
251
|
+
const authHeader = req.header("Authorization");
|
|
252
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
253
|
+
res.status(401).json({
|
|
254
|
+
error: "invalid_token",
|
|
255
|
+
error_description: "Missing or malformed Authorization header",
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const token = authHeader.slice("Bearer ".length).trim();
|
|
260
|
+
if (!token) {
|
|
261
|
+
res.status(401).json({
|
|
262
|
+
error: "invalid_token",
|
|
263
|
+
error_description: "Missing access token",
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const accessToken = await provider.verifyAccessToken(token);
|
|
268
|
+
req.oauth = {
|
|
269
|
+
token: accessToken.token,
|
|
270
|
+
clientId: accessToken.clientId,
|
|
271
|
+
scopes: accessToken.scopes,
|
|
272
|
+
expiresAt: accessToken.expiresAt,
|
|
273
|
+
};
|
|
274
|
+
next();
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
console.error("Error in OAuth middleware:", error);
|
|
278
|
+
res.status(401).json({
|
|
279
|
+
error: "invalid_token",
|
|
280
|
+
error_description: "Invalid or expired token",
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
// helper function to extract OAuth errors pretty self explanatory
|
|
286
|
+
function extractOAuthError(error) {
|
|
287
|
+
if (error && error.oauth) {
|
|
288
|
+
return error.oauth;
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
error: "server_error",
|
|
292
|
+
error_description: error?.message || "Internal server error",
|
|
293
|
+
};
|
|
294
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { OAuthStorage, TokenStorage, AccessToken } from "../types";
|
|
2
|
+
export declare class MemoryTokenStorage implements TokenStorage {
|
|
3
|
+
tokens: Map<string, AccessToken>;
|
|
4
|
+
getToken(token: string): Promise<AccessToken | null>;
|
|
5
|
+
saveToken(token: AccessToken): Promise<void>;
|
|
6
|
+
deleteToken(token: string): Promise<void>;
|
|
7
|
+
deleteTokensByClient(clientId: string): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare class MemoryOAuthStorage implements OAuthStorage {
|
|
10
|
+
tokens: TokenStorage;
|
|
11
|
+
constructor();
|
|
12
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryOAuthStorage = exports.MemoryTokenStorage = void 0;
|
|
4
|
+
// Module-level storage map that persists across instances (SINGLETON PATTERN)
|
|
5
|
+
const tokensMap = new Map();
|
|
6
|
+
class MemoryTokenStorage {
|
|
7
|
+
// same, module level map
|
|
8
|
+
tokens = tokensMap;
|
|
9
|
+
async getToken(token) {
|
|
10
|
+
const accessToken = tokensMap.get(token);
|
|
11
|
+
if (accessToken &&
|
|
12
|
+
accessToken.expiresAt &&
|
|
13
|
+
accessToken.expiresAt < new Date()) {
|
|
14
|
+
tokensMap.delete(token);
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return accessToken || null;
|
|
18
|
+
}
|
|
19
|
+
async saveToken(token) {
|
|
20
|
+
tokensMap.set(token.token, token);
|
|
21
|
+
}
|
|
22
|
+
async deleteToken(token) {
|
|
23
|
+
tokensMap.delete(token);
|
|
24
|
+
}
|
|
25
|
+
async deleteTokensByClient(clientId) {
|
|
26
|
+
for (const [tokenValue, tokenData] of tokensMap.entries()) {
|
|
27
|
+
if (tokenData.clientId === clientId) {
|
|
28
|
+
tokensMap.delete(tokenValue);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.MemoryTokenStorage = MemoryTokenStorage;
|
|
34
|
+
class MemoryOAuthStorage {
|
|
35
|
+
tokens;
|
|
36
|
+
constructor() {
|
|
37
|
+
this.tokens = new MemoryTokenStorage();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.MemoryOAuthStorage = MemoryOAuthStorage;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export interface OAuthClient {
|
|
2
|
+
client_id: string;
|
|
3
|
+
client_secret?: string;
|
|
4
|
+
redirect_uris: string[];
|
|
5
|
+
grant_types?: string[];
|
|
6
|
+
response_types?: string[];
|
|
7
|
+
scopes?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface AccessToken {
|
|
10
|
+
token: string;
|
|
11
|
+
clientId: string;
|
|
12
|
+
scopes: string[];
|
|
13
|
+
expiresAt?: Date;
|
|
14
|
+
refreshToken?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface OAuthEndpoints {
|
|
17
|
+
authorizationUrl: string;
|
|
18
|
+
tokenUrl: string;
|
|
19
|
+
revocationUrl?: string;
|
|
20
|
+
userInfoUrl?: string;
|
|
21
|
+
registerUrl: string;
|
|
22
|
+
}
|
|
23
|
+
export interface OAuthError {
|
|
24
|
+
error: string;
|
|
25
|
+
error_description?: string;
|
|
26
|
+
error_uri?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface TokenResponse {
|
|
29
|
+
access_token: string;
|
|
30
|
+
token_type: string;
|
|
31
|
+
expires_in?: number;
|
|
32
|
+
refresh_token?: string;
|
|
33
|
+
scope?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface OAuthStorage {
|
|
36
|
+
tokens: TokenStorage;
|
|
37
|
+
}
|
|
38
|
+
export interface TokenStorage {
|
|
39
|
+
getToken(token: string): Promise<AccessToken | null>;
|
|
40
|
+
saveToken(token: AccessToken): Promise<void>;
|
|
41
|
+
deleteToken(token: string): Promise<void>;
|
|
42
|
+
deleteTokensByClient(clientId: string): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
export interface ProxyOAuthProviderConfig {
|
|
45
|
+
endpoints: OAuthEndpoints;
|
|
46
|
+
storage?: OAuthStorage;
|
|
47
|
+
verifyAccessToken?: (token: string) => Promise<AccessToken>;
|
|
48
|
+
issuerUrl?: string;
|
|
49
|
+
defaultScopes?: string[];
|
|
50
|
+
}
|
|
51
|
+
export interface OAuthRouterConfig {
|
|
52
|
+
provider: ProxyOAuthServerProvider;
|
|
53
|
+
issuerUrl: URL;
|
|
54
|
+
baseUrl: URL;
|
|
55
|
+
serviceDocumentationUrl?: URL;
|
|
56
|
+
pathPrefix?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface ProxyOAuthServerProvider {
|
|
59
|
+
verifyAccessToken(token: string): Promise<AccessToken>;
|
|
60
|
+
authorize(params: AuthorizeParams): Promise<string>;
|
|
61
|
+
token(params: TokenParams): Promise<TokenResponse>;
|
|
62
|
+
revoke(params: RevokeParams): Promise<void>;
|
|
63
|
+
readonly endpoints: OAuthEndpoints;
|
|
64
|
+
}
|
|
65
|
+
export interface AuthorizeParams {
|
|
66
|
+
response_type: string;
|
|
67
|
+
client_id: string;
|
|
68
|
+
redirect_uri: string;
|
|
69
|
+
scope?: string;
|
|
70
|
+
state?: string;
|
|
71
|
+
code_challenge: string;
|
|
72
|
+
code_challenge_method: string;
|
|
73
|
+
}
|
|
74
|
+
export interface TokenParams {
|
|
75
|
+
grant_type: string;
|
|
76
|
+
client_id: string;
|
|
77
|
+
client_secret?: string;
|
|
78
|
+
code?: string;
|
|
79
|
+
redirect_uri?: string;
|
|
80
|
+
refresh_token?: string;
|
|
81
|
+
code_verifier?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface RevokeParams {
|
|
84
|
+
token: string;
|
|
85
|
+
token_type_hint?: string;
|
|
86
|
+
client_id?: string;
|
|
87
|
+
client_secret?: string;
|
|
88
|
+
}
|
|
89
|
+
export interface OAuthConfigOptions {
|
|
90
|
+
endpoints: {
|
|
91
|
+
authorizationUrl: string;
|
|
92
|
+
tokenUrl: string;
|
|
93
|
+
revocationUrl?: string;
|
|
94
|
+
userInfoUrl?: string;
|
|
95
|
+
registerUrl: string;
|
|
96
|
+
};
|
|
97
|
+
issuerUrl: string;
|
|
98
|
+
baseUrl: string;
|
|
99
|
+
serviceDocumentationUrl?: string;
|
|
100
|
+
pathPrefix?: string;
|
|
101
|
+
defaultScopes?: string[];
|
|
102
|
+
}
|
|
103
|
+
export interface OAuthProxyConfig {
|
|
104
|
+
endpoints: OAuthEndpoints;
|
|
105
|
+
issuerUrl: string;
|
|
106
|
+
baseUrl: string;
|
|
107
|
+
serviceDocumentationUrl?: string;
|
|
108
|
+
pathPrefix?: string;
|
|
109
|
+
storage?: OAuthStorage;
|
|
110
|
+
verifyAccessToken?: (token: string) => Promise<any>;
|
|
111
|
+
defaultScopes?: string[];
|
|
112
|
+
}
|