@lanonasis/cli 3.1.13 → 3.3.15
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/dist/__tests__/auth-persistence.test.d.ts +1 -0
- package/dist/__tests__/auth-persistence.test.js +243 -0
- package/dist/__tests__/cross-device-integration.test.d.ts +1 -0
- package/dist/__tests__/cross-device-integration.test.js +305 -0
- package/dist/__tests__/mcp-connection-reliability.test.d.ts +1 -0
- package/dist/__tests__/mcp-connection-reliability.test.js +489 -0
- package/dist/__tests__/setup.d.ts +1 -0
- package/dist/__tests__/setup.js +26 -0
- package/dist/commands/api-keys.js +12 -6
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +420 -50
- package/dist/commands/config.js +519 -1
- package/dist/commands/mcp.js +299 -0
- package/dist/index.js +5 -1
- package/dist/mcp/server/lanonasis-server.d.ts +161 -6
- package/dist/mcp/server/lanonasis-server.js +813 -17
- package/dist/mcp/server/mcp/server/lanonasis-server.js +911 -0
- package/dist/mcp/server/utils/api.js +431 -0
- package/dist/mcp/server/utils/config.js +855 -0
- package/dist/utils/config.d.ts +40 -1
- package/dist/utils/config.js +273 -36
- package/dist/utils/mcp-client.d.ts +83 -2
- package/dist/utils/mcp-client.js +414 -15
- package/package.json +8 -4
package/dist/commands/auth.js
CHANGED
|
@@ -36,6 +36,340 @@ async function handleAuthDelay(config) {
|
|
|
36
36
|
console.log();
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
// Enhanced authentication failure handler
|
|
40
|
+
async function handleAuthenticationFailure(error, config, authMethod = 'jwt') {
|
|
41
|
+
// Increment failure count
|
|
42
|
+
await config.incrementFailureCount();
|
|
43
|
+
const failureCount = config.getFailureCount();
|
|
44
|
+
// Determine error type and provide specific guidance
|
|
45
|
+
const errorType = categorizeAuthError(error);
|
|
46
|
+
console.log();
|
|
47
|
+
console.log(chalk.red('✖ Authentication failed'));
|
|
48
|
+
switch (errorType) {
|
|
49
|
+
case 'invalid_credentials':
|
|
50
|
+
console.log(chalk.red('Invalid credentials provided'));
|
|
51
|
+
if (authMethod === 'vendor_key') {
|
|
52
|
+
console.log(chalk.gray('• Check your vendor key format: pk_xxx.sk_xxx'));
|
|
53
|
+
console.log(chalk.gray('• Verify the key is active in your account dashboard'));
|
|
54
|
+
console.log(chalk.gray('• Ensure you copied the complete key including both parts'));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(chalk.gray('• Double-check your email and password'));
|
|
58
|
+
console.log(chalk.gray('• Passwords are case-sensitive'));
|
|
59
|
+
console.log(chalk.gray('• Consider resetting your password if needed'));
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
case 'network_error':
|
|
63
|
+
console.log(chalk.red('Network connection failed'));
|
|
64
|
+
console.log(chalk.gray('• Check your internet connection'));
|
|
65
|
+
console.log(chalk.gray('• Verify you can access https://api.lanonasis.com'));
|
|
66
|
+
console.log(chalk.gray('• Try again in a few moments'));
|
|
67
|
+
if (failureCount >= 2) {
|
|
68
|
+
console.log(chalk.gray('• Consider using a different network if issues persist'));
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case 'server_error':
|
|
72
|
+
console.log(chalk.red('Server temporarily unavailable'));
|
|
73
|
+
console.log(chalk.gray('• The authentication service may be experiencing issues'));
|
|
74
|
+
console.log(chalk.gray('• Please try again in a few minutes'));
|
|
75
|
+
console.log(chalk.gray('• Check https://status.lanonasis.com for service status'));
|
|
76
|
+
break;
|
|
77
|
+
case 'rate_limited':
|
|
78
|
+
console.log(chalk.red('Too many authentication attempts'));
|
|
79
|
+
console.log(chalk.gray('• Please wait before trying again'));
|
|
80
|
+
console.log(chalk.gray('• Rate limiting helps protect your account'));
|
|
81
|
+
console.log(chalk.gray('• Consider using a vendor key for automated access'));
|
|
82
|
+
break;
|
|
83
|
+
case 'expired_token':
|
|
84
|
+
console.log(chalk.red('Authentication token has expired'));
|
|
85
|
+
console.log(chalk.gray('• Please log in again to refresh your session'));
|
|
86
|
+
console.log(chalk.gray('• Consider using a vendor key for longer-term access'));
|
|
87
|
+
await config.clearInvalidCredentials();
|
|
88
|
+
break;
|
|
89
|
+
default:
|
|
90
|
+
console.log(chalk.red(`Unexpected error: ${error.message || 'Unknown error'}`));
|
|
91
|
+
console.log(chalk.gray('• Please try again'));
|
|
92
|
+
console.log(chalk.gray('• If the problem persists, contact support'));
|
|
93
|
+
}
|
|
94
|
+
// Progressive guidance for repeated failures
|
|
95
|
+
if (failureCount >= 3) {
|
|
96
|
+
console.log();
|
|
97
|
+
console.log(chalk.yellow('💡 Multiple failures detected. Recovery options:'));
|
|
98
|
+
if (authMethod === 'vendor_key') {
|
|
99
|
+
console.log(chalk.cyan('• Generate a new vendor key from your dashboard'));
|
|
100
|
+
console.log(chalk.cyan('• Try: lanonasis auth logout && lanonasis auth login'));
|
|
101
|
+
console.log(chalk.cyan('• Switch to browser login: lanonasis auth login --use-web-auth'));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.log(chalk.cyan('• Reset your password if you\'re unsure'));
|
|
105
|
+
console.log(chalk.cyan('• Try vendor key authentication instead'));
|
|
106
|
+
console.log(chalk.cyan('• Clear stored config: lanonasis auth logout'));
|
|
107
|
+
}
|
|
108
|
+
if (failureCount >= 5) {
|
|
109
|
+
console.log(chalk.yellow('• Consider contacting support if issues persist'));
|
|
110
|
+
console.log(chalk.gray('• Include error details and your email address'));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Categorize authentication errors for specific handling
|
|
115
|
+
function categorizeAuthError(error) {
|
|
116
|
+
if (!error)
|
|
117
|
+
return 'unknown';
|
|
118
|
+
// Check HTTP status codes
|
|
119
|
+
if (error.response?.status) {
|
|
120
|
+
const status = error.response.status;
|
|
121
|
+
switch (status) {
|
|
122
|
+
case 401:
|
|
123
|
+
// Check if it's specifically an expired token
|
|
124
|
+
if (error.response.data?.error?.includes('expired') || error.response.data?.message?.includes('expired')) {
|
|
125
|
+
return 'expired_token';
|
|
126
|
+
}
|
|
127
|
+
return 'invalid_credentials';
|
|
128
|
+
case 403:
|
|
129
|
+
return 'invalid_credentials';
|
|
130
|
+
case 429:
|
|
131
|
+
return 'rate_limited';
|
|
132
|
+
case 500:
|
|
133
|
+
case 502:
|
|
134
|
+
case 503:
|
|
135
|
+
case 504:
|
|
136
|
+
return 'server_error';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Check error codes for network issues
|
|
140
|
+
if (error.code) {
|
|
141
|
+
switch (error.code) {
|
|
142
|
+
case 'ECONNREFUSED':
|
|
143
|
+
case 'ENOTFOUND':
|
|
144
|
+
case 'ECONNRESET':
|
|
145
|
+
case 'ETIMEDOUT':
|
|
146
|
+
case 'ENETUNREACH':
|
|
147
|
+
return 'network_error';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Check error messages
|
|
151
|
+
const message = error.message?.toLowerCase() || '';
|
|
152
|
+
if (message.includes('network') || message.includes('connection') || message.includes('timeout')) {
|
|
153
|
+
return 'network_error';
|
|
154
|
+
}
|
|
155
|
+
if (message.includes('invalid') || message.includes('unauthorized') || message.includes('forbidden')) {
|
|
156
|
+
return 'invalid_credentials';
|
|
157
|
+
}
|
|
158
|
+
if (message.includes('expired')) {
|
|
159
|
+
return 'expired_token';
|
|
160
|
+
}
|
|
161
|
+
if (message.includes('rate limit') || message.includes('too many')) {
|
|
162
|
+
return 'rate_limited';
|
|
163
|
+
}
|
|
164
|
+
return 'unknown';
|
|
165
|
+
}
|
|
166
|
+
export async function diagnoseCommand() {
|
|
167
|
+
const config = new CLIConfig();
|
|
168
|
+
await config.init();
|
|
169
|
+
console.log(chalk.blue.bold('🔍 Authentication Diagnostic'));
|
|
170
|
+
console.log(colors.info('━'.repeat(50)));
|
|
171
|
+
console.log();
|
|
172
|
+
const diagnostics = {
|
|
173
|
+
configExists: false,
|
|
174
|
+
hasCredentials: false,
|
|
175
|
+
credentialType: 'none',
|
|
176
|
+
credentialsValid: false,
|
|
177
|
+
tokenExpired: false,
|
|
178
|
+
authFailures: 0,
|
|
179
|
+
lastFailure: null,
|
|
180
|
+
endpointsReachable: false,
|
|
181
|
+
serviceDiscovery: false,
|
|
182
|
+
deviceId: null
|
|
183
|
+
};
|
|
184
|
+
// Step 1: Check if config exists
|
|
185
|
+
console.log(chalk.cyan('1. Configuration File'));
|
|
186
|
+
try {
|
|
187
|
+
const configExists = await config.exists();
|
|
188
|
+
diagnostics.configExists = configExists;
|
|
189
|
+
if (configExists) {
|
|
190
|
+
console.log(chalk.green(' ✓ Config file exists at'), config.getConfigPath());
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.log(chalk.red(' ✖ Config file not found at'), config.getConfigPath());
|
|
194
|
+
console.log(chalk.gray(' → Run: lanonasis auth login'));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
console.log(chalk.red(' ✖ Error checking config:'), error instanceof Error ? error.message : 'Unknown error');
|
|
199
|
+
}
|
|
200
|
+
// Step 2: Check stored credentials
|
|
201
|
+
console.log(chalk.cyan('\n2. Stored Credentials'));
|
|
202
|
+
const token = config.getToken();
|
|
203
|
+
const vendorKey = config.getVendorKey();
|
|
204
|
+
const authMethod = config.get('authMethod');
|
|
205
|
+
if (vendorKey) {
|
|
206
|
+
diagnostics.hasCredentials = true;
|
|
207
|
+
diagnostics.credentialType = 'vendor_key';
|
|
208
|
+
console.log(chalk.green(' ✓ Vendor key found'));
|
|
209
|
+
// Validate vendor key format
|
|
210
|
+
const formatValidation = config.validateVendorKeyFormat(vendorKey);
|
|
211
|
+
if (formatValidation === true) {
|
|
212
|
+
console.log(chalk.green(' ✓ Vendor key format is valid'));
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
console.log(chalk.red(' ✖ Vendor key format is invalid:'));
|
|
216
|
+
console.log(chalk.gray(` ${formatValidation}`));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (token) {
|
|
220
|
+
diagnostics.hasCredentials = true;
|
|
221
|
+
diagnostics.credentialType = authMethod === 'oauth' ? 'oauth' : 'jwt';
|
|
222
|
+
console.log(chalk.green(` ✓ ${diagnostics.credentialType.toUpperCase()} token found`));
|
|
223
|
+
// Check token expiry
|
|
224
|
+
try {
|
|
225
|
+
const isAuth = await config.isAuthenticated();
|
|
226
|
+
if (!isAuth) {
|
|
227
|
+
diagnostics.tokenExpired = true;
|
|
228
|
+
console.log(chalk.red(' ✖ Token is expired'));
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
console.log(chalk.green(' ✓ Token is not expired'));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
console.log(chalk.yellow(' ⚠ Could not validate token expiry'));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
console.log(chalk.red(' ✖ No credentials found'));
|
|
240
|
+
console.log(chalk.gray(' → Run: lanonasis auth login'));
|
|
241
|
+
}
|
|
242
|
+
// Step 3: Check authentication failures
|
|
243
|
+
console.log(chalk.cyan('\n3. Authentication History'));
|
|
244
|
+
diagnostics.authFailures = config.getFailureCount();
|
|
245
|
+
diagnostics.lastFailure = config.getLastAuthFailure() ?? null;
|
|
246
|
+
if (diagnostics.authFailures === 0) {
|
|
247
|
+
console.log(chalk.green(' ✓ No recent authentication failures'));
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
console.log(chalk.yellow(` ⚠ ${diagnostics.authFailures} recent authentication failures`));
|
|
251
|
+
if (diagnostics.lastFailure) {
|
|
252
|
+
const lastFailureDate = new Date(diagnostics.lastFailure);
|
|
253
|
+
console.log(chalk.gray(` Last failure: ${lastFailureDate.toLocaleString()}`));
|
|
254
|
+
}
|
|
255
|
+
if (config.shouldDelayAuth()) {
|
|
256
|
+
const delayMs = config.getAuthDelayMs();
|
|
257
|
+
console.log(chalk.yellow(` ⚠ Authentication delay active: ${Math.round(delayMs / 1000)}s`));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Step 4: Test credential validation against server
|
|
261
|
+
console.log(chalk.cyan('\n4. Server Validation'));
|
|
262
|
+
if (diagnostics.hasCredentials) {
|
|
263
|
+
const spinner = ora('Testing credentials against server...').start();
|
|
264
|
+
try {
|
|
265
|
+
const isValid = await config.validateStoredCredentials();
|
|
266
|
+
diagnostics.credentialsValid = isValid;
|
|
267
|
+
if (isValid) {
|
|
268
|
+
spinner.succeed('Credentials are valid');
|
|
269
|
+
console.log(chalk.green(' ✓ Server authentication successful'));
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
spinner.fail('Credentials are invalid');
|
|
273
|
+
console.log(chalk.red(' ✖ Server rejected credentials'));
|
|
274
|
+
console.log(chalk.gray(' → Try: lanonasis auth login'));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
spinner.fail('Server validation failed');
|
|
279
|
+
console.log(chalk.red(' ✖ Could not validate with server:'));
|
|
280
|
+
console.log(chalk.gray(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
console.log(chalk.gray(' - Skipped (no credentials to validate)'));
|
|
285
|
+
}
|
|
286
|
+
// Step 5: Test endpoint connectivity
|
|
287
|
+
console.log(chalk.cyan('\n5. Endpoint Connectivity'));
|
|
288
|
+
const spinner2 = ora('Testing authentication endpoints...').start();
|
|
289
|
+
try {
|
|
290
|
+
await config.discoverServices();
|
|
291
|
+
diagnostics.serviceDiscovery = true;
|
|
292
|
+
const services = config.get('discoveredServices');
|
|
293
|
+
if (services) {
|
|
294
|
+
spinner2.succeed('Authentication endpoints reachable');
|
|
295
|
+
console.log(chalk.green(' ✓ Service discovery successful'));
|
|
296
|
+
console.log(chalk.gray(` Auth endpoint: ${services.auth_base}`));
|
|
297
|
+
diagnostics.endpointsReachable = true;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
spinner2.warn('Using fallback endpoints');
|
|
301
|
+
console.log(chalk.yellow(' ⚠ Service discovery failed, using fallbacks'));
|
|
302
|
+
diagnostics.endpointsReachable = true; // Fallbacks still work
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
spinner2.fail('Endpoint connectivity failed');
|
|
307
|
+
console.log(chalk.red(' ✖ Cannot reach authentication endpoints'));
|
|
308
|
+
console.log(chalk.gray(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
309
|
+
console.log(chalk.gray(' → Check internet connection'));
|
|
310
|
+
}
|
|
311
|
+
// Step 6: Device identification
|
|
312
|
+
console.log(chalk.cyan('\n6. Device Information'));
|
|
313
|
+
try {
|
|
314
|
+
const deviceId = await config.getDeviceId();
|
|
315
|
+
diagnostics.deviceId = deviceId;
|
|
316
|
+
console.log(chalk.green(' ✓ Device ID:'), chalk.gray(deviceId));
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
console.log(chalk.yellow(' ⚠ Could not get device ID'));
|
|
320
|
+
}
|
|
321
|
+
// Summary and recommendations
|
|
322
|
+
console.log(chalk.blue.bold('\n📋 Diagnostic Summary'));
|
|
323
|
+
console.log(colors.info('━'.repeat(50)));
|
|
324
|
+
const issues = [];
|
|
325
|
+
const recommendations = [];
|
|
326
|
+
if (!diagnostics.configExists) {
|
|
327
|
+
issues.push('No configuration file found');
|
|
328
|
+
recommendations.push('Run: lanonasis auth login');
|
|
329
|
+
}
|
|
330
|
+
if (!diagnostics.hasCredentials) {
|
|
331
|
+
issues.push('No authentication credentials stored');
|
|
332
|
+
recommendations.push('Run: lanonasis auth login --vendor-key pk_xxx.sk_xxx');
|
|
333
|
+
}
|
|
334
|
+
if (diagnostics.hasCredentials && !diagnostics.credentialsValid) {
|
|
335
|
+
issues.push('Stored credentials are invalid');
|
|
336
|
+
recommendations.push('Run: lanonasis auth logout && lanonasis auth login');
|
|
337
|
+
}
|
|
338
|
+
if (diagnostics.tokenExpired) {
|
|
339
|
+
issues.push('Authentication token has expired');
|
|
340
|
+
recommendations.push('Run: lanonasis auth login');
|
|
341
|
+
}
|
|
342
|
+
if (diagnostics.authFailures >= 3) {
|
|
343
|
+
issues.push(`Multiple authentication failures (${diagnostics.authFailures})`);
|
|
344
|
+
recommendations.push('Wait for delay period, then try: lanonasis auth login');
|
|
345
|
+
}
|
|
346
|
+
if (!diagnostics.endpointsReachable) {
|
|
347
|
+
issues.push('Cannot reach authentication endpoints');
|
|
348
|
+
recommendations.push('Check internet connection and firewall settings');
|
|
349
|
+
}
|
|
350
|
+
if (issues.length === 0) {
|
|
351
|
+
console.log(chalk.green('✅ All authentication checks passed!'));
|
|
352
|
+
console.log(chalk.cyan(' Your authentication is working correctly.'));
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
console.log(chalk.red(`❌ Found ${issues.length} issue(s):`));
|
|
356
|
+
issues.forEach(issue => {
|
|
357
|
+
console.log(chalk.red(` • ${issue}`));
|
|
358
|
+
});
|
|
359
|
+
console.log(chalk.yellow('\n💡 Recommended actions:'));
|
|
360
|
+
recommendations.forEach(rec => {
|
|
361
|
+
console.log(chalk.cyan(` • ${rec}`));
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
// Additional troubleshooting info
|
|
365
|
+
if (diagnostics.authFailures > 0 || !diagnostics.credentialsValid) {
|
|
366
|
+
console.log(chalk.gray('\n🔧 Additional troubleshooting:'));
|
|
367
|
+
console.log(chalk.gray(' • Verify your vendor key format: pk_xxx.sk_xxx'));
|
|
368
|
+
console.log(chalk.gray(' • Check if your key is active in the dashboard'));
|
|
369
|
+
console.log(chalk.gray(' • Try browser authentication: lanonasis auth login --use-web-auth'));
|
|
370
|
+
console.log(chalk.gray(' • Contact support if issues persist'));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
39
373
|
export async function loginCommand(options) {
|
|
40
374
|
const config = new CLIConfig();
|
|
41
375
|
await config.init();
|
|
@@ -109,19 +443,8 @@ async function handleVendorKeyAuth(vendorKey, config) {
|
|
|
109
443
|
}
|
|
110
444
|
catch (error) {
|
|
111
445
|
spinner.fail('Vendor key validation failed');
|
|
112
|
-
//
|
|
113
|
-
await config
|
|
114
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
115
|
-
console.error(chalk.red('✖ Invalid vendor key:'), errorMessage);
|
|
116
|
-
// Provide guidance for repeated failures
|
|
117
|
-
const failureCount = config.getFailureCount();
|
|
118
|
-
if (failureCount >= 3) {
|
|
119
|
-
console.log();
|
|
120
|
-
console.log(chalk.yellow('💡 Troubleshooting tips:'));
|
|
121
|
-
console.log(chalk.gray('• Verify your vendor key format: pk_xxx.sk_xxx'));
|
|
122
|
-
console.log(chalk.gray('• Check if your key is active in your account dashboard'));
|
|
123
|
-
console.log(chalk.gray('• Try: lanonasis auth logout && lanonasis auth login'));
|
|
124
|
-
}
|
|
446
|
+
// Use enhanced error handling
|
|
447
|
+
await handleAuthenticationFailure(error, config, 'vendor_key');
|
|
125
448
|
process.exit(1);
|
|
126
449
|
}
|
|
127
450
|
}
|
|
@@ -130,6 +453,13 @@ async function handleVendorKeyFlow(config) {
|
|
|
130
453
|
console.log(chalk.yellow('🔑 Vendor Key Authentication'));
|
|
131
454
|
console.log(chalk.gray('Vendor keys provide secure API access with format: pk_xxx.sk_xxx'));
|
|
132
455
|
console.log();
|
|
456
|
+
// Enhanced guidance for obtaining vendor keys
|
|
457
|
+
console.log(chalk.cyan('📋 How to get your vendor key:'));
|
|
458
|
+
console.log(chalk.gray('1. Visit your Lanonasis dashboard at https://app.lanonasis.com'));
|
|
459
|
+
console.log(chalk.gray('2. Navigate to Settings → API Keys'));
|
|
460
|
+
console.log(chalk.gray('3. Click "Generate New Key" and copy the full key'));
|
|
461
|
+
console.log(chalk.gray('4. The key format should be: pk_[letters/numbers].sk_[letters/numbers]'));
|
|
462
|
+
console.log();
|
|
133
463
|
const { vendorKey } = await inquirer.prompt([
|
|
134
464
|
{
|
|
135
465
|
type: 'password',
|
|
@@ -137,32 +467,73 @@ async function handleVendorKeyFlow(config) {
|
|
|
137
467
|
message: 'Enter your vendor key (pk_xxx.sk_xxx):',
|
|
138
468
|
mask: '*',
|
|
139
469
|
validate: (input) => {
|
|
140
|
-
|
|
141
|
-
return 'Vendor key is required';
|
|
142
|
-
if (!input.match(/^pk_[a-zA-Z0-9]+\.sk_[a-zA-Z0-9]+$/)) {
|
|
143
|
-
return 'Invalid format. Expected: pk_xxx.sk_xxx';
|
|
144
|
-
}
|
|
145
|
-
return true;
|
|
470
|
+
return validateVendorKeyFormat(input);
|
|
146
471
|
}
|
|
147
472
|
}
|
|
148
473
|
]);
|
|
149
474
|
await handleVendorKeyAuth(vendorKey, config);
|
|
150
475
|
}
|
|
476
|
+
// Enhanced vendor key format validation with detailed error messages
|
|
477
|
+
function validateVendorKeyFormat(input) {
|
|
478
|
+
if (!input || input.trim().length === 0) {
|
|
479
|
+
return 'Vendor key is required';
|
|
480
|
+
}
|
|
481
|
+
const trimmed = input.trim();
|
|
482
|
+
// Check basic format
|
|
483
|
+
if (!trimmed.includes('.')) {
|
|
484
|
+
return 'Invalid format: Vendor key must contain a dot (.) separator\nExpected format: pk_xxx.sk_xxx';
|
|
485
|
+
}
|
|
486
|
+
const parts = trimmed.split('.');
|
|
487
|
+
if (parts.length !== 2) {
|
|
488
|
+
return 'Invalid format: Vendor key must have exactly two parts separated by a dot\nExpected format: pk_xxx.sk_xxx';
|
|
489
|
+
}
|
|
490
|
+
const [publicPart, secretPart] = parts;
|
|
491
|
+
// Validate public key part
|
|
492
|
+
if (!publicPart.startsWith('pk_')) {
|
|
493
|
+
return 'Invalid format: First part must start with "pk_"\nExpected format: pk_xxx.sk_xxx';
|
|
494
|
+
}
|
|
495
|
+
if (publicPart.length < 4) {
|
|
496
|
+
return 'Invalid format: Public key part is too short\nExpected format: pk_xxx.sk_xxx (where xxx is alphanumeric)';
|
|
497
|
+
}
|
|
498
|
+
const publicKeyContent = publicPart.substring(3); // Remove 'pk_'
|
|
499
|
+
if (!/^[a-zA-Z0-9]+$/.test(publicKeyContent)) {
|
|
500
|
+
return 'Invalid format: Public key part contains invalid characters\nOnly letters and numbers are allowed after "pk_"';
|
|
501
|
+
}
|
|
502
|
+
// Validate secret key part
|
|
503
|
+
if (!secretPart.startsWith('sk_')) {
|
|
504
|
+
return 'Invalid format: Second part must start with "sk_"\nExpected format: pk_xxx.sk_xxx';
|
|
505
|
+
}
|
|
506
|
+
if (secretPart.length < 4) {
|
|
507
|
+
return 'Invalid format: Secret key part is too short\nExpected format: pk_xxx.sk_xxx (where xxx is alphanumeric)';
|
|
508
|
+
}
|
|
509
|
+
const secretKeyContent = secretPart.substring(3); // Remove 'sk_'
|
|
510
|
+
if (!/^[a-zA-Z0-9]+$/.test(secretKeyContent)) {
|
|
511
|
+
return 'Invalid format: Secret key part contains invalid characters\nOnly letters and numbers are allowed after "sk_"';
|
|
512
|
+
}
|
|
513
|
+
// Check minimum length requirements
|
|
514
|
+
if (publicKeyContent.length < 8) {
|
|
515
|
+
return 'Invalid format: Public key part is too short (minimum 8 characters after "pk_")';
|
|
516
|
+
}
|
|
517
|
+
if (secretKeyContent.length < 16) {
|
|
518
|
+
return 'Invalid format: Secret key part is too short (minimum 16 characters after "sk_")';
|
|
519
|
+
}
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
151
522
|
async function handleOAuthFlow(config) {
|
|
152
523
|
console.log();
|
|
153
524
|
console.log(chalk.yellow('🌐 Browser-Based Authentication'));
|
|
154
525
|
console.log(chalk.gray('This will open your browser for secure authentication'));
|
|
155
526
|
console.log();
|
|
156
|
-
const {
|
|
527
|
+
const { openBrowser } = await inquirer.prompt([
|
|
157
528
|
{
|
|
158
529
|
type: 'confirm',
|
|
159
|
-
name: '
|
|
530
|
+
name: 'openBrowser',
|
|
160
531
|
message: 'Open browser for authentication?',
|
|
161
532
|
default: true
|
|
162
533
|
}
|
|
163
534
|
]);
|
|
164
|
-
if (!
|
|
165
|
-
console.log(chalk.yellow('Authentication cancelled'));
|
|
535
|
+
if (!openBrowser) {
|
|
536
|
+
console.log(chalk.yellow('⚠️ Authentication cancelled'));
|
|
166
537
|
return;
|
|
167
538
|
}
|
|
168
539
|
// Use the browser-based CLI login endpoint from MCP service
|
|
@@ -182,10 +553,30 @@ async function handleOAuthFlow(config) {
|
|
|
182
553
|
type: 'input',
|
|
183
554
|
name: 'token',
|
|
184
555
|
message: 'Paste the authentication token from browser:',
|
|
185
|
-
validate: (input) => {
|
|
556
|
+
validate: async (input) => {
|
|
186
557
|
if (!input || input.trim().length === 0) {
|
|
187
558
|
return 'Token is required';
|
|
188
559
|
}
|
|
560
|
+
const trimmed = input.trim();
|
|
561
|
+
// Reject if user pasted a URL instead of token
|
|
562
|
+
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
|
|
563
|
+
return 'Please paste the TOKEN from the page, not the URL';
|
|
564
|
+
}
|
|
565
|
+
// Check token format - should start with 'cli_' or be a JWT
|
|
566
|
+
if (!trimmed.startsWith('cli_') && !trimmed.match(/^[\w-]+\.[\w-]+\.[\w-]+$/)) {
|
|
567
|
+
return 'Invalid token format. Expected format: cli_xxx or JWT token';
|
|
568
|
+
}
|
|
569
|
+
// Verify token with server
|
|
570
|
+
try {
|
|
571
|
+
const response = await apiClient.post('/auth/verify', { token: trimmed });
|
|
572
|
+
if (!response.valid) {
|
|
573
|
+
return 'Token verification failed. Please try again.';
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
const errorMessage = error instanceof Error ? error.message : 'Server verification failed';
|
|
578
|
+
return `Token verification error: ${errorMessage}`;
|
|
579
|
+
}
|
|
189
580
|
return true;
|
|
190
581
|
}
|
|
191
582
|
}
|
|
@@ -251,22 +642,12 @@ async function handleCredentialsFlow(options, config) {
|
|
|
251
642
|
}
|
|
252
643
|
catch (error) {
|
|
253
644
|
spinner.fail('Login failed');
|
|
254
|
-
//
|
|
255
|
-
await config
|
|
256
|
-
|
|
645
|
+
// Use enhanced error handling
|
|
646
|
+
await handleAuthenticationFailure(error, config, 'jwt');
|
|
647
|
+
// For 401 errors, offer registration option
|
|
257
648
|
const errorResponse = error && typeof error === 'object' && 'response' in error ? error.response : null;
|
|
258
649
|
if (errorResponse && typeof errorResponse === 'object' && 'status' in errorResponse && errorResponse.status === 401) {
|
|
259
|
-
console.
|
|
260
|
-
// Provide guidance for repeated failures
|
|
261
|
-
const failureCount = config.getFailureCount();
|
|
262
|
-
if (failureCount >= 3) {
|
|
263
|
-
console.log();
|
|
264
|
-
console.log(chalk.yellow('💡 Multiple login failures detected. Consider:'));
|
|
265
|
-
console.log(chalk.gray('• Double-check your email and password'));
|
|
266
|
-
console.log(chalk.gray('• Reset your password if needed'));
|
|
267
|
-
console.log(chalk.gray('• Try using a vendor key instead: lanonasis auth login --vendor-key'));
|
|
268
|
-
}
|
|
269
|
-
// Ask if they want to register
|
|
650
|
+
console.log();
|
|
270
651
|
const answer = await inquirer.prompt([
|
|
271
652
|
{
|
|
272
653
|
type: 'confirm',
|
|
@@ -277,18 +658,7 @@ async function handleCredentialsFlow(options, config) {
|
|
|
277
658
|
]);
|
|
278
659
|
if (answer.register) {
|
|
279
660
|
await registerFlow(email);
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
console.error(chalk.red('✖ Login failed:'), errorMessage);
|
|
284
|
-
// Provide guidance for repeated failures
|
|
285
|
-
const failureCount = config.getFailureCount();
|
|
286
|
-
if (failureCount >= 3) {
|
|
287
|
-
console.log();
|
|
288
|
-
console.log(chalk.yellow('💡 Connection issues detected. Try:'));
|
|
289
|
-
console.log(chalk.gray('• Check your internet connection'));
|
|
290
|
-
console.log(chalk.gray('• Verify the service is available'));
|
|
291
|
-
console.log(chalk.gray('• Try again later'));
|
|
661
|
+
return; // Don't exit if registration succeeds
|
|
292
662
|
}
|
|
293
663
|
}
|
|
294
664
|
process.exit(1);
|