@inkeep/agents-manage-api 0.1.1 → 0.1.6
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/LICENSE.md +29 -17
- package/README.md +1 -1
- package/SUPPLEMENTAL_TERMS.md +40 -0
- package/dist/index.cjs +5083 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -4
- package/dist/index.js +5046 -35
- package/package.json +15 -13
- package/dist/ManagementServer.d.ts +0 -28
- package/dist/ManagementServer.d.ts.map +0 -1
- package/dist/ManagementServer.js +0 -41
- package/dist/__tests__/setup.d.ts +0 -2
- package/dist/__tests__/setup.d.ts.map +0 -1
- package/dist/__tests__/setup.js +0 -26
- package/dist/__tests__/utils/testProject.d.ts +0 -18
- package/dist/__tests__/utils/testProject.d.ts.map +0 -1
- package/dist/__tests__/utils/testProject.js +0 -26
- package/dist/__tests__/utils/testRequest.d.ts +0 -2
- package/dist/__tests__/utils/testRequest.d.ts.map +0 -1
- package/dist/__tests__/utils/testRequest.js +0 -11
- package/dist/__tests__/utils/testTenant.d.ts +0 -64
- package/dist/__tests__/utils/testTenant.d.ts.map +0 -1
- package/dist/__tests__/utils/testTenant.js +0 -71
- package/dist/app.d.ts +0 -4
- package/dist/app.d.ts.map +0 -1
- package/dist/app.js +0 -140
- package/dist/data/conversations.d.ts +0 -59
- package/dist/data/conversations.d.ts.map +0 -1
- package/dist/data/conversations.js +0 -216
- package/dist/data/db/clean.d.ts +0 -6
- package/dist/data/db/clean.d.ts.map +0 -1
- package/dist/data/db/clean.js +0 -77
- package/dist/data/db/dbClient.d.ts +0 -3
- package/dist/data/db/dbClient.d.ts.map +0 -1
- package/dist/data/db/dbClient.js +0 -13
- package/dist/data/graphFull.d.ts +0 -11
- package/dist/data/graphFull.d.ts.map +0 -1
- package/dist/data/graphFull.js +0 -90
- package/dist/data/graphFullClient.d.ts +0 -22
- package/dist/data/graphFullClient.d.ts.map +0 -1
- package/dist/data/graphFullClient.js +0 -189
- package/dist/data/tools.d.ts +0 -81
- package/dist/data/tools.d.ts.map +0 -1
- package/dist/data/tools.js +0 -266
- package/dist/env.d.ts +0 -41
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js +0 -59
- package/dist/index.d.ts.map +0 -1
- package/dist/logger.d.ts +0 -4
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -32
- package/dist/middleware/auth.d.ts +0 -12
- package/dist/middleware/auth.d.ts.map +0 -1
- package/dist/middleware/auth.js +0 -36
- package/dist/openapi.d.ts +0 -2
- package/dist/openapi.d.ts.map +0 -1
- package/dist/openapi.js +0 -38
- package/dist/routes/agentArtifactComponents.d.ts +0 -4
- package/dist/routes/agentArtifactComponents.d.ts.map +0 -1
- package/dist/routes/agentArtifactComponents.js +0 -230
- package/dist/routes/agentDataComponents.d.ts +0 -4
- package/dist/routes/agentDataComponents.d.ts.map +0 -1
- package/dist/routes/agentDataComponents.js +0 -225
- package/dist/routes/agentGraph.d.ts +0 -4
- package/dist/routes/agentGraph.d.ts.map +0 -1
- package/dist/routes/agentGraph.js +0 -289
- package/dist/routes/agentRelations.d.ts +0 -4
- package/dist/routes/agentRelations.d.ts.map +0 -1
- package/dist/routes/agentRelations.js +0 -290
- package/dist/routes/agentToolRelations.d.ts +0 -4
- package/dist/routes/agentToolRelations.d.ts.map +0 -1
- package/dist/routes/agentToolRelations.js +0 -342
- package/dist/routes/agents.d.ts +0 -4
- package/dist/routes/agents.d.ts.map +0 -1
- package/dist/routes/agents.js +0 -213
- package/dist/routes/apiKeys.d.ts +0 -4
- package/dist/routes/apiKeys.d.ts.map +0 -1
- package/dist/routes/apiKeys.js +0 -236
- package/dist/routes/artifactComponents.d.ts +0 -4
- package/dist/routes/artifactComponents.d.ts.map +0 -1
- package/dist/routes/artifactComponents.js +0 -202
- package/dist/routes/contextConfigs.d.ts +0 -4
- package/dist/routes/contextConfigs.d.ts.map +0 -1
- package/dist/routes/contextConfigs.js +0 -181
- package/dist/routes/credentials.d.ts +0 -4
- package/dist/routes/credentials.d.ts.map +0 -1
- package/dist/routes/credentials.js +0 -219
- package/dist/routes/dataComponents.d.ts +0 -4
- package/dist/routes/dataComponents.d.ts.map +0 -1
- package/dist/routes/dataComponents.js +0 -188
- package/dist/routes/externalAgents.d.ts +0 -4
- package/dist/routes/externalAgents.d.ts.map +0 -1
- package/dist/routes/externalAgents.js +0 -216
- package/dist/routes/graphFull.d.ts +0 -4
- package/dist/routes/graphFull.d.ts.map +0 -1
- package/dist/routes/graphFull.js +0 -248
- package/dist/routes/index.d.ts +0 -4
- package/dist/routes/index.d.ts.map +0 -1
- package/dist/routes/index.js +0 -37
- package/dist/routes/oauth.d.ts +0 -14
- package/dist/routes/oauth.d.ts.map +0 -1
- package/dist/routes/oauth.js +0 -191
- package/dist/routes/projects.d.ts +0 -4
- package/dist/routes/projects.d.ts.map +0 -1
- package/dist/routes/projects.js +0 -221
- package/dist/routes/tools.d.ts +0 -4
- package/dist/routes/tools.d.ts.map +0 -1
- package/dist/routes/tools.js +0 -547
- package/dist/utils/auth-detection.d.ts +0 -22
- package/dist/utils/auth-detection.d.ts.map +0 -1
- package/dist/utils/auth-detection.js +0 -149
- package/dist/utils/oauth-service.d.ts +0 -88
- package/dist/utils/oauth-service.d.ts.map +0 -1
- package/dist/utils/oauth-service.js +0 -240
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized authentication detection utilities for MCP tools
|
|
3
|
-
*/
|
|
4
|
-
import { getLogger } from '../logger.js';
|
|
5
|
-
const logger = getLogger('auth-detection');
|
|
6
|
-
/**
|
|
7
|
-
* Helper function to construct well-known OAuth endpoint URLs
|
|
8
|
-
*/
|
|
9
|
-
const getWellKnownUrls = (baseUrl) => [
|
|
10
|
-
`${baseUrl}/.well-known/oauth-authorization-server`,
|
|
11
|
-
`${baseUrl}/.well-known/openid-configuration`,
|
|
12
|
-
];
|
|
13
|
-
/**
|
|
14
|
-
* Helper function to validate OAuth metadata for PKCE support
|
|
15
|
-
*/
|
|
16
|
-
const validateOAuthMetadata = (metadata) => {
|
|
17
|
-
return metadata.code_challenge_methods_supported?.includes('S256');
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Helper function to construct OAuthConfig from metadata
|
|
21
|
-
*/
|
|
22
|
-
const buildOAuthConfig = (metadata) => ({
|
|
23
|
-
authorizationUrl: metadata.authorization_endpoint,
|
|
24
|
-
tokenUrl: metadata.token_endpoint,
|
|
25
|
-
registrationUrl: metadata.registration_endpoint,
|
|
26
|
-
supportsDynamicRegistration: !!metadata.registration_endpoint,
|
|
27
|
-
});
|
|
28
|
-
/**
|
|
29
|
-
* Helper function to try OAuth discovery at well-known endpoints
|
|
30
|
-
*/
|
|
31
|
-
const tryWellKnownEndpoints = async (baseUrl) => {
|
|
32
|
-
const wellKnownUrls = getWellKnownUrls(baseUrl);
|
|
33
|
-
for (const wellKnownUrl of wellKnownUrls) {
|
|
34
|
-
try {
|
|
35
|
-
const response = await fetch(wellKnownUrl);
|
|
36
|
-
if (response.ok) {
|
|
37
|
-
const metadata = await response.json();
|
|
38
|
-
if (validateOAuthMetadata(metadata)) {
|
|
39
|
-
logger.debug({ baseUrl, wellKnownUrl }, 'OAuth 2.1/PKCE support detected');
|
|
40
|
-
return buildOAuthConfig(metadata);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
logger.debug({ wellKnownUrl, error }, 'OAuth endpoint check failed');
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Check if a server supports OAuth 2.1/PKCE endpoints (simple boolean)
|
|
52
|
-
*/
|
|
53
|
-
const checkForOAuthEndpoints = async (serverUrl) => {
|
|
54
|
-
const config = await discoverOAuthEndpoints(serverUrl);
|
|
55
|
-
return config !== null;
|
|
56
|
-
};
|
|
57
|
-
/**
|
|
58
|
-
* Full OAuth endpoint discovery with complete configuration
|
|
59
|
-
*/
|
|
60
|
-
export const discoverOAuthEndpoints = async (serverUrl) => {
|
|
61
|
-
try {
|
|
62
|
-
// 1. Try direct 401 response first
|
|
63
|
-
const response = await fetch(serverUrl, {
|
|
64
|
-
method: 'POST',
|
|
65
|
-
headers: { 'Content-Type': 'application/json' },
|
|
66
|
-
body: JSON.stringify({}),
|
|
67
|
-
});
|
|
68
|
-
if (response.status === 401) {
|
|
69
|
-
const wwwAuth = response.headers.get('WWW-Authenticate');
|
|
70
|
-
if (wwwAuth) {
|
|
71
|
-
// Parse Protected Resource Metadata URL from WWW-Authenticate header
|
|
72
|
-
const metadataMatch = wwwAuth.match(/as_uri="([^"]+)"/);
|
|
73
|
-
if (metadataMatch) {
|
|
74
|
-
const metadataResponse = await fetch(metadataMatch[1]);
|
|
75
|
-
if (metadataResponse.ok) {
|
|
76
|
-
const metadata = await metadataResponse.json();
|
|
77
|
-
if (metadata.authorization_servers?.length > 0) {
|
|
78
|
-
return await tryWellKnownEndpoints(metadata.authorization_servers[0]);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
catch (_error) {
|
|
86
|
-
// Continue to well-known endpoints
|
|
87
|
-
}
|
|
88
|
-
// 2. Try well-known endpoints
|
|
89
|
-
const url = new URL(serverUrl);
|
|
90
|
-
const baseUrl = `${url.protocol}//${url.host}`;
|
|
91
|
-
return await tryWellKnownEndpoints(baseUrl);
|
|
92
|
-
};
|
|
93
|
-
/**
|
|
94
|
-
* Detect if OAuth 2.1/PKCE authentication is specifically required for a tool
|
|
95
|
-
*/
|
|
96
|
-
export const detectAuthenticationRequired = async (tool, error) => {
|
|
97
|
-
const serverUrl = tool.config.mcp.server.url;
|
|
98
|
-
// 1. First, try OAuth 2.1/PKCE endpoint discovery (most reliable for our use case)
|
|
99
|
-
let hasOAuthEndpoints = false;
|
|
100
|
-
try {
|
|
101
|
-
hasOAuthEndpoints = await checkForOAuthEndpoints(serverUrl);
|
|
102
|
-
if (hasOAuthEndpoints) {
|
|
103
|
-
logger.info({ toolId: tool.id, serverUrl }, 'OAuth 2.1/PKCE support confirmed via endpoint discovery');
|
|
104
|
-
return true; // Server supports OAuth 2.1/PKCE, prioritize this
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
catch (discoveryError) {
|
|
108
|
-
logger.debug({ toolId: tool.id, discoveryError }, 'OAuth endpoint discovery failed');
|
|
109
|
-
}
|
|
110
|
-
// 2. Check for 401 with OAuth-specific WWW-Authenticate header
|
|
111
|
-
try {
|
|
112
|
-
const response = await fetch(serverUrl, {
|
|
113
|
-
method: 'POST',
|
|
114
|
-
headers: { 'Content-Type': 'application/json' },
|
|
115
|
-
body: JSON.stringify({
|
|
116
|
-
jsonrpc: '2.0',
|
|
117
|
-
method: 'initialize',
|
|
118
|
-
id: 1,
|
|
119
|
-
params: { protocolVersion: '2024-11-05', capabilities: {} },
|
|
120
|
-
}),
|
|
121
|
-
});
|
|
122
|
-
// Check for 401 with OAuth-specific WWW-Authenticate header
|
|
123
|
-
if (response.status === 401) {
|
|
124
|
-
const wwwAuth = response.headers.get('WWW-Authenticate');
|
|
125
|
-
// Only return true for OAuth-specific auth schemes (not Basic, API Key, etc.)
|
|
126
|
-
if (wwwAuth &&
|
|
127
|
-
(wwwAuth.toLowerCase().includes('bearer') ||
|
|
128
|
-
wwwAuth.toLowerCase().includes('oauth') ||
|
|
129
|
-
wwwAuth.toLowerCase().includes('authorization_uri'))) {
|
|
130
|
-
logger.info({ toolId: tool.id, wwwAuth }, 'OAuth authentication detected via WWW-Authenticate header');
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (fetchError) {
|
|
136
|
-
logger.debug({ toolId: tool.id, fetchError }, 'Direct fetch authentication check failed');
|
|
137
|
-
}
|
|
138
|
-
// 3. Check if 401 error message AND OAuth endpoints exist together
|
|
139
|
-
if (error.message.includes('401') || error.message.toLowerCase().includes('unauthorized')) {
|
|
140
|
-
if (hasOAuthEndpoints) {
|
|
141
|
-
logger.info({ toolId: tool.id, error: error.message }, 'OAuth required: 401 error + OAuth endpoints detected');
|
|
142
|
-
return true; // Only return true if BOTH 401 AND OAuth endpoints exist
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
// If none of the OAuth-specific checks pass, return false
|
|
146
|
-
// (This means we won't trigger OAuth flow for Basic Auth, API keys, etc.)
|
|
147
|
-
logger.debug({ toolId: tool.id, error: error.message }, 'No OAuth authentication requirement detected');
|
|
148
|
-
return false;
|
|
149
|
-
};
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized OAuth service for MCP tools
|
|
3
|
-
* Handles the complete OAuth 2.1/PKCE flow for MCP tool authentication
|
|
4
|
-
*/
|
|
5
|
-
import type { McpTool } from '@inkeep/agents-core';
|
|
6
|
-
import { type OAuthConfig } from './auth-detection.js';
|
|
7
|
-
/**
|
|
8
|
-
* Retrieve and remove PKCE verifier
|
|
9
|
-
*/
|
|
10
|
-
export declare function retrievePKCEVerifier(state: string): {
|
|
11
|
-
codeVerifier: string;
|
|
12
|
-
toolId: string;
|
|
13
|
-
tenantId: string;
|
|
14
|
-
projectId: string;
|
|
15
|
-
clientId: string;
|
|
16
|
-
} | null;
|
|
17
|
-
/**
|
|
18
|
-
* OAuth client configuration
|
|
19
|
-
*/
|
|
20
|
-
interface OAuthClientConfig {
|
|
21
|
-
defaultClientId?: string;
|
|
22
|
-
clientName?: string;
|
|
23
|
-
clientUri?: string;
|
|
24
|
-
logoUri?: string;
|
|
25
|
-
redirectBaseUrl?: string;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* OAuth flow initiation result
|
|
29
|
-
*/
|
|
30
|
-
interface OAuthInitiationResult {
|
|
31
|
-
redirectUrl: string;
|
|
32
|
-
state: string;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Token exchange result
|
|
36
|
-
*/
|
|
37
|
-
interface TokenExchangeResult {
|
|
38
|
-
tokens: any;
|
|
39
|
-
oAuthConfig: OAuthConfig;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* OAuth service class that handles the complete OAuth flow
|
|
43
|
-
*/
|
|
44
|
-
declare class OAuthService {
|
|
45
|
-
private defaultConfig;
|
|
46
|
-
constructor(config?: OAuthClientConfig);
|
|
47
|
-
/**
|
|
48
|
-
* Initiate OAuth flow for an MCP tool
|
|
49
|
-
*/
|
|
50
|
-
initiateOAuthFlow(params: {
|
|
51
|
-
tool: McpTool;
|
|
52
|
-
tenantId: string;
|
|
53
|
-
projectId: string;
|
|
54
|
-
toolId: string;
|
|
55
|
-
}): Promise<OAuthInitiationResult>;
|
|
56
|
-
/**
|
|
57
|
-
* Exchange authorization code for access tokens
|
|
58
|
-
*/
|
|
59
|
-
exchangeCodeForTokens(params: {
|
|
60
|
-
code: string;
|
|
61
|
-
codeVerifier: string;
|
|
62
|
-
clientId: string;
|
|
63
|
-
tool: McpTool;
|
|
64
|
-
}): Promise<TokenExchangeResult>;
|
|
65
|
-
/**
|
|
66
|
-
* Perform dynamic client registration
|
|
67
|
-
*/
|
|
68
|
-
private performDynamicClientRegistration;
|
|
69
|
-
/**
|
|
70
|
-
* Build authorization URL
|
|
71
|
-
*/
|
|
72
|
-
private buildAuthorizationUrl;
|
|
73
|
-
/**
|
|
74
|
-
* Exchange code using openid-client library
|
|
75
|
-
*/
|
|
76
|
-
private exchangeWithOpenIdClient;
|
|
77
|
-
/**
|
|
78
|
-
* Internal PKCE generation
|
|
79
|
-
*/
|
|
80
|
-
private generatePKCEInternal;
|
|
81
|
-
/**
|
|
82
|
-
* Manual token exchange fallback
|
|
83
|
-
*/
|
|
84
|
-
private exchangeManually;
|
|
85
|
-
}
|
|
86
|
-
export declare const oauthService: OAuthService;
|
|
87
|
-
export {};
|
|
88
|
-
//# sourceMappingURL=oauth-service.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-service.d.ts","sourceRoot":"","sources":["../../src/utils/oauth-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAsC/E;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAOP;AAED;;GAEG;AACH,UAAU,iBAAiB;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,UAAU,qBAAqB;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,UAAU,mBAAmB;IAC3B,MAAM,EAAE,GAAG,CAAC;IACZ,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED;;GAEG;AACH,cAAM,YAAY;IAChB,OAAO,CAAC,aAAa,CAA8B;gBAEvC,MAAM,GAAE,iBAAsB;IAe1C;;OAEG;IACG,iBAAiB,CAAC,MAAM,EAAE;QAC9B,IAAI,EAAE,OAAO,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA6ClC;;OAEG;IACG,qBAAqB,CAAC,MAAM,EAAE;QAClC,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,OAAO,CAAC;KACf,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA2ChC;;OAEG;YACW,gCAAgC;IAiD9C;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAsB7B;;OAEG;YACW,wBAAwB;IAiCtC;;OAEG;YACW,oBAAoB;IAgBlC;;OAEG;YACW,gBAAgB;CA4C/B;AAGD,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized OAuth service for MCP tools
|
|
3
|
-
* Handles the complete OAuth 2.1/PKCE flow for MCP tool authentication
|
|
4
|
-
*/
|
|
5
|
-
import { getLogger } from '../logger.js';
|
|
6
|
-
import { discoverOAuthEndpoints } from './auth-detection.js';
|
|
7
|
-
const logger = getLogger('oauth-service');
|
|
8
|
-
// PKCE storage (TODO: Use Redis or database in production)
|
|
9
|
-
const pkceStore = new Map();
|
|
10
|
-
/**
|
|
11
|
-
* Store PKCE verifier for later use in token exchange
|
|
12
|
-
*/
|
|
13
|
-
function storePKCEVerifier(state, codeVerifier, toolId, tenantId, projectId, clientId) {
|
|
14
|
-
pkceStore.set(state, { codeVerifier, toolId, tenantId, projectId, clientId });
|
|
15
|
-
// Clean up after 10 minutes (OAuth flows should complete quickly)
|
|
16
|
-
setTimeout(() => {
|
|
17
|
-
pkceStore.delete(state);
|
|
18
|
-
}, 10 * 60 * 1000);
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Retrieve and remove PKCE verifier
|
|
22
|
-
*/
|
|
23
|
-
export function retrievePKCEVerifier(state) {
|
|
24
|
-
const data = pkceStore.get(state);
|
|
25
|
-
if (data) {
|
|
26
|
-
pkceStore.delete(state); // One-time use
|
|
27
|
-
return data;
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* OAuth service class that handles the complete OAuth flow
|
|
33
|
-
*/
|
|
34
|
-
class OAuthService {
|
|
35
|
-
defaultConfig;
|
|
36
|
-
constructor(config = {}) {
|
|
37
|
-
this.defaultConfig = {
|
|
38
|
-
defaultClientId: config.defaultClientId || process.env.DEFAULT_OAUTH_CLIENT_ID || 'mcp-client',
|
|
39
|
-
clientName: config.clientName || process.env.OAUTH_CLIENT_NAME || 'Inkeep Agent Framework',
|
|
40
|
-
clientUri: config.clientUri || process.env.OAUTH_CLIENT_URI || 'https://inkeep.com',
|
|
41
|
-
logoUri: config.logoUri ||
|
|
42
|
-
process.env.OAUTH_CLIENT_LOGO_URI ||
|
|
43
|
-
'https://storage.googleapis.com/inkeep-static-assets/Favicon_gray_large.png',
|
|
44
|
-
redirectBaseUrl: config.redirectBaseUrl || process.env.OAUTH_REDIRECT_BASE_URL || 'http://localhost:3002',
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Initiate OAuth flow for an MCP tool
|
|
49
|
-
*/
|
|
50
|
-
async initiateOAuthFlow(params) {
|
|
51
|
-
const { tool, tenantId, projectId, toolId } = params;
|
|
52
|
-
// 1. Detect OAuth requirements
|
|
53
|
-
const oAuthConfig = await discoverOAuthEndpoints(tool.config.mcp.server.url);
|
|
54
|
-
if (!oAuthConfig) {
|
|
55
|
-
throw new Error('OAuth not supported by this server');
|
|
56
|
-
}
|
|
57
|
-
// 2. Generate PKCE parameters
|
|
58
|
-
const { codeVerifier, codeChallenge } = await this.generatePKCEInternal();
|
|
59
|
-
// 3. Handle dynamic client registration if supported
|
|
60
|
-
const redirectUri = `${this.defaultConfig.redirectBaseUrl}/oauth/callback`;
|
|
61
|
-
let clientId = this.defaultConfig.defaultClientId;
|
|
62
|
-
if (oAuthConfig.supportsDynamicRegistration && oAuthConfig.registrationUrl) {
|
|
63
|
-
clientId = await this.performDynamicClientRegistration(oAuthConfig.registrationUrl, redirectUri);
|
|
64
|
-
}
|
|
65
|
-
// 4. Build OAuth URL
|
|
66
|
-
const state = `tool_${toolId}`;
|
|
67
|
-
const authUrl = this.buildAuthorizationUrl({
|
|
68
|
-
oAuthConfig,
|
|
69
|
-
clientId,
|
|
70
|
-
redirectUri,
|
|
71
|
-
state,
|
|
72
|
-
codeChallenge,
|
|
73
|
-
resource: tool.config.mcp.server.url,
|
|
74
|
-
});
|
|
75
|
-
// 5. Store PKCE verifier for callback handling
|
|
76
|
-
storePKCEVerifier(state, codeVerifier, toolId, tenantId, projectId, clientId);
|
|
77
|
-
logger.info({ toolId, oAuthConfig, tenantId, projectId }, 'OAuth flow initiated successfully');
|
|
78
|
-
return {
|
|
79
|
-
redirectUrl: authUrl,
|
|
80
|
-
state,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Exchange authorization code for access tokens
|
|
85
|
-
*/
|
|
86
|
-
async exchangeCodeForTokens(params) {
|
|
87
|
-
const { code, codeVerifier, clientId, tool } = params;
|
|
88
|
-
// Discover OAuth server endpoints from MCP server
|
|
89
|
-
const oAuthConfig = await discoverOAuthEndpoints(tool.config.mcp.server.url);
|
|
90
|
-
if (!oAuthConfig?.tokenUrl) {
|
|
91
|
-
throw new Error('Could not discover OAuth token endpoint');
|
|
92
|
-
}
|
|
93
|
-
const redirectUri = `${this.defaultConfig.redirectBaseUrl}/oauth/callback`;
|
|
94
|
-
let tokens;
|
|
95
|
-
try {
|
|
96
|
-
// Try openid-client first (more robust)
|
|
97
|
-
tokens = await this.exchangeWithOpenIdClient({
|
|
98
|
-
oAuthConfig,
|
|
99
|
-
clientId,
|
|
100
|
-
code,
|
|
101
|
-
codeVerifier,
|
|
102
|
-
redirectUri,
|
|
103
|
-
});
|
|
104
|
-
logger.info({ tokenType: tokens.token_type }, 'Token exchange successful with openid-client');
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
logger.warn({ error: error instanceof Error ? error.message : error }, 'openid-client failed, falling back to manual token exchange');
|
|
108
|
-
// Fallback to manual token exchange
|
|
109
|
-
tokens = await this.exchangeManually({
|
|
110
|
-
oAuthConfig,
|
|
111
|
-
clientId,
|
|
112
|
-
code,
|
|
113
|
-
codeVerifier,
|
|
114
|
-
redirectUri,
|
|
115
|
-
});
|
|
116
|
-
logger.info({ tokenType: tokens.token_type }, 'Manual token exchange successful');
|
|
117
|
-
}
|
|
118
|
-
return { tokens, oAuthConfig };
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Perform dynamic client registration
|
|
122
|
-
*/
|
|
123
|
-
async performDynamicClientRegistration(registrationUrl, redirectUri) {
|
|
124
|
-
logger.info({ registrationUrl }, 'Attempting dynamic client registration');
|
|
125
|
-
try {
|
|
126
|
-
const registrationResponse = await fetch(registrationUrl, {
|
|
127
|
-
method: 'POST',
|
|
128
|
-
headers: {
|
|
129
|
-
'Content-Type': 'application/json',
|
|
130
|
-
Accept: 'application/json',
|
|
131
|
-
},
|
|
132
|
-
body: JSON.stringify({
|
|
133
|
-
client_name: this.defaultConfig.clientName,
|
|
134
|
-
client_uri: this.defaultConfig.clientUri,
|
|
135
|
-
logo_uri: this.defaultConfig.logoUri,
|
|
136
|
-
redirect_uris: [redirectUri],
|
|
137
|
-
grant_types: ['authorization_code'],
|
|
138
|
-
response_types: ['code'],
|
|
139
|
-
token_endpoint_auth_method: 'none', // PKCE only, no client secret
|
|
140
|
-
application_type: 'native', // For PKCE flows
|
|
141
|
-
}),
|
|
142
|
-
});
|
|
143
|
-
if (registrationResponse.ok) {
|
|
144
|
-
const registration = await registrationResponse.json();
|
|
145
|
-
logger.info({ clientId: registration.client_id }, 'Dynamic client registration successful');
|
|
146
|
-
return registration.client_id;
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
const errorText = await registrationResponse.text();
|
|
150
|
-
logger.warn({
|
|
151
|
-
status: registrationResponse.status,
|
|
152
|
-
errorText,
|
|
153
|
-
}, 'Dynamic client registration failed, using default client_id');
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
catch (regError) {
|
|
157
|
-
logger.warn({ error: regError }, 'Dynamic client registration error, using default client_id');
|
|
158
|
-
}
|
|
159
|
-
return this.defaultConfig.defaultClientId;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Build authorization URL
|
|
163
|
-
*/
|
|
164
|
-
buildAuthorizationUrl(params) {
|
|
165
|
-
const { oAuthConfig, clientId, redirectUri, state, codeChallenge, resource } = params;
|
|
166
|
-
const authUrl = new URL(oAuthConfig.authorizationUrl);
|
|
167
|
-
authUrl.searchParams.set('response_type', 'code');
|
|
168
|
-
authUrl.searchParams.set('client_id', clientId);
|
|
169
|
-
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
170
|
-
authUrl.searchParams.set('state', state);
|
|
171
|
-
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
172
|
-
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
173
|
-
authUrl.searchParams.set('resource', resource); // Required by MCP spec
|
|
174
|
-
return authUrl.toString();
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Exchange code using openid-client library
|
|
178
|
-
*/
|
|
179
|
-
async exchangeWithOpenIdClient(params) {
|
|
180
|
-
const { oAuthConfig, clientId, code, codeVerifier, redirectUri } = params;
|
|
181
|
-
// Import openid-client for proper OAuth handling
|
|
182
|
-
const oauth = await import('openid-client');
|
|
183
|
-
// Extract OAuth server base URL from token URL
|
|
184
|
-
const tokenUrl = new URL(oAuthConfig.tokenUrl);
|
|
185
|
-
const oauthServerUrl = `${tokenUrl.protocol}//${tokenUrl.host}`;
|
|
186
|
-
logger.info({ oauthServerUrl, clientId }, 'Attempting openid-client discovery');
|
|
187
|
-
const config = await oauth.discovery(new URL(oauthServerUrl), clientId, undefined // No client secret for PKCE
|
|
188
|
-
);
|
|
189
|
-
const callbackUrl = new URL(`${redirectUri}?${new URLSearchParams({ code, state: 'unused' }).toString()}`);
|
|
190
|
-
return await oauth.authorizationCodeGrant(config, callbackUrl, {
|
|
191
|
-
pkceCodeVerifier: codeVerifier,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Internal PKCE generation
|
|
196
|
-
*/
|
|
197
|
-
async generatePKCEInternal() {
|
|
198
|
-
const codeVerifier = Buffer.from(Array.from(crypto.getRandomValues(new Uint8Array(32)))).toString('base64url');
|
|
199
|
-
const encoder = new TextEncoder();
|
|
200
|
-
const data = encoder.encode(codeVerifier);
|
|
201
|
-
const hash = await crypto.subtle.digest('SHA-256', data);
|
|
202
|
-
const codeChallenge = Buffer.from(hash).toString('base64url');
|
|
203
|
-
return { codeVerifier, codeChallenge };
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Manual token exchange fallback
|
|
207
|
-
*/
|
|
208
|
-
async exchangeManually(params) {
|
|
209
|
-
const { oAuthConfig, clientId, code, codeVerifier, redirectUri } = params;
|
|
210
|
-
logger.info({ tokenUrl: oAuthConfig.tokenUrl }, 'Attempting manual token exchange');
|
|
211
|
-
const tokenResponse = await fetch(oAuthConfig.tokenUrl, {
|
|
212
|
-
method: 'POST',
|
|
213
|
-
headers: {
|
|
214
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
215
|
-
Accept: 'application/json',
|
|
216
|
-
},
|
|
217
|
-
body: new URLSearchParams({
|
|
218
|
-
grant_type: 'authorization_code',
|
|
219
|
-
code,
|
|
220
|
-
redirect_uri: redirectUri,
|
|
221
|
-
client_id: clientId,
|
|
222
|
-
code_verifier: codeVerifier, // PKCE verification
|
|
223
|
-
}),
|
|
224
|
-
});
|
|
225
|
-
if (!tokenResponse.ok) {
|
|
226
|
-
const errorText = await tokenResponse.text();
|
|
227
|
-
logger.error({
|
|
228
|
-
status: tokenResponse.status,
|
|
229
|
-
statusText: tokenResponse.statusText,
|
|
230
|
-
...(process.env.NODE_ENV === 'development' && { errorText }),
|
|
231
|
-
clientId,
|
|
232
|
-
tokenUrl: oAuthConfig.tokenUrl,
|
|
233
|
-
}, 'Token exchange failed');
|
|
234
|
-
throw new Error('Authentication failed. Please try again or contact support.');
|
|
235
|
-
}
|
|
236
|
-
return await tokenResponse.json();
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
// Default instance for convenience
|
|
240
|
-
export const oauthService = new OAuthService();
|