@lanonasis/cli 3.7.5 → 3.7.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/dist/commands/auth.js +43 -59
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -91,7 +91,7 @@ async function handleAuthenticationFailure(error, config, authMethod = 'jwt') {
|
|
|
91
91
|
await config.clearInvalidCredentials();
|
|
92
92
|
break;
|
|
93
93
|
default:
|
|
94
|
-
console.log(chalk.red(`Unexpected error: ${error.message || 'Unknown error'}`));
|
|
94
|
+
console.log(chalk.red(`Unexpected error: ${sanitizeErrorMessage(error.message || 'Unknown error')}`));
|
|
95
95
|
console.log(chalk.gray('• Please try again'));
|
|
96
96
|
console.log(chalk.gray('• If the problem persists, contact support'));
|
|
97
97
|
}
|
|
@@ -183,12 +183,38 @@ function generatePKCE() {
|
|
|
183
183
|
.digest('base64url');
|
|
184
184
|
return { verifier, challenge };
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Sanitize error messages to prevent command injection
|
|
188
|
+
*/
|
|
189
|
+
function sanitizeErrorMessage(message) {
|
|
190
|
+
if (typeof message !== 'string')
|
|
191
|
+
return 'Unknown error';
|
|
192
|
+
// Remove potential command injection characters
|
|
193
|
+
return message
|
|
194
|
+
.replace(/[;&|`$()]/g, '') // Remove shell metacharacters
|
|
195
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove script tags
|
|
196
|
+
.replace(/javascript:/gi, '') // Remove javascript: URLs
|
|
197
|
+
.trim();
|
|
198
|
+
}
|
|
186
199
|
/**
|
|
187
200
|
* Start local HTTP server to catch OAuth2 callback
|
|
188
201
|
*/
|
|
189
202
|
function createCallbackServer(port = 8888) {
|
|
190
203
|
return new Promise((resolve, reject) => {
|
|
204
|
+
// Sanitize HTML to prevent XSS
|
|
205
|
+
function sanitizeHtml(str) {
|
|
206
|
+
return str
|
|
207
|
+
.replace(/&/g, '&')
|
|
208
|
+
.replace(/</g, '<')
|
|
209
|
+
.replace(/>/g, '>')
|
|
210
|
+
.replace(/"/g, '"')
|
|
211
|
+
.replace(/'/g, ''');
|
|
212
|
+
}
|
|
191
213
|
const server = http.createServer((req, res) => {
|
|
214
|
+
// Set security headers
|
|
215
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
216
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
217
|
+
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
192
218
|
const parsedUrl = url.parse(req.url, true);
|
|
193
219
|
if (parsedUrl.pathname === '/callback') {
|
|
194
220
|
const { code, state, error, error_description } = parsedUrl.query;
|
|
@@ -200,7 +226,7 @@ function createCallbackServer(port = 8888) {
|
|
|
200
226
|
<head><title>Authentication Failed</title></head>
|
|
201
227
|
<body style="font-family: sans-serif; text-align: center; padding: 50px;">
|
|
202
228
|
<h1>❌ Authentication Failed</h1>
|
|
203
|
-
<p>${error_description || error}</p>
|
|
229
|
+
<p>${sanitizeHtml(String(error_description || error))}</p>
|
|
204
230
|
<p style="color: gray;">You can close this window.</p>
|
|
205
231
|
</body>
|
|
206
232
|
</html>
|
|
@@ -280,7 +306,9 @@ async function exchangeCodeForTokens(code, verifier, authBase, redirectUri) {
|
|
|
280
306
|
}
|
|
281
307
|
/**
|
|
282
308
|
* Refresh OAuth2 access token using refresh token
|
|
309
|
+
* @internal Used for token refresh flows
|
|
283
310
|
*/
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
284
312
|
async function refreshOAuth2Token(config) {
|
|
285
313
|
const refreshToken = config.get('refresh_token');
|
|
286
314
|
if (!refreshToken) {
|
|
@@ -300,45 +328,11 @@ async function refreshOAuth2Token(config) {
|
|
|
300
328
|
await config.set('token_expires_at', Date.now() + (response.expires_in * 1000));
|
|
301
329
|
return true;
|
|
302
330
|
}
|
|
303
|
-
catch
|
|
331
|
+
catch {
|
|
304
332
|
console.error(chalk.yellow('⚠️ Token refresh failed, please re-authenticate'));
|
|
305
333
|
return false;
|
|
306
334
|
}
|
|
307
335
|
}
|
|
308
|
-
/**
|
|
309
|
-
* Exchange Supabase JWT token for auth-gateway API key
|
|
310
|
-
* This enables CLI to work with MCP WebSocket and all services seamlessly
|
|
311
|
-
*/
|
|
312
|
-
async function exchangeSupabaseTokenForApiKey(supabaseToken, config) {
|
|
313
|
-
try {
|
|
314
|
-
const discoveredServices = config.get('discoveredServices');
|
|
315
|
-
const authBase = discoveredServices?.auth_base || 'https://auth.lanonasis.com';
|
|
316
|
-
if (process.env.CLI_VERBOSE === 'true') {
|
|
317
|
-
console.log(chalk.dim(` Exchanging token at: ${authBase}/v1/auth/token/exchange`));
|
|
318
|
-
}
|
|
319
|
-
const response = await axios.post(`${authBase}/v1/auth/token/exchange`, {
|
|
320
|
-
project_scope: 'lanonasis-maas',
|
|
321
|
-
platform: 'cli'
|
|
322
|
-
}, {
|
|
323
|
-
headers: {
|
|
324
|
-
'Authorization': `Bearer ${supabaseToken}`,
|
|
325
|
-
'Content-Type': 'application/json',
|
|
326
|
-
'X-Project-Scope': 'lanonasis-maas'
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
return {
|
|
330
|
-
access_token: response.data.access_token,
|
|
331
|
-
user: response.data.user
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
catch (error) {
|
|
335
|
-
console.error(chalk.yellow('⚠️ Token exchange failed:', error.message));
|
|
336
|
-
if (process.env.CLI_VERBOSE === 'true' && error.response) {
|
|
337
|
-
console.error(chalk.dim(' Response:', JSON.stringify(error.response.data, null, 2)));
|
|
338
|
-
}
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
336
|
export async function diagnoseCommand() {
|
|
343
337
|
const config = new CLIConfig();
|
|
344
338
|
await config.init();
|
|
@@ -708,33 +702,23 @@ async function handleOAuthFlow(config) {
|
|
|
708
702
|
}
|
|
709
703
|
const tokens = await exchangeCodeForTokens(code, pkce.verifier, authBase, redirectUri);
|
|
710
704
|
spinner.succeed('Access tokens received');
|
|
711
|
-
// Store OAuth tokens
|
|
705
|
+
// Store OAuth tokens - these are already valid auth-gateway tokens from /oauth/token
|
|
706
|
+
// No need for additional exchange since /oauth/token returns auth-gateway's own tokens
|
|
712
707
|
await config.setToken(tokens.access_token);
|
|
713
708
|
await config.set('refresh_token', tokens.refresh_token);
|
|
714
709
|
await config.set('token_expires_at', Date.now() + (tokens.expires_in * 1000));
|
|
715
|
-
|
|
710
|
+
await config.set('authMethod', 'oauth2');
|
|
711
|
+
// The OAuth access token from auth-gateway works as the API token for all services
|
|
712
|
+
// Store it as the vendor key equivalent for MCP and API access
|
|
716
713
|
spinner.text = 'Configuring unified access...';
|
|
717
714
|
spinner.start();
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
console.log(chalk.green('✓ OAuth2 authentication successful'));
|
|
726
|
-
console.log(colors.info('You can now use all Lanonasis services'));
|
|
727
|
-
console.log(chalk.gray('✓ MCP, API, and CLI access configured'));
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
// Fallback
|
|
731
|
-
await config.set('authMethod', 'oauth2');
|
|
732
|
-
spinner.warn('Token exchange failed, OAuth token stored');
|
|
733
|
-
console.log();
|
|
734
|
-
console.log(chalk.green('✓ OAuth2 authentication successful'));
|
|
735
|
-
console.log(colors.info('You can now use Lanonasis services'));
|
|
736
|
-
console.log(chalk.yellow('⚠️ Some services may require re-authentication'));
|
|
737
|
-
}
|
|
715
|
+
// Use the OAuth access token directly - it's already an auth-gateway token
|
|
716
|
+
await config.setVendorKey(tokens.access_token);
|
|
717
|
+
spinner.succeed('Unified authentication configured');
|
|
718
|
+
console.log();
|
|
719
|
+
console.log(chalk.green('✓ OAuth2 authentication successful'));
|
|
720
|
+
console.log(colors.info('You can now use all Lanonasis services'));
|
|
721
|
+
console.log(chalk.gray('✓ MCP, API, and CLI access configured'));
|
|
738
722
|
process.exit(0);
|
|
739
723
|
}
|
|
740
724
|
catch (error) {
|