From 33834d808b0eb3e7bdaa5add7df3998ad8dd2b4d Mon Sep 17 00:00:00 2001 From: simos Date: Mon, 17 Nov 2025 08:48:15 +0100 Subject: [PATCH] feature:show auth status on settings --- server/index.js | 4 + server/routes/cli-auth.js | 179 ++++++++++++++++++++++++++++ src/components/Settings.jsx | 226 ++++++++++++++++++++++++++++++------ 3 files changed, 374 insertions(+), 35 deletions(-) create mode 100644 server/routes/cli-auth.js diff --git a/server/index.js b/server/index.js index ce798a4..0514346 100755 --- a/server/index.js +++ b/server/index.js @@ -70,6 +70,7 @@ import commandsRoutes from './routes/commands.js'; import settingsRoutes from './routes/settings.js'; import agentRoutes from './routes/agent.js'; import projectsRoutes from './routes/projects.js'; +import cliAuthRoutes from './routes/cli-auth.js'; import { initializeDatabase } from './database/db.js'; import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js'; @@ -250,6 +251,9 @@ app.use('/api/commands', authenticateToken, commandsRoutes); // Settings API Routes (protected) app.use('/api/settings', authenticateToken, settingsRoutes); +// CLI Authentication API Routes (protected) +app.use('/api/cli', authenticateToken, cliAuthRoutes); + // Agent API Routes (uses API key authentication) app.use('/api/agent', agentRoutes); diff --git a/server/routes/cli-auth.js b/server/routes/cli-auth.js new file mode 100644 index 0000000..d380413 --- /dev/null +++ b/server/routes/cli-auth.js @@ -0,0 +1,179 @@ +import express from 'express'; +import { spawn } from 'child_process'; +import fs from 'fs/promises'; +import path from 'path'; +import os from 'os'; + +const router = express.Router(); + +router.get('/claude/status', async (req, res) => { + try { + const credentialsResult = await checkClaudeCredentials(); + + if (credentialsResult.authenticated) { + return res.json({ + authenticated: true, + email: credentialsResult.email || 'Authenticated', + method: 'credentials_file' + }); + } + + return res.json({ + authenticated: false, + email: null, + error: credentialsResult.error || 'Not authenticated' + }); + + } catch (error) { + console.error('Error checking Claude auth status:', error); + res.status(500).json({ + authenticated: false, + email: null, + error: error.message + }); + } +}); + +router.get('/cursor/status', async (req, res) => { + try { + const result = await checkCursorStatus(); + + res.json({ + authenticated: result.authenticated, + email: result.email, + error: result.error + }); + + } catch (error) { + console.error('Error checking Cursor auth status:', error); + res.status(500).json({ + authenticated: false, + email: null, + error: error.message + }); + } +}); + +async function checkClaudeCredentials() { + try { + const credPath = path.join(os.homedir(), '.claude', '.credentials.json'); + const content = await fs.readFile(credPath, 'utf8'); + const creds = JSON.parse(content); + + if (creds.accessToken) { + const isExpired = creds.expiresAt && Date.now() >= creds.expiresAt; + + if (!isExpired) { + return { + authenticated: true, + email: creds.email || creds.user || null + }; + } + } + + return { + authenticated: false, + email: null + }; + } catch (error) { + return { + authenticated: false, + email: null + }; + } +} + +function checkCursorStatus() { + return new Promise((resolve) => { + let processCompleted = false; + + const timeout = setTimeout(() => { + if (!processCompleted) { + processCompleted = true; + if (childProcess) { + childProcess.kill(); + } + resolve({ + authenticated: false, + email: null, + error: 'Command timeout' + }); + } + }, 5000); + + let childProcess; + try { + childProcess = spawn('cursor-agent', ['status']); + } catch (err) { + clearTimeout(timeout); + processCompleted = true; + resolve({ + authenticated: false, + email: null, + error: 'Cursor CLI not found or not installed' + }); + return; + } + + let stdout = ''; + let stderr = ''; + + childProcess.stdout.on('data', (data) => { + stdout += data.toString(); + }); + + childProcess.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + childProcess.on('close', (code) => { + if (processCompleted) return; + processCompleted = true; + clearTimeout(timeout); + + if (code === 0) { + const emailMatch = stdout.match(/Logged in as ([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i); + + if (emailMatch) { + resolve({ + authenticated: true, + email: emailMatch[1], + output: stdout + }); + } else if (stdout.includes('Logged in')) { + resolve({ + authenticated: true, + email: 'Logged in', + output: stdout + }); + } else { + resolve({ + authenticated: false, + email: null, + error: 'Not logged in' + }); + } + } else { + resolve({ + authenticated: false, + email: null, + error: stderr || 'Not logged in' + }); + } + }); + + childProcess.on('error', (err) => { + if (processCompleted) return; + processCompleted = true; + clearTimeout(timeout); + + resolve({ + authenticated: false, + email: null, + error: 'Cursor CLI not found or not installed' + }); + }); + }); +} + +export default router; diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx index 0bae64e..5a051e0 100644 --- a/src/components/Settings.jsx +++ b/src/components/Settings.jsx @@ -81,10 +81,23 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) { const [newCursorDisallowedCommand, setNewCursorDisallowedCommand] = useState(''); const [cursorMcpServers, setCursorMcpServers] = useState([]); - // Login modal states const [showLoginModal, setShowLoginModal] = useState(false); - const [loginProvider, setLoginProvider] = useState(''); // 'claude' or 'cursor' + const [loginProvider, setLoginProvider] = useState(''); const [selectedProject, setSelectedProject] = useState(null); + + const [claudeAuthStatus, setClaudeAuthStatus] = useState({ + authenticated: false, + email: null, + loading: true, + error: null + }); + const [cursorAuthStatus, setCursorAuthStatus] = useState({ + authenticated: false, + email: null, + loading: true, + error: null + }); + // Common tool patterns for Claude const commonTools = [ 'Bash(git log:*)', @@ -344,6 +357,9 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) { useEffect(() => { if (isOpen) { loadSettings(); + // Check CLI authentication status + checkClaudeAuthStatus(); + checkCursorAuthStatus(); // Set the active tab when the modal opens setActiveTab(initialTab); } @@ -425,6 +441,81 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) { } }; + // CLI Authentication status checking functions + const checkClaudeAuthStatus = async () => { + try { + const token = localStorage.getItem('auth-token'); + const response = await fetch('/api/cli/claude/status', { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); + + if (response.ok) { + const data = await response.json(); + setClaudeAuthStatus({ + authenticated: data.authenticated, + email: data.email, + loading: false, + error: data.error || null + }); + } else { + setClaudeAuthStatus({ + authenticated: false, + email: null, + loading: false, + error: 'Failed to check authentication status' + }); + } + } catch (error) { + console.error('Error checking Claude auth status:', error); + setClaudeAuthStatus({ + authenticated: false, + email: null, + loading: false, + error: error.message + }); + } + }; + + const checkCursorAuthStatus = async () => { + try { + const token = localStorage.getItem('auth-token'); + const response = await fetch('/api/cli/cursor/status', { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); + + if (response.ok) { + const data = await response.json(); + setCursorAuthStatus({ + authenticated: data.authenticated, + email: data.email, + loading: false, + error: data.error || null + }); + } else { + setCursorAuthStatus({ + authenticated: false, + email: null, + loading: false, + error: 'Failed to check authentication status' + }); + } + } catch (error) { + console.error('Error checking Cursor auth status:', error); + setCursorAuthStatus({ + authenticated: false, + email: null, + loading: false, + error: error.message + }); + } + }; + // Login handlers const handleClaudeLogin = () => { setLoginProvider('claude'); @@ -440,8 +531,15 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) { const handleLoginComplete = (exitCode) => { if (exitCode === 0) { - // Login successful - could show a success message here + // Login successful - refresh authentication status setSaveStatus('success'); + + // Refresh auth status based on which provider was used + if (loginProvider === 'claude') { + checkClaudeAuthStatus(); + } else if (loginProvider === 'cursor') { + checkCursorAuthStatus(); + } } // Modal will close itself via the LoginModal component }; @@ -1039,23 +1137,52 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
-
-
-
- Claude CLI Login -
-
- Sign in to your Claude account to enable AI features -
+
+ {/* Authentication Status */} +
+ {claudeAuthStatus.loading ? ( + + Checking authentication... + + ) : claudeAuthStatus.authenticated ? ( +
+ + ✓ Logged in + + {claudeAuthStatus.email && ( + + as {claudeAuthStatus.email} + + )} +
+ ) : ( + + Not authenticated + + )} +
+ + {/* Login Button and Description */} +
+
+
+ Claude CLI Login +
+
+ {claudeAuthStatus.authenticated + ? 'Re-authenticate or switch accounts' + : 'Sign in to your Claude account to enable AI features'} +
+
+
-
@@ -1799,23 +1926,52 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
-
-
-
- Cursor CLI Login -
-
- Sign in to your Cursor account to enable AI features -
+
+ {/* Authentication Status */} +
+ {cursorAuthStatus.loading ? ( + + Checking authentication... + + ) : cursorAuthStatus.authenticated ? ( +
+ + ✓ Logged in + + {cursorAuthStatus.email && ( + + as {cursorAuthStatus.email} + + )} +
+ ) : ( + + Not authenticated + + )} +
+ + {/* Login Button and Description */} +
+
+
+ Cursor CLI Login +
+
+ {cursorAuthStatus.authenticated + ? 'Re-authenticate or switch accounts' + : 'Sign in to your Cursor account to enable AI features'} +
+
+
-