@1mcp/agent 0.13.0 → 0.13.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/build/auth/sdkOAuthClientProvider.d.ts +1 -1
- package/build/auth/sdkOAuthClientProvider.d.ts.map +1 -1
- package/build/auth/sdkOAuthClientProvider.js +11 -6
- package/build/auth/sdkOAuthClientProvider.js.map +1 -1
- package/build/auth/sdkOAuthServerProvider.d.ts +4 -3
- package/build/auth/sdkOAuthServerProvider.d.ts.map +1 -1
- package/build/auth/sdkOAuthServerProvider.js +41 -110
- package/build/auth/sdkOAuthServerProvider.js.map +1 -1
- package/build/auth/sessionTypes.d.ts +24 -10
- package/build/auth/sessionTypes.d.ts.map +1 -1
- package/build/auth/storage/authCodeRepository.d.ts +25 -0
- package/build/auth/storage/authCodeRepository.d.ts.map +1 -0
- package/build/auth/storage/authCodeRepository.js +50 -0
- package/build/auth/storage/authCodeRepository.js.map +1 -0
- package/build/auth/storage/authRequestRepository.d.ts +25 -0
- package/build/auth/storage/authRequestRepository.d.ts.map +1 -0
- package/build/auth/storage/authRequestRepository.js +51 -0
- package/build/auth/storage/authRequestRepository.js.map +1 -0
- package/build/auth/storage/clientDataRepository.d.ts +25 -0
- package/build/auth/storage/clientDataRepository.d.ts.map +1 -0
- package/build/auth/storage/clientDataRepository.js +45 -0
- package/build/auth/storage/clientDataRepository.js.map +1 -0
- package/build/auth/storage/clientSessionRepository.d.ts +77 -0
- package/build/auth/storage/clientSessionRepository.d.ts.map +1 -0
- package/build/auth/storage/clientSessionRepository.js +120 -0
- package/build/auth/storage/clientSessionRepository.js.map +1 -0
- package/build/auth/storage/fileStorageService.d.ts +68 -0
- package/build/auth/storage/fileStorageService.d.ts.map +1 -0
- package/build/auth/storage/fileStorageService.js +262 -0
- package/build/auth/storage/fileStorageService.js.map +1 -0
- package/build/auth/storage/oauthStorageService.d.ts +57 -0
- package/build/auth/storage/oauthStorageService.d.ts.map +1 -0
- package/build/auth/storage/oauthStorageService.js +131 -0
- package/build/auth/storage/oauthStorageService.js.map +1 -0
- package/build/auth/storage/sessionRepository.d.ts +29 -0
- package/build/auth/storage/sessionRepository.d.ts.map +1 -0
- package/build/auth/storage/sessionRepository.js +64 -0
- package/build/auth/storage/sessionRepository.js.map +1 -0
- package/build/constants.d.ts +26 -11
- package/build/constants.d.ts.map +1 -1
- package/build/constants.js +32 -11
- package/build/constants.js.map +1 -1
- package/build/core/server/agentConfig.js +2 -2
- package/build/core/server/agentConfig.js.map +1 -1
- package/build/transport/http/middlewares/scopeAuthMiddleware.d.ts +4 -7
- package/build/transport/http/middlewares/scopeAuthMiddleware.d.ts.map +1 -1
- package/build/transport/http/middlewares/scopeAuthMiddleware.js +5 -12
- package/build/transport/http/middlewares/scopeAuthMiddleware.js.map +1 -1
- package/build/transport/http/routes/oauthRoutes.d.ts +6 -2
- package/build/transport/http/routes/oauthRoutes.d.ts.map +1 -1
- package/build/transport/http/routes/oauthRoutes.js +331 -335
- package/build/transport/http/routes/oauthRoutes.js.map +1 -1
- package/build/transport/http/routes/sseRoutes.d.ts +2 -1
- package/build/transport/http/routes/sseRoutes.d.ts.map +1 -1
- package/build/transport/http/routes/sseRoutes.js +3 -2
- package/build/transport/http/routes/sseRoutes.js.map +1 -1
- package/build/transport/http/routes/streamableHttpRoutes.d.ts +2 -1
- package/build/transport/http/routes/streamableHttpRoutes.d.ts.map +1 -1
- package/build/transport/http/routes/streamableHttpRoutes.js +3 -2
- package/build/transport/http/routes/streamableHttpRoutes.js.map +1 -1
- package/build/transport/http/server.js +4 -4
- package/build/transport/http/server.js.map +1 -1
- package/build/utils/clientFiltering.d.ts.map +1 -1
- package/build/utils/clientFiltering.js +40 -5
- package/build/utils/clientFiltering.js.map +1 -1
- package/package.json +1 -1
- package/build/auth/clientSessionManager.d.ts +0 -94
- package/build/auth/clientSessionManager.d.ts.map +0 -1
- package/build/auth/clientSessionManager.js +0 -220
- package/build/auth/clientSessionManager.js.map +0 -1
- package/build/auth/serverSessionManager.d.ts +0 -185
- package/build/auth/serverSessionManager.d.ts.map +0 -1
- package/build/auth/serverSessionManager.js +0 -461
- package/build/auth/serverSessionManager.js.map +0 -1
|
@@ -5,317 +5,310 @@ import { ServerManager } from '../../../core/server/serverManager.js';
|
|
|
5
5
|
import { ClientStatus } from '../../../core/types/index.js';
|
|
6
6
|
import { OAuthRequiredError } from '../../../core/client/clientManager.js';
|
|
7
7
|
import createClient from '../../../core/client/clientFactory.js';
|
|
8
|
-
import { RATE_LIMIT_CONFIG } from '../../../constants.js';
|
|
8
|
+
import { RATE_LIMIT_CONFIG, AUTH_CONFIG } from '../../../constants.js';
|
|
9
9
|
import { AgentConfigManager } from '../../../core/server/agentConfig.js';
|
|
10
10
|
import { escapeHtml, sanitizeUrlParam, sanitizeErrorMessage, sanitizeServerNameForContext, } from '../../../utils/sanitization.js';
|
|
11
|
-
import {
|
|
12
|
-
import { validateScopes, auditScopeOperation } from '../../../utils/scopeValidation.js';
|
|
11
|
+
import { validateScopes } from '../../../utils/scopeValidation.js';
|
|
13
12
|
import { sensitiveOperationLimiter } from '../middlewares/securityMiddleware.js';
|
|
14
|
-
const router = Router();
|
|
15
|
-
// Rate limiter for OAuth endpoints
|
|
16
|
-
const createOAuthLimiter = () => {
|
|
17
|
-
const serverConfig = AgentConfigManager.getInstance();
|
|
18
|
-
return rateLimit({
|
|
19
|
-
windowMs: serverConfig.getRateLimitWindowMs(),
|
|
20
|
-
max: serverConfig.getRateLimitMax(),
|
|
21
|
-
standardHeaders: true,
|
|
22
|
-
legacyHeaders: false,
|
|
23
|
-
message: RATE_LIMIT_CONFIG.OAUTH.MESSAGE,
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
router.use(createOAuthLimiter());
|
|
27
|
-
/**
|
|
28
|
-
* Check if a server requires OAuth based on runtime behavior
|
|
29
|
-
* A server requires OAuth if it has ever thrown UnauthorizedError (indicated by authorizationUrl or oauthStartTime)
|
|
30
|
-
*/
|
|
31
|
-
function requiresOAuth(service) {
|
|
32
|
-
// The most reliable indicator: server has ever had an authorization URL
|
|
33
|
-
// This means the server threw UnauthorizedError and we captured the OAuth URL
|
|
34
|
-
if (service.authorizationUrl) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
// Secondary indicator: server has ever been in AwaitingOAuth status
|
|
38
|
-
// This means the server threw UnauthorizedError at some point
|
|
39
|
-
if (service.oauthStartTime) {
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
// If currently awaiting OAuth, it definitely requires OAuth
|
|
43
|
-
if (service.status === ClientStatus.AwaitingOAuth) {
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
13
|
/**
|
|
49
|
-
* OAuth
|
|
14
|
+
* Creates OAuth routes with the provided OAuth provider
|
|
50
15
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
16
|
+
export function createOAuthRoutes(oauthProvider) {
|
|
17
|
+
const router = Router();
|
|
18
|
+
// Rate limiter for OAuth endpoints
|
|
19
|
+
const createOAuthLimiter = () => {
|
|
20
|
+
const serverConfig = AgentConfigManager.getInstance();
|
|
21
|
+
return rateLimit({
|
|
22
|
+
windowMs: serverConfig.getRateLimitWindowMs(),
|
|
23
|
+
max: serverConfig.getRateLimitMax(),
|
|
24
|
+
standardHeaders: true,
|
|
25
|
+
legacyHeaders: false,
|
|
26
|
+
message: RATE_LIMIT_CONFIG.OAUTH.MESSAGE,
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
router.use(createOAuthLimiter());
|
|
30
|
+
/**
|
|
31
|
+
* Check if a server requires OAuth based on runtime behavior
|
|
32
|
+
* A server requires OAuth if it has ever thrown UnauthorizedError (indicated by authorizationUrl or oauthStartTime)
|
|
33
|
+
*/
|
|
34
|
+
function requiresOAuth(service) {
|
|
35
|
+
// The most reliable indicator: server has ever had an authorization URL
|
|
36
|
+
// This means the server threw UnauthorizedError and we captured the OAuth URL
|
|
37
|
+
if (service.authorizationUrl) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
// Secondary indicator: server has ever been in AwaitingOAuth status
|
|
41
|
+
// This means the server threw UnauthorizedError at some point
|
|
42
|
+
if (service.oauthStartTime) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// If currently awaiting OAuth, it definitely requires OAuth
|
|
46
|
+
if (service.status === ClientStatus.AwaitingOAuth) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
70
50
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
51
|
+
/**
|
|
52
|
+
* OAuth Dashboard - Shows all services and their OAuth status
|
|
53
|
+
*/
|
|
54
|
+
router.get('/', async (req, res) => {
|
|
55
|
+
try {
|
|
56
|
+
const serverManager = ServerManager.current;
|
|
57
|
+
const clients = serverManager.getClients();
|
|
58
|
+
const services = Array.from(clients.entries()).map(([name, clientInfo]) => ({
|
|
59
|
+
name,
|
|
60
|
+
status: clientInfo.status,
|
|
61
|
+
authorizationUrl: clientInfo.authorizationUrl,
|
|
62
|
+
oauthStartTime: clientInfo.oauthStartTime,
|
|
63
|
+
lastError: clientInfo.lastError?.message,
|
|
64
|
+
lastConnected: clientInfo.lastConnected,
|
|
65
|
+
}));
|
|
66
|
+
const html = generateOAuthDashboard(services, req);
|
|
67
|
+
res.setHeader('Content-Type', 'text/html');
|
|
68
|
+
res.send(html);
|
|
83
69
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
res.
|
|
87
|
-
return;
|
|
70
|
+
catch (error) {
|
|
71
|
+
logger.error('Error serving OAuth dashboard:', error);
|
|
72
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
88
73
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Start OAuth authorization for a specific service
|
|
77
|
+
*/
|
|
78
|
+
const authorizeHandler = async (req, res) => {
|
|
79
|
+
try {
|
|
80
|
+
const { serverName } = req.params;
|
|
81
|
+
const serverManager = ServerManager.current;
|
|
82
|
+
const clientInfo = serverManager.getClient(serverName);
|
|
83
|
+
if (!clientInfo) {
|
|
84
|
+
res.status(404).json({ error: 'Service not found' });
|
|
97
85
|
return;
|
|
98
86
|
}
|
|
99
|
-
|
|
100
|
-
|
|
87
|
+
if (clientInfo.authorizationUrl) {
|
|
88
|
+
// Redirect to existing authorization URL
|
|
89
|
+
res.redirect(clientInfo.authorizationUrl);
|
|
101
90
|
return;
|
|
102
91
|
}
|
|
92
|
+
else {
|
|
93
|
+
// Generate new authorization URL by attempting connection
|
|
94
|
+
await initiateOAuth(serverName);
|
|
95
|
+
// Get updated client info
|
|
96
|
+
const updatedClients = serverManager.getClients();
|
|
97
|
+
const updatedClientInfo = updatedClients.get(serverName);
|
|
98
|
+
if (updatedClientInfo?.authorizationUrl) {
|
|
99
|
+
res.redirect(updatedClientInfo.authorizationUrl);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
res.status(500).json({ error: 'Failed to generate OAuth URL' });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
103
107
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
res.status(500).json({ error: 'Failed to start OAuth flow' });
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
router.get('/authorize/:serverName', authorizeHandler);
|
|
111
|
-
function hasFinishAuth(transport) {
|
|
112
|
-
return typeof transport === 'object' && transport !== null && typeof transport.finishAuth === 'function';
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Handle OAuth callback and trigger reconnection
|
|
116
|
-
*/
|
|
117
|
-
router.get('/callback/:serverName', async (req, res) => {
|
|
118
|
-
const { serverName } = req.params;
|
|
119
|
-
const { code, error } = req.query;
|
|
120
|
-
try {
|
|
121
|
-
if (error) {
|
|
122
|
-
logger.error(`OAuth error for ${serverName}:`, error);
|
|
123
|
-
return res.redirect(`/oauth?error=${encodeURIComponent(String(error))}`);
|
|
108
|
+
catch (error) {
|
|
109
|
+
logger.error(`Error starting OAuth for ${req.params.serverName}:`, error);
|
|
110
|
+
res.status(500).json({ error: 'Failed to start OAuth flow' });
|
|
124
111
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
112
|
+
};
|
|
113
|
+
router.get('/authorize/:serverName', authorizeHandler);
|
|
114
|
+
function hasFinishAuth(transport) {
|
|
115
|
+
return typeof transport === 'object' && transport !== null && typeof transport.finishAuth === 'function';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Handle OAuth callback and trigger reconnection
|
|
119
|
+
*/
|
|
120
|
+
router.get('/callback/:serverName', async (req, res) => {
|
|
121
|
+
const { serverName } = req.params;
|
|
122
|
+
const { code, error } = req.query;
|
|
123
|
+
try {
|
|
124
|
+
if (error) {
|
|
125
|
+
logger.error(`OAuth error for ${serverName}:`, error);
|
|
126
|
+
return res.redirect(`/oauth?error=${encodeURIComponent(String(error))}`);
|
|
127
|
+
}
|
|
128
|
+
if (!code) {
|
|
129
|
+
logger.error(`OAuth callback missing authorization code for ${serverName}`);
|
|
130
|
+
return res.redirect(`/oauth?error=missing_code`);
|
|
131
|
+
}
|
|
132
|
+
const serverManager = ServerManager.current;
|
|
133
|
+
const clientInfo = serverManager.getClient(serverName);
|
|
134
|
+
if (!clientInfo) {
|
|
135
|
+
logger.error(`Client ${serverName} not found in OAuth callback`);
|
|
136
|
+
return res.redirect(`/oauth?error=client_not_found`);
|
|
137
|
+
}
|
|
138
|
+
// Use type guard for transport with finishAuth
|
|
139
|
+
if (!hasFinishAuth(clientInfo.transport)) {
|
|
140
|
+
logger.error(`Transport for ${serverName} does not support finishAuth`);
|
|
141
|
+
return res.redirect(`/oauth?error=invalid_oauth_transport`);
|
|
142
|
+
}
|
|
143
|
+
// Complete the OAuth flow with the authorization code
|
|
144
|
+
await clientInfo.transport.finishAuth(String(code));
|
|
145
|
+
clientInfo.status = ClientStatus.Connected;
|
|
146
|
+
clientInfo.lastConnected = new Date();
|
|
147
|
+
clientInfo.lastError = undefined;
|
|
148
|
+
// Redirect back to dashboard with success
|
|
149
|
+
res.redirect('/oauth?success=1');
|
|
128
150
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
logger.error(`Client ${serverName} not found in OAuth callback`);
|
|
133
|
-
return res.redirect(`/oauth?error=client_not_found`);
|
|
151
|
+
catch (error) {
|
|
152
|
+
logger.error(`Error handling OAuth callback for ${serverName}:`, error);
|
|
153
|
+
res.redirect(`/oauth?error=callback_failed`);
|
|
134
154
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
});
|
|
156
|
+
/**
|
|
157
|
+
* Restart OAuth flow for a service
|
|
158
|
+
*/
|
|
159
|
+
const restartHandler = async (req, res) => {
|
|
160
|
+
const { serverName } = req.params;
|
|
161
|
+
try {
|
|
162
|
+
const serverManager = ServerManager.current;
|
|
163
|
+
const clientInfo = serverManager.getClient(serverName);
|
|
164
|
+
if (!clientInfo) {
|
|
165
|
+
res.status(404).json({ error: 'Service not found' });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Clear existing OAuth data and restart flow
|
|
169
|
+
await restartOAuthFlow(serverName);
|
|
170
|
+
res.json({ success: true, message: 'OAuth flow restarted' });
|
|
139
171
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
clientInfo.lastConnected = new Date();
|
|
144
|
-
clientInfo.lastError = undefined;
|
|
145
|
-
// Redirect back to dashboard with success
|
|
146
|
-
res.redirect('/oauth?success=1');
|
|
147
|
-
}
|
|
148
|
-
catch (error) {
|
|
149
|
-
logger.error(`Error handling OAuth callback for ${serverName}:`, error);
|
|
150
|
-
res.redirect(`/oauth?error=callback_failed`);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
/**
|
|
154
|
-
* Restart OAuth flow for a service
|
|
155
|
-
*/
|
|
156
|
-
const restartHandler = async (req, res) => {
|
|
157
|
-
const { serverName } = req.params;
|
|
158
|
-
try {
|
|
159
|
-
const serverManager = ServerManager.current;
|
|
160
|
-
const clientInfo = serverManager.getClient(serverName);
|
|
161
|
-
if (!clientInfo) {
|
|
162
|
-
res.status(404).json({ error: 'Service not found' });
|
|
163
|
-
return;
|
|
172
|
+
catch (error) {
|
|
173
|
+
logger.error(`Error restarting OAuth for ${serverName}:`, error);
|
|
174
|
+
res.status(500).json({ error: 'Failed to restart OAuth flow' });
|
|
164
175
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
};
|
|
177
|
+
router.post('/restart/:serverName', restartHandler);
|
|
178
|
+
/**
|
|
179
|
+
* Handle consent form submission for OAuth authorization
|
|
180
|
+
*/
|
|
181
|
+
const consentHandler = async (req, res) => {
|
|
182
|
+
try {
|
|
183
|
+
const { auth_request_id, action, scopes } = req.body;
|
|
184
|
+
// Validate required fields
|
|
185
|
+
if (!auth_request_id || !action) {
|
|
186
|
+
res.status(400).json({
|
|
187
|
+
error: 'invalid_request',
|
|
188
|
+
error_description: 'Missing required parameters',
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// Use the OAuth provider's storage service to ensure consistent storage directory
|
|
193
|
+
const authRequest = oauthProvider.oauthStorage.getAuthorizationRequest(auth_request_id);
|
|
194
|
+
if (!authRequest) {
|
|
195
|
+
res.status(400).json({
|
|
196
|
+
error: 'invalid_request',
|
|
197
|
+
error_description: 'Invalid or expired authorization request',
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Get client data using the OAuth provider's storage service
|
|
202
|
+
const clientKey = `${AUTH_CONFIG.CLIENT.PREFIXES.CLIENT}${authRequest.clientId}`;
|
|
203
|
+
const client = oauthProvider.oauthStorage.clientDataRepository.get(clientKey);
|
|
204
|
+
if (!client) {
|
|
205
|
+
res.status(400).json({
|
|
206
|
+
error: 'invalid_client',
|
|
207
|
+
error_description: 'Client not found',
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// Client validation passed - we have a valid client
|
|
212
|
+
if (action === 'deny') {
|
|
213
|
+
// Use the service layer for denial processing
|
|
214
|
+
const redirectUrl = await oauthProvider.oauthStorage.processConsentDenial(auth_request_id);
|
|
215
|
+
res.redirect(redirectUrl.toString());
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (action === 'approve') {
|
|
219
|
+
// User approved the authorization
|
|
220
|
+
const selectedScopes = Array.isArray(scopes) ? scopes : scopes ? [scopes] : [];
|
|
221
|
+
// Validate selected scopes
|
|
222
|
+
const validation = validateScopes(selectedScopes);
|
|
223
|
+
if (!validation.isValid) {
|
|
224
|
+
res.status(400).json({
|
|
225
|
+
error: 'invalid_scope',
|
|
226
|
+
error_description: `Invalid scopes: ${validation.errors.join(', ')}`,
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// Use the service layer for approval processing
|
|
231
|
+
const { redirectUrl } = await oauthProvider.oauthStorage.processConsentApproval(auth_request_id, validation.validScopes);
|
|
232
|
+
res.redirect(redirectUrl.toString());
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// Invalid action
|
|
183
236
|
res.status(400).json({
|
|
184
237
|
error: 'invalid_request',
|
|
185
|
-
error_description: '
|
|
238
|
+
error_description: 'Invalid action',
|
|
186
239
|
});
|
|
187
|
-
return;
|
|
188
240
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
error: 'invalid_client',
|
|
195
|
-
error_description: 'Client not found',
|
|
241
|
+
catch (error) {
|
|
242
|
+
logger.error('Error handling consent form:', error);
|
|
243
|
+
res.status(500).json({
|
|
244
|
+
error: 'server_error',
|
|
245
|
+
error_description: 'Internal server error',
|
|
196
246
|
});
|
|
197
|
-
return;
|
|
198
247
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
redirectUrl.searchParams.set('error', 'access_denied');
|
|
210
|
-
redirectUrl.searchParams.set('error_description', 'User denied the request');
|
|
211
|
-
if (state) {
|
|
212
|
-
redirectUrl.searchParams.set('state', state);
|
|
213
|
-
}
|
|
214
|
-
logger.info(`OAuth authorization denied by user for client ${client_id}`);
|
|
215
|
-
res.redirect(redirectUrl.toString());
|
|
216
|
-
return;
|
|
248
|
+
};
|
|
249
|
+
router.post('/consent', sensitiveOperationLimiter, consentHandler);
|
|
250
|
+
/**
|
|
251
|
+
* Initiate OAuth flow for a service
|
|
252
|
+
*/
|
|
253
|
+
async function initiateOAuth(serverName) {
|
|
254
|
+
const serverManager = ServerManager.current;
|
|
255
|
+
const clientInfo = serverManager.getClient(serverName);
|
|
256
|
+
if (!clientInfo) {
|
|
257
|
+
throw new Error(`Service ${serverName} not found`);
|
|
217
258
|
}
|
|
218
|
-
|
|
219
|
-
//
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
const validation = validateScopes(selectedScopes);
|
|
223
|
-
if (!validation.isValid) {
|
|
224
|
-
res.status(400).json({
|
|
225
|
-
error: 'invalid_scope',
|
|
226
|
-
error_description: `Invalid scopes: ${validation.errors.join(', ')}`,
|
|
227
|
-
});
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
// Reconstruct authorization params for the approve method
|
|
231
|
-
const authParams = {
|
|
232
|
-
scopes: selectedScopes,
|
|
233
|
-
redirectUri: redirect_uri,
|
|
234
|
-
codeChallenge: code_challenge,
|
|
235
|
-
state,
|
|
236
|
-
resource: resource ? new URL(resource) : undefined,
|
|
237
|
-
};
|
|
238
|
-
// Use the OAuth provider to approve the authorization
|
|
239
|
-
await oauthProvider.approveAuthorization(typedClient, authParams, validation.validScopes, res);
|
|
240
|
-
return;
|
|
259
|
+
try {
|
|
260
|
+
// Create new client and attempt connection to trigger OAuth
|
|
261
|
+
const newClient = await createClient();
|
|
262
|
+
await newClient.connect(clientInfo.transport);
|
|
241
263
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
router.post('/consent', sensitiveOperationLimiter, consentHandler);
|
|
257
|
-
/**
|
|
258
|
-
* Initiate OAuth flow for a service
|
|
259
|
-
*/
|
|
260
|
-
async function initiateOAuth(serverName) {
|
|
261
|
-
const serverManager = ServerManager.current;
|
|
262
|
-
const clientInfo = serverManager.getClient(serverName);
|
|
263
|
-
if (!clientInfo) {
|
|
264
|
-
throw new Error(`Service ${serverName} not found`);
|
|
265
|
-
}
|
|
266
|
-
try {
|
|
267
|
-
// Create new client and attempt connection to trigger OAuth
|
|
268
|
-
const newClient = await createClient();
|
|
269
|
-
await newClient.connect(clientInfo.transport);
|
|
270
|
-
}
|
|
271
|
-
catch (error) {
|
|
272
|
-
if (error instanceof OAuthRequiredError) {
|
|
273
|
-
// Update client info with OAuth status
|
|
274
|
-
clientInfo.status = ClientStatus.AwaitingOAuth;
|
|
275
|
-
clientInfo.oauthStartTime = new Date();
|
|
276
|
-
// Try to get authorization URL from OAuth provider
|
|
277
|
-
try {
|
|
278
|
-
const oauthProvider = clientInfo.transport.oauthProvider;
|
|
279
|
-
if (oauthProvider && typeof oauthProvider.getAuthorizationUrl === 'function') {
|
|
280
|
-
clientInfo.authorizationUrl = oauthProvider.getAuthorizationUrl();
|
|
264
|
+
catch (error) {
|
|
265
|
+
if (error instanceof OAuthRequiredError) {
|
|
266
|
+
// Update client info with OAuth status
|
|
267
|
+
clientInfo.status = ClientStatus.AwaitingOAuth;
|
|
268
|
+
clientInfo.oauthStartTime = new Date();
|
|
269
|
+
// Try to get authorization URL from OAuth provider
|
|
270
|
+
try {
|
|
271
|
+
const oauthProvider = clientInfo.transport.oauthProvider;
|
|
272
|
+
if (oauthProvider && typeof oauthProvider.getAuthorizationUrl === 'function') {
|
|
273
|
+
clientInfo.authorizationUrl = oauthProvider.getAuthorizationUrl();
|
|
274
|
+
}
|
|
281
275
|
}
|
|
276
|
+
catch (urlError) {
|
|
277
|
+
logger.warn(`Could not extract authorization URL for ${serverName}:`, urlError);
|
|
278
|
+
}
|
|
279
|
+
logger.info(`OAuth initiated for ${serverName}`);
|
|
282
280
|
}
|
|
283
|
-
|
|
284
|
-
|
|
281
|
+
else {
|
|
282
|
+
throw error; // Re-throw non-OAuth errors
|
|
285
283
|
}
|
|
286
|
-
logger.info(`OAuth initiated for ${serverName}`);
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
throw error; // Re-throw non-OAuth errors
|
|
290
284
|
}
|
|
291
285
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Restart OAuth flow for a service
|
|
288
|
+
*/
|
|
289
|
+
async function restartOAuthFlow(serverName) {
|
|
290
|
+
const serverManager = ServerManager.current;
|
|
291
|
+
const clientInfo = serverManager.getClient(serverName);
|
|
292
|
+
if (!clientInfo) {
|
|
293
|
+
throw new Error(`Service ${serverName} not found`);
|
|
294
|
+
}
|
|
295
|
+
// Clear OAuth state
|
|
296
|
+
clientInfo.authorizationUrl = undefined;
|
|
297
|
+
clientInfo.oauthStartTime = undefined;
|
|
298
|
+
clientInfo.status = ClientStatus.Disconnected;
|
|
299
|
+
// Initiate new OAuth flow
|
|
300
|
+
await initiateOAuth(serverName);
|
|
301
301
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
function generateOAuthDashboard(services, req) {
|
|
313
|
-
const servicesHtml = services
|
|
314
|
-
.map((service) => {
|
|
315
|
-
const statusIcon = getStatusIcon(service.status);
|
|
316
|
-
const statusText = getStatusText(service.status);
|
|
317
|
-
const actionButton = getActionButton(service);
|
|
318
|
-
return `
|
|
302
|
+
/**
|
|
303
|
+
* Generate OAuth dashboard HTML
|
|
304
|
+
*/
|
|
305
|
+
function generateOAuthDashboard(services, req) {
|
|
306
|
+
const servicesHtml = services
|
|
307
|
+
.map((service) => {
|
|
308
|
+
const statusIcon = getStatusIcon(service.status);
|
|
309
|
+
const statusText = getStatusText(service.status);
|
|
310
|
+
const actionButton = getActionButton(service);
|
|
311
|
+
return `
|
|
319
312
|
<tr>
|
|
320
313
|
<td>${sanitizeServerNameForContext(service.name, 'html')}</td>
|
|
321
314
|
<td>${statusIcon} ${statusText}</td>
|
|
@@ -324,9 +317,9 @@ function generateOAuthDashboard(services, req) {
|
|
|
324
317
|
<td>${actionButton}</td>
|
|
325
318
|
</tr>
|
|
326
319
|
`;
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
320
|
+
})
|
|
321
|
+
.join('');
|
|
322
|
+
return `
|
|
330
323
|
<!DOCTYPE html>
|
|
331
324
|
<html lang="en">
|
|
332
325
|
<head>
|
|
@@ -412,63 +405,66 @@ function generateOAuthDashboard(services, req) {
|
|
|
412
405
|
</body>
|
|
413
406
|
</html>
|
|
414
407
|
`;
|
|
415
|
-
}
|
|
416
|
-
function getStatusIcon(status) {
|
|
417
|
-
switch (status) {
|
|
418
|
-
case ClientStatus.Connected:
|
|
419
|
-
return '✅';
|
|
420
|
-
case ClientStatus.AwaitingOAuth:
|
|
421
|
-
return '⏳';
|
|
422
|
-
case ClientStatus.Error:
|
|
423
|
-
return '❌';
|
|
424
|
-
case ClientStatus.Disconnected:
|
|
425
|
-
return '🔌';
|
|
426
|
-
default:
|
|
427
|
-
return '❓';
|
|
428
408
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
409
|
+
function getStatusIcon(status) {
|
|
410
|
+
switch (status) {
|
|
411
|
+
case ClientStatus.Connected:
|
|
412
|
+
return '✅';
|
|
413
|
+
case ClientStatus.AwaitingOAuth:
|
|
414
|
+
return '⏳';
|
|
415
|
+
case ClientStatus.Error:
|
|
416
|
+
return '❌';
|
|
417
|
+
case ClientStatus.Disconnected:
|
|
418
|
+
return '🔌';
|
|
419
|
+
default:
|
|
420
|
+
return '❓';
|
|
421
|
+
}
|
|
442
422
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
return '<span class="status-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
case ClientStatus.Error:
|
|
457
|
-
case ClientStatus.Disconnected:
|
|
458
|
-
return `<button onclick="restartOAuth('${sanitizeServerNameForContext(service.name, 'html')}')" class="btn btn-primary">🔄 Restart OAuth</button>`;
|
|
459
|
-
default:
|
|
460
|
-
return `<button onclick="restartOAuth('${sanitizeServerNameForContext(service.name, 'html')}')" class="btn btn-primary">🔄 Start OAuth</button>`;
|
|
423
|
+
function getStatusText(status) {
|
|
424
|
+
switch (status) {
|
|
425
|
+
case ClientStatus.Connected:
|
|
426
|
+
return '<span class="status-connected">Connected</span>';
|
|
427
|
+
case ClientStatus.AwaitingOAuth:
|
|
428
|
+
return '<span class="status-awaiting">Awaiting OAuth</span>';
|
|
429
|
+
case ClientStatus.Error:
|
|
430
|
+
return '<span class="status-error">Error</span>';
|
|
431
|
+
case ClientStatus.Disconnected:
|
|
432
|
+
return '<span class="status-disconnected">Disconnected</span>';
|
|
433
|
+
default:
|
|
434
|
+
return '<span class="status-disconnected">Unknown</span>';
|
|
435
|
+
}
|
|
461
436
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
437
|
+
function getActionButton(service) {
|
|
438
|
+
switch (service.status) {
|
|
439
|
+
case ClientStatus.Connected:
|
|
440
|
+
// Check if server requires OAuth based on runtime behavior
|
|
441
|
+
if (requiresOAuth(service)) {
|
|
442
|
+
return '<span class="status-connected">✓ Authorized</span>';
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
return '<span class="status-connected">✓ Connected</span>';
|
|
446
|
+
}
|
|
447
|
+
case ClientStatus.AwaitingOAuth:
|
|
448
|
+
return `<a href="/oauth/authorize/${sanitizeUrlParam(service.name)}" class="btn btn-warning">🔐 Authorize</a>`;
|
|
449
|
+
case ClientStatus.Error:
|
|
450
|
+
case ClientStatus.Disconnected:
|
|
451
|
+
return `<button onclick="restartOAuth('${sanitizeServerNameForContext(service.name, 'html')}')" class="btn btn-primary">🔄 Restart OAuth</button>`;
|
|
452
|
+
default:
|
|
453
|
+
return `<button onclick="restartOAuth('${sanitizeServerNameForContext(service.name, 'html')}')" class="btn btn-primary">🔄 Start OAuth</button>`;
|
|
454
|
+
}
|
|
466
455
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
456
|
+
function getAlertHtml(req) {
|
|
457
|
+
if (req.query.success) {
|
|
458
|
+
return '<div class="alert alert-success">✅ OAuth authorization completed successfully!</div>';
|
|
459
|
+
}
|
|
460
|
+
if (req.query.error) {
|
|
461
|
+
const error = req.query.error;
|
|
462
|
+
return `<div class="alert alert-error">❌ OAuth error: ${sanitizeErrorMessage(String(error))}</div>`;
|
|
463
|
+
}
|
|
464
|
+
return '';
|
|
470
465
|
}
|
|
471
|
-
return
|
|
466
|
+
return router;
|
|
472
467
|
}
|
|
473
|
-
|
|
468
|
+
// Export the factory function as default
|
|
469
|
+
export default createOAuthRoutes;
|
|
474
470
|
//# sourceMappingURL=oauthRoutes.js.map
|