@perfai/mcp 1.0.24
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 +21 -0
- package/README.md +352 -0
- package/dist/auth/authManager.d.ts +83 -0
- package/dist/auth/authManager.d.ts.map +1 -0
- package/dist/auth/authManager.js +555 -0
- package/dist/auth/sessionCache.d.ts +5 -0
- package/dist/auth/sessionCache.d.ts.map +1 -0
- package/dist/auth/sessionCache.js +29 -0
- package/dist/auth/sessionStorage.d.ts +53 -0
- package/dist/auth/sessionStorage.d.ts.map +1 -0
- package/dist/auth/sessionStorage.js +234 -0
- package/dist/auth/types.d.ts +28 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +1 -0
- package/dist/config.d.ts +65 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +74 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +144 -0
- package/dist/setup-config.d.ts +4 -0
- package/dist/setup-config.d.ts.map +1 -0
- package/dist/setup-config.js +69 -0
- package/dist/tools/index.d.ts +361 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +275 -0
- package/dist/tools/protected/aiFixDesignIssue.d.ts +17 -0
- package/dist/tools/protected/aiFixDesignIssue.d.ts.map +1 -0
- package/dist/tools/protected/aiFixDesignIssue.js +205 -0
- package/dist/tools/protected/aiFixQualityIssue.d.ts +17 -0
- package/dist/tools/protected/aiFixQualityIssue.d.ts.map +1 -0
- package/dist/tools/protected/aiFixQualityIssue.js +188 -0
- package/dist/tools/protected/aiFixSecurityIssue.d.ts +17 -0
- package/dist/tools/protected/aiFixSecurityIssue.d.ts.map +1 -0
- package/dist/tools/protected/aiFixSecurityIssue.js +205 -0
- package/dist/tools/protected/checkDesignFixes.d.ts +17 -0
- package/dist/tools/protected/checkDesignFixes.d.ts.map +1 -0
- package/dist/tools/protected/checkDesignFixes.js +199 -0
- package/dist/tools/protected/checkQualityFixes.d.ts +17 -0
- package/dist/tools/protected/checkQualityFixes.d.ts.map +1 -0
- package/dist/tools/protected/checkQualityFixes.js +199 -0
- package/dist/tools/protected/checkSecurityFixes.d.ts +17 -0
- package/dist/tools/protected/checkSecurityFixes.d.ts.map +1 -0
- package/dist/tools/protected/checkSecurityFixes.js +177 -0
- package/dist/tools/protected/listApis.d.ts +28 -0
- package/dist/tools/protected/listApis.d.ts.map +1 -0
- package/dist/tools/protected/listApis.js +102 -0
- package/dist/tools/protected/logout.d.ts +11 -0
- package/dist/tools/protected/logout.d.ts.map +1 -0
- package/dist/tools/protected/logout.js +22 -0
- package/dist/tools/protected/manageOrganizations.d.ts +26 -0
- package/dist/tools/protected/manageOrganizations.d.ts.map +1 -0
- package/dist/tools/protected/manageOrganizations.js +147 -0
- package/dist/tools/protected/runDesignTest.d.ts +21 -0
- package/dist/tools/protected/runDesignTest.d.ts.map +1 -0
- package/dist/tools/protected/runDesignTest.js +132 -0
- package/dist/tools/protected/runQualityTest.d.ts +21 -0
- package/dist/tools/protected/runQualityTest.d.ts.map +1 -0
- package/dist/tools/protected/runQualityTest.js +150 -0
- package/dist/tools/protected/runSecurityTest.d.ts +21 -0
- package/dist/tools/protected/runSecurityTest.d.ts.map +1 -0
- package/dist/tools/protected/runSecurityTest.js +107 -0
- package/dist/tools/protected/selectApi.d.ts +24 -0
- package/dist/tools/protected/selectApi.d.ts.map +1 -0
- package/dist/tools/protected/selectApi.js +172 -0
- package/dist/tools/protected/setup.d.ts +11 -0
- package/dist/tools/protected/setup.d.ts.map +1 -0
- package/dist/tools/protected/setup.js +151 -0
- package/dist/tools/protected/showDesignIssues.d.ts +38 -0
- package/dist/tools/protected/showDesignIssues.d.ts.map +1 -0
- package/dist/tools/protected/showDesignIssues.js +201 -0
- package/dist/tools/protected/showFixedIssues.d.ts +11 -0
- package/dist/tools/protected/showFixedIssues.d.ts.map +1 -0
- package/dist/tools/protected/showFixedIssues.js +36 -0
- package/dist/tools/protected/showQualityIssues.d.ts +33 -0
- package/dist/tools/protected/showQualityIssues.d.ts.map +1 -0
- package/dist/tools/protected/showQualityIssues.js +225 -0
- package/dist/tools/protected/showSecurityIssues.d.ts +47 -0
- package/dist/tools/protected/showSecurityIssues.d.ts.map +1 -0
- package/dist/tools/protected/showSecurityIssues.js +212 -0
- package/dist/tools/protected/summarizeIssues.d.ts +11 -0
- package/dist/tools/protected/summarizeIssues.d.ts.map +1 -0
- package/dist/tools/protected/summarizeIssues.js +161 -0
- package/dist/tools/protected/userInfo.d.ts +11 -0
- package/dist/tools/protected/userInfo.d.ts.map +1 -0
- package/dist/tools/protected/userInfo.js +21 -0
- package/dist/tools/protected/visionAiAppLearning.d.ts +37 -0
- package/dist/tools/protected/visionAiAppLearning.d.ts.map +1 -0
- package/dist/tools/protected/visionAiAppLearning.js +122 -0
- package/dist/tools/public/authStatus.d.ts +11 -0
- package/dist/tools/public/authStatus.d.ts.map +1 -0
- package/dist/tools/public/authStatus.js +78 -0
- package/dist/tools/public/login.d.ts +12 -0
- package/dist/tools/public/login.d.ts.map +1 -0
- package/dist/tools/public/login.js +230 -0
- package/dist/types/api.d.ts +12 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +1 -0
- package/dist/utils/dockerRunner.d.ts +44 -0
- package/dist/utils/dockerRunner.d.ts.map +1 -0
- package/dist/utils/dockerRunner.js +300 -0
- package/dist/utils/formatters.d.ts +14 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/formatters.js +510 -0
- package/dist/utils/promptBuilder.d.ts +4 -0
- package/dist/utils/promptBuilder.d.ts.map +1 -0
- package/dist/utils/promptBuilder.js +132 -0
- package/package.json +67 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import open from "open";
|
|
3
|
+
import { createHash, randomBytes } from "crypto";
|
|
4
|
+
import * as http from "http";
|
|
5
|
+
import { URL } from "url";
|
|
6
|
+
import { AUTH0_DOMAIN, CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, API_ENDPOINTS } from '../config.js';
|
|
7
|
+
import { SessionStorage } from './sessionStorage.js';
|
|
8
|
+
export class AuthenticationManager {
|
|
9
|
+
session = null;
|
|
10
|
+
codeVerifier = "";
|
|
11
|
+
authServer = null;
|
|
12
|
+
sessionStorage;
|
|
13
|
+
isInitialized = false;
|
|
14
|
+
initializationPromise;
|
|
15
|
+
constructor() {
|
|
16
|
+
this.sessionStorage = new SessionStorage();
|
|
17
|
+
this.initializationPromise = this.initializeSession();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Wait for session initialization to complete
|
|
21
|
+
*/
|
|
22
|
+
async waitForInitialization() {
|
|
23
|
+
if (!this.isInitialized) {
|
|
24
|
+
await this.initializationPromise;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Initialize session from persistent storage
|
|
29
|
+
*/
|
|
30
|
+
async initializeSession() {
|
|
31
|
+
if (this.isInitialized)
|
|
32
|
+
return;
|
|
33
|
+
try {
|
|
34
|
+
console.error('🔍 Checking for saved session...');
|
|
35
|
+
const savedSession = await this.sessionStorage.loadSession();
|
|
36
|
+
if (savedSession) {
|
|
37
|
+
this.session = savedSession;
|
|
38
|
+
const userEmail = savedSession.userInfo?.email || 'Unknown';
|
|
39
|
+
const timeLeft = Math.round((savedSession.expiresAt - Date.now()) / (1000 * 60 * 60));
|
|
40
|
+
console.error(`✅ Session restored for ${userEmail}, expires in ${timeLeft} hours`);
|
|
41
|
+
// Auto-save session updates
|
|
42
|
+
this.scheduleSessionSave();
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.error('📝 No valid saved session found');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('⚠️ Failed to initialize session:', error);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
this.isInitialized = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Schedule automatic session saving when session data changes
|
|
57
|
+
*/
|
|
58
|
+
scheduleSessionSave() {
|
|
59
|
+
if (this.session) {
|
|
60
|
+
// Debounced save to avoid excessive I/O
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
if (this.session) {
|
|
63
|
+
this.sessionStorage.saveSession(this.session);
|
|
64
|
+
}
|
|
65
|
+
}, 1000);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async isAuthenticated() {
|
|
69
|
+
// Ensure session is initialized
|
|
70
|
+
if (!this.isInitialized) {
|
|
71
|
+
await this.initializationPromise;
|
|
72
|
+
}
|
|
73
|
+
if (!this.session)
|
|
74
|
+
return false;
|
|
75
|
+
const isValid = Date.now() < this.session.expiresAt;
|
|
76
|
+
// If session is expired, clear it
|
|
77
|
+
if (!isValid && this.session) {
|
|
78
|
+
console.error('⏰ Session expired, clearing storage');
|
|
79
|
+
this.clearSession();
|
|
80
|
+
}
|
|
81
|
+
return isValid;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Synchronous version for backward compatibility
|
|
85
|
+
* Note: This will return false if initialization is still in progress
|
|
86
|
+
*/
|
|
87
|
+
isAuthenticatedSync() {
|
|
88
|
+
if (!this.isInitialized || !this.session)
|
|
89
|
+
return false;
|
|
90
|
+
const isValid = Date.now() < this.session.expiresAt;
|
|
91
|
+
// If session is expired, clear it
|
|
92
|
+
if (!isValid && this.session) {
|
|
93
|
+
console.error('⏰ Session expired, clearing storage');
|
|
94
|
+
this.clearSession();
|
|
95
|
+
}
|
|
96
|
+
return isValid;
|
|
97
|
+
}
|
|
98
|
+
getUserInfo() {
|
|
99
|
+
return this.session?.userInfo || null;
|
|
100
|
+
}
|
|
101
|
+
getAccessToken() {
|
|
102
|
+
return this.session?.accessToken || null;
|
|
103
|
+
}
|
|
104
|
+
getTokenType() {
|
|
105
|
+
return this.session?.tokenType || 'Bearer';
|
|
106
|
+
}
|
|
107
|
+
getSelectedOrgId() {
|
|
108
|
+
return this.session?.selectedOrgId || null;
|
|
109
|
+
}
|
|
110
|
+
getOrganizations() {
|
|
111
|
+
return this.session?.organizations || [];
|
|
112
|
+
}
|
|
113
|
+
setSelectedOrgId(orgId) {
|
|
114
|
+
if (this.session) {
|
|
115
|
+
this.session.selectedOrgId = orgId;
|
|
116
|
+
this.scheduleSessionSave();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// APP management methods
|
|
120
|
+
getSelectedApiId() {
|
|
121
|
+
return this.session?.selectedApiId || null;
|
|
122
|
+
}
|
|
123
|
+
getSelectedApiData() {
|
|
124
|
+
return this.session?.selectedApiData || null;
|
|
125
|
+
}
|
|
126
|
+
getSelectedApiIdentifier() {
|
|
127
|
+
return this.session?.selectedApiIdentifier || null;
|
|
128
|
+
}
|
|
129
|
+
setSelectedApi(apiId, apiData, apiIdentifier) {
|
|
130
|
+
if (this.session) {
|
|
131
|
+
this.session.selectedApiId = apiId;
|
|
132
|
+
this.session.selectedApiData = apiData;
|
|
133
|
+
this.session.selectedApiIdentifier = apiIdentifier;
|
|
134
|
+
this.scheduleSessionSave();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Security app ID management methods
|
|
138
|
+
getSelectedSecurityAppId() {
|
|
139
|
+
return this.session?.selectedSecurityAppId || null;
|
|
140
|
+
}
|
|
141
|
+
setSelectedSecurityAppId(securityAppId) {
|
|
142
|
+
if (this.session) {
|
|
143
|
+
this.session.selectedSecurityAppId = securityAppId;
|
|
144
|
+
this.scheduleSessionSave();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Design app ID management methods
|
|
148
|
+
getSelectedDesignAppId() {
|
|
149
|
+
return this.session?.selectedDesignAppId || null;
|
|
150
|
+
}
|
|
151
|
+
setSelectedDesignAppId(designAppId) {
|
|
152
|
+
if (this.session) {
|
|
153
|
+
this.session.selectedDesignAppId = designAppId;
|
|
154
|
+
this.scheduleSessionSave();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Quality app ID management methods
|
|
158
|
+
getSelectedQualityAppId() {
|
|
159
|
+
return this.session?.selectedQualityAppId || null;
|
|
160
|
+
}
|
|
161
|
+
setSelectedQualityAppId(qualityAppId) {
|
|
162
|
+
if (this.session) {
|
|
163
|
+
this.session.selectedQualityAppId = qualityAppId;
|
|
164
|
+
this.scheduleSessionSave();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// App sequence mapping methods
|
|
168
|
+
setAppSequenceMap(sequenceMap) {
|
|
169
|
+
if (this.session) {
|
|
170
|
+
this.session.appSequenceMap = sequenceMap;
|
|
171
|
+
this.scheduleSessionSave();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
getAppSequenceMap() {
|
|
175
|
+
return this.session?.appSequenceMap || null;
|
|
176
|
+
}
|
|
177
|
+
getAppIdBySequence(sequenceNumber) {
|
|
178
|
+
const sequenceMap = this.getAppSequenceMap();
|
|
179
|
+
return sequenceMap?.get(sequenceNumber) || null;
|
|
180
|
+
}
|
|
181
|
+
clearAppSequenceMap() {
|
|
182
|
+
if (this.session) {
|
|
183
|
+
this.session.appSequenceMap = undefined;
|
|
184
|
+
this.scheduleSessionSave();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Fixed issues management methods
|
|
188
|
+
getFixedIssues() {
|
|
189
|
+
return this.session?.fixedIssues || [];
|
|
190
|
+
}
|
|
191
|
+
markIssueAsFixed(issueId, issue, prompt) {
|
|
192
|
+
if (!this.session) {
|
|
193
|
+
throw new Error('No active session');
|
|
194
|
+
}
|
|
195
|
+
const fixedIssue = {
|
|
196
|
+
issueId,
|
|
197
|
+
issue: {
|
|
198
|
+
...issue,
|
|
199
|
+
isAIFixed: true,
|
|
200
|
+
aiFixedAt: new Date(),
|
|
201
|
+
aiFixPrompt: prompt,
|
|
202
|
+
},
|
|
203
|
+
fixedAt: new Date(),
|
|
204
|
+
prompt,
|
|
205
|
+
status: "ai-fixed",
|
|
206
|
+
};
|
|
207
|
+
// Initialize fixedIssues array if it doesn't exist
|
|
208
|
+
if (!this.session.fixedIssues) {
|
|
209
|
+
this.session.fixedIssues = [];
|
|
210
|
+
}
|
|
211
|
+
// Add or update fixed issue
|
|
212
|
+
const existingIndex = this.session.fixedIssues.findIndex((fi) => fi.issueId === issueId);
|
|
213
|
+
if (existingIndex !== -1) {
|
|
214
|
+
this.session.fixedIssues[existingIndex] = fixedIssue;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
this.session.fixedIssues.push(fixedIssue);
|
|
218
|
+
}
|
|
219
|
+
this.scheduleSessionSave();
|
|
220
|
+
return fixedIssue;
|
|
221
|
+
}
|
|
222
|
+
isIssueFixed(issueId) {
|
|
223
|
+
return this.session?.fixedIssues?.some(fi => fi.issueId === issueId) || false;
|
|
224
|
+
}
|
|
225
|
+
clearFixedIssues() {
|
|
226
|
+
if (this.session) {
|
|
227
|
+
this.session.fixedIssues = [];
|
|
228
|
+
this.scheduleSessionSave();
|
|
229
|
+
console.error('🗑️ Fixed issues cleared from session');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Design fixed issues management methods
|
|
233
|
+
getFixedDesignIssues() {
|
|
234
|
+
return this.session?.fixedDesignIssues || [];
|
|
235
|
+
}
|
|
236
|
+
markDesignIssueAsFixed(issueId, issue, prompt) {
|
|
237
|
+
if (!this.session) {
|
|
238
|
+
throw new Error('No active session');
|
|
239
|
+
}
|
|
240
|
+
const fixedIssue = {
|
|
241
|
+
issueId,
|
|
242
|
+
issue: {
|
|
243
|
+
...issue,
|
|
244
|
+
isAIFixed: true,
|
|
245
|
+
aiFixedAt: new Date(),
|
|
246
|
+
aiFixPrompt: prompt
|
|
247
|
+
},
|
|
248
|
+
fixedAt: new Date(),
|
|
249
|
+
prompt,
|
|
250
|
+
status: 'ai-fixed'
|
|
251
|
+
};
|
|
252
|
+
// Initialize fixedDesignIssues array if it doesn't exist
|
|
253
|
+
if (!this.session.fixedDesignIssues) {
|
|
254
|
+
this.session.fixedDesignIssues = [];
|
|
255
|
+
}
|
|
256
|
+
// Check if issue is already fixed and update or add
|
|
257
|
+
const existingIndex = this.session.fixedDesignIssues.findIndex(fi => fi.issueId === issueId);
|
|
258
|
+
if (existingIndex !== -1) {
|
|
259
|
+
this.session.fixedDesignIssues[existingIndex] = fixedIssue;
|
|
260
|
+
console.error(`🔄 Updated existing design fix for issue: ${issueId}`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
this.session.fixedDesignIssues.push(fixedIssue);
|
|
264
|
+
console.error(`✅ Design issue marked as fixed: ${issueId}`);
|
|
265
|
+
}
|
|
266
|
+
this.scheduleSessionSave();
|
|
267
|
+
return fixedIssue;
|
|
268
|
+
}
|
|
269
|
+
isDesignIssueFixed(issueId) {
|
|
270
|
+
return this.session?.fixedDesignIssues?.some(fi => fi.issueId === issueId) || false;
|
|
271
|
+
}
|
|
272
|
+
clearFixedDesignIssues() {
|
|
273
|
+
if (this.session) {
|
|
274
|
+
this.session.fixedDesignIssues = [];
|
|
275
|
+
this.scheduleSessionSave();
|
|
276
|
+
console.error('🗑️ Fixed design issues cleared from session');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Quality fixed issues management methods
|
|
280
|
+
getFixedQualityIssues() {
|
|
281
|
+
return this.session?.fixedQualityIssues || [];
|
|
282
|
+
}
|
|
283
|
+
addFixedQualityIssue(fixedIssue) {
|
|
284
|
+
if (!this.session) {
|
|
285
|
+
throw new Error('No active session');
|
|
286
|
+
}
|
|
287
|
+
// Initialize fixedQualityIssues array if it doesn't exist
|
|
288
|
+
if (!this.session.fixedQualityIssues) {
|
|
289
|
+
this.session.fixedQualityIssues = [];
|
|
290
|
+
}
|
|
291
|
+
// Add or update fixed issue
|
|
292
|
+
const existingIndex = this.session.fixedQualityIssues.findIndex(fi => fi.issueId === fixedIssue.issueId);
|
|
293
|
+
if (existingIndex !== -1) {
|
|
294
|
+
this.session.fixedQualityIssues[existingIndex] = fixedIssue;
|
|
295
|
+
console.error(`🔄 Updated existing quality fix for issue: ${fixedIssue.issueId}`);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
this.session.fixedQualityIssues.push(fixedIssue);
|
|
299
|
+
console.error(`✅ Quality issue marked as fixed: ${fixedIssue.issueId}`);
|
|
300
|
+
}
|
|
301
|
+
this.scheduleSessionSave();
|
|
302
|
+
}
|
|
303
|
+
markQualityIssueAsFixed(issueId, issue, prompt) {
|
|
304
|
+
if (!this.session) {
|
|
305
|
+
throw new Error('No active session');
|
|
306
|
+
}
|
|
307
|
+
const fixedIssue = {
|
|
308
|
+
issueId,
|
|
309
|
+
issue: {
|
|
310
|
+
...issue,
|
|
311
|
+
isAIFixed: true,
|
|
312
|
+
aiFixedAt: new Date(),
|
|
313
|
+
aiFixPrompt: prompt,
|
|
314
|
+
},
|
|
315
|
+
fixedAt: new Date(),
|
|
316
|
+
prompt,
|
|
317
|
+
status: "ai-fixed",
|
|
318
|
+
};
|
|
319
|
+
this.addFixedQualityIssue(fixedIssue);
|
|
320
|
+
return fixedIssue;
|
|
321
|
+
}
|
|
322
|
+
isQualityIssueFixed(issueId) {
|
|
323
|
+
return this.session?.fixedQualityIssues?.some(fi => fi.issueId === issueId) || false;
|
|
324
|
+
}
|
|
325
|
+
clearFixedQualityIssues() {
|
|
326
|
+
if (this.session) {
|
|
327
|
+
this.session.fixedQualityIssues = [];
|
|
328
|
+
this.scheduleSessionSave();
|
|
329
|
+
console.error('🗑️ Fixed quality issues cleared from session');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
clearSession() {
|
|
333
|
+
this.session = null;
|
|
334
|
+
this.sessionStorage.clearSession();
|
|
335
|
+
console.error('🗑️ Session cleared from memory and storage');
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Get session storage location for debugging
|
|
339
|
+
*/
|
|
340
|
+
getStorageLocation() {
|
|
341
|
+
return this.sessionStorage.getStorageLocation();
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Check if there's a valid session in storage without loading it
|
|
345
|
+
*/
|
|
346
|
+
hasValidStoredSession() {
|
|
347
|
+
return this.sessionStorage.hasValidSession();
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Save current session to persistent storage
|
|
351
|
+
*/
|
|
352
|
+
async saveCurrentSession() {
|
|
353
|
+
if (this.session) {
|
|
354
|
+
await this.sessionStorage.saveSession(this.session);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
throw new Error('No active session to save');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
generateCodeChallenge() {
|
|
361
|
+
this.codeVerifier = randomBytes(32).toString('base64url');
|
|
362
|
+
const codeChallenge = createHash('sha256')
|
|
363
|
+
.update(this.codeVerifier)
|
|
364
|
+
.digest('base64url');
|
|
365
|
+
return { codeVerifier: this.codeVerifier, codeChallenge };
|
|
366
|
+
}
|
|
367
|
+
buildAuthUrl() {
|
|
368
|
+
const { codeChallenge } = this.generateCodeChallenge();
|
|
369
|
+
const params = new URLSearchParams({
|
|
370
|
+
response_type: "code",
|
|
371
|
+
client_id: CLIENT_ID,
|
|
372
|
+
redirect_uri: REDIRECT_URI,
|
|
373
|
+
scope: "openid profile email offline_access",
|
|
374
|
+
//code_challenge: codeChallenge,
|
|
375
|
+
//code_challenge_method: "S256",
|
|
376
|
+
});
|
|
377
|
+
return `https://${AUTH0_DOMAIN}/authorize?${params.toString()}`;
|
|
378
|
+
}
|
|
379
|
+
async manualCodeFlow() {
|
|
380
|
+
const authUrl = this.buildAuthUrl();
|
|
381
|
+
// Open browser for user to authenticate
|
|
382
|
+
await open(authUrl);
|
|
383
|
+
return authUrl;
|
|
384
|
+
}
|
|
385
|
+
async startAuthServer() {
|
|
386
|
+
return new Promise((resolve, reject) => {
|
|
387
|
+
this.authServer = http.createServer(async (req, res) => {
|
|
388
|
+
if (req.url?.startsWith('/') || req.url?.startsWith('/auth-callback')) {
|
|
389
|
+
try {
|
|
390
|
+
const url = new URL(req.url, `http://localhost:5173`);
|
|
391
|
+
const code = url.searchParams.get('code');
|
|
392
|
+
const error = url.searchParams.get('error');
|
|
393
|
+
if (error) {
|
|
394
|
+
throw new Error(`Auth error: ${error}`);
|
|
395
|
+
}
|
|
396
|
+
if (!code) {
|
|
397
|
+
throw new Error('No authorization code received');
|
|
398
|
+
}
|
|
399
|
+
// Exchange code for tokens
|
|
400
|
+
await this.exchangeCodeForTokens(code);
|
|
401
|
+
// Send success response
|
|
402
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
403
|
+
res.end(`
|
|
404
|
+
<html>
|
|
405
|
+
<body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
|
|
406
|
+
<h1 style="color: #28a745;">✅ Authentication Successful!</h1>
|
|
407
|
+
<p>You have successfully logged in to PerfAI MCP Server.</p>
|
|
408
|
+
<p>You can now close this window and return to your MCP client.</p>
|
|
409
|
+
<script>setTimeout(() => window.close(), 3000);</script>
|
|
410
|
+
</body>
|
|
411
|
+
</html>
|
|
412
|
+
`);
|
|
413
|
+
// Close server after successful auth
|
|
414
|
+
setTimeout(() => {
|
|
415
|
+
this.authServer?.close();
|
|
416
|
+
this.authServer = null;
|
|
417
|
+
}, 1000);
|
|
418
|
+
resolve('Authentication successful');
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
422
|
+
res.end(`
|
|
423
|
+
<html>
|
|
424
|
+
<body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
|
|
425
|
+
<h1 style="color: #dc3545;">❌ Authentication Failed</h1>
|
|
426
|
+
<p>Error: ${error instanceof Error ? error.message : 'Unknown error'}</p>
|
|
427
|
+
<p>Please try again.</p>
|
|
428
|
+
</body>
|
|
429
|
+
</html>
|
|
430
|
+
`);
|
|
431
|
+
reject(error);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
res.writeHead(404);
|
|
436
|
+
res.end('Not found');
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
// Try to bind to port 5173 first (matching Auth0 config), fallback to 5174
|
|
440
|
+
const tryPort = (port) => {
|
|
441
|
+
this.authServer.listen(port, () => {
|
|
442
|
+
console.error(`🚀 Auth server listening on port ${port} for OAuth callback`);
|
|
443
|
+
if (port === 5173) {
|
|
444
|
+
console.error('✅ Using port 5173 - matches your Auth0 configuration perfectly!');
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
console.error('⚠️ Using port 5174 - Auth0 should redirect to localhost:5173/auth-callback');
|
|
448
|
+
console.error(' (The server will handle the callback regardless of port)');
|
|
449
|
+
}
|
|
450
|
+
resolve('Authentication server started');
|
|
451
|
+
});
|
|
452
|
+
this.authServer.on('error', (error) => {
|
|
453
|
+
if (error.code === 'EADDRINUSE' && port === 5173) {
|
|
454
|
+
console.error('⚠️ Port 5173 is busy (MCP Inspector running), trying port 5174...');
|
|
455
|
+
// Clear the error handler before trying the next port
|
|
456
|
+
this.authServer.removeAllListeners('error');
|
|
457
|
+
tryPort(5174);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
reject(error);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
};
|
|
464
|
+
tryPort(5173);
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
async exchangeCodeForTokens(code) {
|
|
468
|
+
console.error('🔄 Starting token exchange process...');
|
|
469
|
+
const tokenData = {
|
|
470
|
+
grant_type: 'authorization_code',
|
|
471
|
+
client_id: CLIENT_ID,
|
|
472
|
+
client_secret: CLIENT_SECRET,
|
|
473
|
+
code: code,
|
|
474
|
+
redirect_uri: REDIRECT_URI,
|
|
475
|
+
};
|
|
476
|
+
try {
|
|
477
|
+
console.error('📡 Sending token request to Auth0...');
|
|
478
|
+
const response = await axios.post(API_ENDPOINTS.TOKEN, tokenData, {
|
|
479
|
+
headers: { 'Content-Type': 'application/json' }
|
|
480
|
+
});
|
|
481
|
+
console.error('✅ Token response received');
|
|
482
|
+
const { access_token, id_token, expires_in, token_type } = response.data;
|
|
483
|
+
console.error('🎫 Tokens extracted - access_token:', !!access_token, 'id_token:', !!id_token, 'expires_in:', expires_in, 'token_type:', token_type);
|
|
484
|
+
// Get user info
|
|
485
|
+
console.error('👤 Fetching user information...');
|
|
486
|
+
const userInfoResponse = await axios.get(API_ENDPOINTS.USERINFO, {
|
|
487
|
+
headers: { Authorization: `Bearer ${access_token}` }
|
|
488
|
+
});
|
|
489
|
+
console.error('✅ User info received:', userInfoResponse.data.email);
|
|
490
|
+
// Fetch user organizations
|
|
491
|
+
let organizations = [];
|
|
492
|
+
let selectedOrgId = undefined;
|
|
493
|
+
try {
|
|
494
|
+
console.error('🏢 Attempting to fetch organizations...');
|
|
495
|
+
const orgResponse = await axios.get(API_ENDPOINTS.ORGANIZATIONS, {
|
|
496
|
+
headers: { Authorization: `Bearer ${access_token}` }
|
|
497
|
+
});
|
|
498
|
+
console.error('🏢 Organization response status:', orgResponse.status);
|
|
499
|
+
console.error('🏢 Organization response data:', JSON.stringify(orgResponse.data, null, 2));
|
|
500
|
+
organizations = orgResponse.data.data?.userOrgs || [];
|
|
501
|
+
const currentOrgId = orgResponse.data.data?.currentOrg;
|
|
502
|
+
// Prefer current org from APP; fallback to first available org_id
|
|
503
|
+
selectedOrgId = currentOrgId || organizations[0]?.org_id;
|
|
504
|
+
console.error(`📊 Found ${organizations.length} organizations, current: ${currentOrgId}`);
|
|
505
|
+
if (organizations.length === 0) {
|
|
506
|
+
console.error('⚠️ No organizations found in response - this might cause issues with APP calls');
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
console.error(`🔍 First org structure:`, JSON.stringify(organizations[0], null, 2));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
catch (orgError) {
|
|
513
|
+
console.error('⚠️ Failed to fetch organizations:');
|
|
514
|
+
console.error(' Error:', orgError instanceof Error ? orgError.message : String(orgError));
|
|
515
|
+
if (axios.isAxiosError(orgError)) {
|
|
516
|
+
console.error(' Status:', orgError.response?.status);
|
|
517
|
+
console.error(' Response:', JSON.stringify(orgError.response?.data, null, 2));
|
|
518
|
+
}
|
|
519
|
+
// Continue without organizations - user can still authenticate but won't be able to access org-specific data
|
|
520
|
+
}
|
|
521
|
+
// Store session
|
|
522
|
+
console.error('💾 Creating session object...');
|
|
523
|
+
this.session = {
|
|
524
|
+
// Align with extension behavior: prefer id_token for APP calls
|
|
525
|
+
accessToken: id_token || access_token,
|
|
526
|
+
idToken: id_token,
|
|
527
|
+
tokenType: token_type || 'Bearer',
|
|
528
|
+
userInfo: userInfoResponse.data,
|
|
529
|
+
expiresAt: Date.now() + (expires_in * 1000),
|
|
530
|
+
organizations,
|
|
531
|
+
selectedOrgId
|
|
532
|
+
};
|
|
533
|
+
console.error('✅ Session object created in memory');
|
|
534
|
+
// Save session to persistent storage - COMMENTED OUT FOR SECURITY
|
|
535
|
+
// console.error('💾 Attempting to save session to persistent storage...');
|
|
536
|
+
// try {
|
|
537
|
+
// await this.sessionStorage.saveSession(this.session);
|
|
538
|
+
// console.error('✅ Session saved successfully to persistent storage');
|
|
539
|
+
// } catch (saveError) {
|
|
540
|
+
// console.error('❌ Failed to save session:', saveError);
|
|
541
|
+
// throw saveError;
|
|
542
|
+
// }
|
|
543
|
+
console.error('⚠️ Session storage disabled - credentials not persisted to disk');
|
|
544
|
+
const expirationDate = new Date(this.session.expiresAt).toLocaleString();
|
|
545
|
+
console.error('✅ Authentication successful for user:', userInfoResponse.data.email);
|
|
546
|
+
console.error('💾 Session saved and expires at:', expirationDate);
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
550
|
+
const responseData = error?.response?.data;
|
|
551
|
+
console.error('❌ Token exchange failed:', responseData || errorMessage);
|
|
552
|
+
throw new Error('Failed to exchange authorization code for tokens');
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AuthenticationManager } from "./authManager.js";
|
|
2
|
+
export declare function getOrCreateSession(username: string, password: string): Promise<AuthenticationManager>;
|
|
3
|
+
export declare function invalidateSession(username: string): void;
|
|
4
|
+
export declare function invalidateSessionByToken(token: string): void;
|
|
5
|
+
//# sourceMappingURL=sessionCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionCache.d.ts","sourceRoot":"","sources":["../../src/auth/sessionCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAKzD,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAY3G;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGxD;AAGD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAQ5D"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AuthenticationManager } from "./authManager.js";
|
|
2
|
+
// In-memory token cache keyed by username
|
|
3
|
+
const cache = new Map();
|
|
4
|
+
export async function getOrCreateSession(username, password) {
|
|
5
|
+
const cached = cache.get(username);
|
|
6
|
+
if (cached && cached.isAuthenticated()) {
|
|
7
|
+
return cached;
|
|
8
|
+
}
|
|
9
|
+
// First request or token expired — login fresh
|
|
10
|
+
console.log(`Logging in: ${username}`);
|
|
11
|
+
const manager = await AuthenticationManager.login(username, password);
|
|
12
|
+
cache.set(username, manager);
|
|
13
|
+
console.log(`Session created for: ${username}`);
|
|
14
|
+
return manager;
|
|
15
|
+
}
|
|
16
|
+
export function invalidateSession(username) {
|
|
17
|
+
cache.delete(username);
|
|
18
|
+
console.log(`Session invalidated for: ${username}`);
|
|
19
|
+
}
|
|
20
|
+
// Called by tool handlers when an API call gets a 401 — token rejected by PerfAI
|
|
21
|
+
export function invalidateSessionByToken(token) {
|
|
22
|
+
for (const [username, manager] of cache.entries()) {
|
|
23
|
+
if (manager.getAccessToken() === token) {
|
|
24
|
+
cache.delete(username);
|
|
25
|
+
console.log(`Session evicted (token rejected) for: ${username}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { UserSession } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Secure session storage for MCP PerfAI server
|
|
4
|
+
* Handles persistent storage of authentication sessions with encryption
|
|
5
|
+
*/
|
|
6
|
+
export declare class SessionStorage {
|
|
7
|
+
private readonly storageDir;
|
|
8
|
+
private readonly sessionFile;
|
|
9
|
+
private readonly keyFile;
|
|
10
|
+
private encryptionKey;
|
|
11
|
+
constructor(appName?: string);
|
|
12
|
+
/**
|
|
13
|
+
* Get OS-specific configuration directory
|
|
14
|
+
*/
|
|
15
|
+
private getConfigDirectory;
|
|
16
|
+
/**
|
|
17
|
+
* Ensure storage directory exists
|
|
18
|
+
*/
|
|
19
|
+
private ensureStorageDirectory;
|
|
20
|
+
/**
|
|
21
|
+
* Initialize or load encryption key
|
|
22
|
+
*/
|
|
23
|
+
private initializeEncryptionKey;
|
|
24
|
+
/**
|
|
25
|
+
* Encrypt data using AES-256-CBC
|
|
26
|
+
*/
|
|
27
|
+
private encrypt;
|
|
28
|
+
/**
|
|
29
|
+
* Decrypt data using AES-256-CBC
|
|
30
|
+
*/
|
|
31
|
+
private decrypt;
|
|
32
|
+
/**
|
|
33
|
+
* Save session to persistent storage
|
|
34
|
+
*/
|
|
35
|
+
saveSession(session: UserSession): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Load session from persistent storage
|
|
38
|
+
*/
|
|
39
|
+
loadSession(): Promise<UserSession | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Clear stored session
|
|
42
|
+
*/
|
|
43
|
+
clearSession(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Check if session exists and is valid
|
|
46
|
+
*/
|
|
47
|
+
hasValidSession(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Get session storage location (for debugging)
|
|
50
|
+
*/
|
|
51
|
+
getStorageLocation(): string;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=sessionStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionStorage.d.ts","sourceRoot":"","sources":["../../src/auth/sessionStorage.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,aAAa,CAAuB;gBAEhC,OAAO,GAAE,MAAqB;IAU1C;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAiB/B;;OAEG;IACH,OAAO,CAAC,OAAO;IAqBf;;OAEG;IACH,OAAO,CAAC,OAAO;IA4Bf;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BtD;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IA+ChD;;OAEG;IACH,YAAY,IAAI,IAAI;IAWpB;;OAEG;IACH,eAAe,IAAI,OAAO;IAoB1B;;OAEG;IACH,kBAAkB,IAAI,MAAM;CAG7B"}
|