fix: settings api calls that would fail.

This commit is contained in:
simos
2025-11-17 13:58:58 +01:00
parent 6219c273a2
commit f91f9f702d
7 changed files with 61 additions and 194 deletions

View File

@@ -528,7 +528,8 @@ router.get('/next/:projectName', async (req, res) => {
console.warn('Failed to execute task-master CLI:', cliError.message); console.warn('Failed to execute task-master CLI:', cliError.message);
// Fallback to loading tasks and finding next one locally // Fallback to loading tasks and finding next one locally
const tasksResponse = await fetch(`${req.protocol}://${req.get('host')}/api/taskmaster/tasks/${encodeURIComponent(projectName)}`, { // Use localhost to bypass proxy for internal server-to-server calls
const tasksResponse = await fetch(`http://localhost:${process.env.PORT || 3001}/api/taskmaster/tasks/${encodeURIComponent(projectName)}`, {
headers: { headers: {
'Authorization': req.headers.authorization 'Authorization': req.headers.authorization
} }

View File

@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { Input } from './ui/input'; import { Input } from './ui/input';
import { Key, Plus, Trash2, Eye, EyeOff, Copy, Check, Github } from 'lucide-react'; import { Key, Plus, Trash2, Eye, EyeOff, Copy, Check, Github } from 'lucide-react';
import { authenticatedFetch } from '../utils/api';
function ApiKeysSettings() { function ApiKeysSettings() {
const [apiKeys, setApiKeys] = useState([]); const [apiKeys, setApiKeys] = useState([]);
@@ -23,19 +24,14 @@ function ApiKeysSettings() {
const fetchData = async () => { const fetchData = async () => {
try { try {
setLoading(true); setLoading(true);
const token = localStorage.getItem('auth-token');
// Fetch API keys // Fetch API keys
const apiKeysRes = await fetch('/api/settings/api-keys', { const apiKeysRes = await authenticatedFetch('/api/settings/api-keys');
headers: { 'Authorization': `Bearer ${token}` }
});
const apiKeysData = await apiKeysRes.json(); const apiKeysData = await apiKeysRes.json();
setApiKeys(apiKeysData.apiKeys || []); setApiKeys(apiKeysData.apiKeys || []);
// Fetch GitHub tokens // Fetch GitHub tokens
const githubRes = await fetch('/api/settings/credentials?type=github_token', { const githubRes = await authenticatedFetch('/api/settings/credentials?type=github_token');
headers: { 'Authorization': `Bearer ${token}` }
});
const githubData = await githubRes.json(); const githubData = await githubRes.json();
setGithubTokens(githubData.credentials || []); setGithubTokens(githubData.credentials || []);
} catch (error) { } catch (error) {
@@ -49,13 +45,8 @@ function ApiKeysSettings() {
if (!newKeyName.trim()) return; if (!newKeyName.trim()) return;
try { try {
const token = localStorage.getItem('auth-token'); const res = await authenticatedFetch('/api/settings/api-keys', {
const res = await fetch('/api/settings/api-keys', {
method: 'POST', method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ keyName: newKeyName }) body: JSON.stringify({ keyName: newKeyName })
}); });
@@ -75,10 +66,8 @@ function ApiKeysSettings() {
if (!confirm('Are you sure you want to delete this API key?')) return; if (!confirm('Are you sure you want to delete this API key?')) return;
try { try {
const token = localStorage.getItem('auth-token'); await authenticatedFetch(`/api/settings/api-keys/${keyId}`, {
await fetch(`/api/settings/api-keys/${keyId}`, { method: 'DELETE'
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
}); });
fetchData(); fetchData();
} catch (error) { } catch (error) {
@@ -88,13 +77,8 @@ function ApiKeysSettings() {
const toggleApiKey = async (keyId, isActive) => { const toggleApiKey = async (keyId, isActive) => {
try { try {
const token = localStorage.getItem('auth-token'); await authenticatedFetch(`/api/settings/api-keys/${keyId}/toggle`, {
await fetch(`/api/settings/api-keys/${keyId}/toggle`, {
method: 'PATCH', method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ isActive: !isActive }) body: JSON.stringify({ isActive: !isActive })
}); });
fetchData(); fetchData();
@@ -107,13 +91,8 @@ function ApiKeysSettings() {
if (!newTokenName.trim() || !newGithubToken.trim()) return; if (!newTokenName.trim() || !newGithubToken.trim()) return;
try { try {
const token = localStorage.getItem('auth-token'); const res = await authenticatedFetch('/api/settings/credentials', {
const res = await fetch('/api/settings/credentials', {
method: 'POST', method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ body: JSON.stringify({
credentialName: newTokenName, credentialName: newTokenName,
credentialType: 'github_token', credentialType: 'github_token',
@@ -137,10 +116,8 @@ function ApiKeysSettings() {
if (!confirm('Are you sure you want to delete this GitHub token?')) return; if (!confirm('Are you sure you want to delete this GitHub token?')) return;
try { try {
const token = localStorage.getItem('auth-token'); await authenticatedFetch(`/api/settings/credentials/${tokenId}`, {
await fetch(`/api/settings/credentials/${tokenId}`, { method: 'DELETE'
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
}); });
fetchData(); fetchData();
} catch (error) { } catch (error) {
@@ -150,13 +127,8 @@ function ApiKeysSettings() {
const toggleGithubToken = async (tokenId, isActive) => { const toggleGithubToken = async (tokenId, isActive) => {
try { try {
const token = localStorage.getItem('auth-token'); await authenticatedFetch(`/api/settings/credentials/${tokenId}/toggle`, {
await fetch(`/api/settings/credentials/${tokenId}/toggle`, {
method: 'PATCH', method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ isActive: !isActive }) body: JSON.stringify({ isActive: !isActive })
}); });
fetchData(); fetchData();

View File

@@ -1728,11 +1728,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
// Load Cursor default model from config // Load Cursor default model from config
useEffect(() => { useEffect(() => {
if (provider === 'cursor') { if (provider === 'cursor') {
fetch('/api/cursor/config', { authenticatedFetch('/api/cursor/config')
headers: {
'Authorization': `Bearer ${localStorage.getItem('auth-token')}`
}
})
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data.success && data.config?.model?.modelId) { if (data.success && data.config?.model?.modelId) {
@@ -3752,15 +3748,9 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
}); });
try { try {
const token = safeLocalStorage.getItem('auth-token'); const response = await authenticatedFetch(`/api/projects/${selectedProject.name}/upload-images`, {
const headers = {};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`/api/projects/${selectedProject.name}/upload-images`, {
method: 'POST', method: 'POST',
headers: headers, headers: {}, // Let browser set Content-Type for FormData
body: formData body: formData
}); });

View File

@@ -4,6 +4,7 @@ import { Input } from './ui/input';
import { Key, Plus, Trash2, Eye, EyeOff, Copy, Check, Github, ExternalLink } from 'lucide-react'; import { Key, Plus, Trash2, Eye, EyeOff, Copy, Check, Github, ExternalLink } from 'lucide-react';
import { useVersionCheck } from '../hooks/useVersionCheck'; import { useVersionCheck } from '../hooks/useVersionCheck';
import { version } from '../../package.json'; import { version } from '../../package.json';
import { authenticatedFetch } from '../utils/api';
function CredentialsSettings() { function CredentialsSettings() {
const [apiKeys, setApiKeys] = useState([]); const [apiKeys, setApiKeys] = useState([]);
@@ -29,19 +30,14 @@ function CredentialsSettings() {
const fetchData = async () => { const fetchData = async () => {
try { try {
setLoading(true); setLoading(true);
const token = localStorage.getItem('auth-token');
// Fetch API keys // Fetch API keys
const apiKeysRes = await fetch('/api/settings/api-keys', { const apiKeysRes = await authenticatedFetch('/api/settings/api-keys');
headers: { 'Authorization': `Bearer ${token}` }
});
const apiKeysData = await apiKeysRes.json(); const apiKeysData = await apiKeysRes.json();
setApiKeys(apiKeysData.apiKeys || []); setApiKeys(apiKeysData.apiKeys || []);
// Fetch GitHub credentials only // Fetch GitHub credentials only
const credentialsRes = await fetch('/api/settings/credentials?type=github_token', { const credentialsRes = await authenticatedFetch('/api/settings/credentials?type=github_token');
headers: { 'Authorization': `Bearer ${token}` }
});
const credentialsData = await credentialsRes.json(); const credentialsData = await credentialsRes.json();
setGithubCredentials(credentialsData.credentials || []); setGithubCredentials(credentialsData.credentials || []);
} catch (error) { } catch (error) {
@@ -55,13 +51,8 @@ function CredentialsSettings() {
if (!newKeyName.trim()) return; if (!newKeyName.trim()) return;
try { try {
const token = localStorage.getItem('auth-token'); const res = await authenticatedFetch('/api/settings/api-keys', {
const res = await fetch('/api/settings/api-keys', {
method: 'POST', method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ keyName: newKeyName }) body: JSON.stringify({ keyName: newKeyName })
}); });
@@ -81,10 +72,8 @@ function CredentialsSettings() {
if (!confirm('Are you sure you want to delete this API key?')) return; if (!confirm('Are you sure you want to delete this API key?')) return;
try { try {
const token = localStorage.getItem('auth-token'); await authenticatedFetch(`/api/settings/api-keys/${keyId}`, {
await fetch(`/api/settings/api-keys/${keyId}`, { method: 'DELETE'
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
}); });
fetchData(); fetchData();
} catch (error) { } catch (error) {
@@ -94,13 +83,8 @@ function CredentialsSettings() {
const toggleApiKey = async (keyId, isActive) => { const toggleApiKey = async (keyId, isActive) => {
try { try {
const token = localStorage.getItem('auth-token'); await authenticatedFetch(`/api/settings/api-keys/${keyId}/toggle`, {
await fetch(`/api/settings/api-keys/${keyId}/toggle`, {
method: 'PATCH', method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ isActive: !isActive }) body: JSON.stringify({ isActive: !isActive })
}); });
fetchData(); fetchData();
@@ -113,13 +97,8 @@ function CredentialsSettings() {
if (!newGithubName.trim() || !newGithubToken.trim()) return; if (!newGithubName.trim() || !newGithubToken.trim()) return;
try { try {
const token = localStorage.getItem('auth-token'); const res = await authenticatedFetch('/api/settings/credentials', {
const res = await fetch('/api/settings/credentials', {
method: 'POST', method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ body: JSON.stringify({
credentialName: newGithubName, credentialName: newGithubName,
credentialType: 'github_token', credentialType: 'github_token',
@@ -145,10 +124,8 @@ function CredentialsSettings() {
if (!confirm('Are you sure you want to delete this GitHub token?')) return; if (!confirm('Are you sure you want to delete this GitHub token?')) return;
try { try {
const token = localStorage.getItem('auth-token'); await authenticatedFetch(`/api/settings/credentials/${credentialId}`, {
await fetch(`/api/settings/credentials/${credentialId}`, { method: 'DELETE'
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
}); });
fetchData(); fetchData();
} catch (error) { } catch (error) {
@@ -158,13 +135,8 @@ function CredentialsSettings() {
const toggleGithubCredential = async (credentialId, isActive) => { const toggleGithubCredential = async (credentialId, isActive) => {
try { try {
const token = localStorage.getItem('auth-token'); await authenticatedFetch(`/api/settings/credentials/${credentialId}/toggle`, {
await fetch(`/api/settings/credentials/${credentialId}/toggle`, {
method: 'PATCH', method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ isActive: !isActive }) body: JSON.stringify({ isActive: !isActive })
}); });
fetchData(); fetchData();

View File

@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { authenticatedFetch } from '../utils/api';
function ImageViewer({ file, onClose }) { function ImageViewer({ file, onClose }) {
const imagePath = `/api/projects/${file.projectName}/files/content?path=${encodeURIComponent(file.path)}`; const imagePath = `/api/projects/${file.projectName}/files/content?path=${encodeURIComponent(file.path)}`;
@@ -18,16 +19,7 @@ function ImageViewer({ file, onClose }) {
setError(null); setError(null);
setImageUrl(null); setImageUrl(null);
const token = localStorage.getItem('auth-token'); const response = await authenticatedFetch(imagePath, {
if (!token) {
setError('Missing authentication token');
return;
}
const response = await fetch(imagePath, {
headers: {
'Authorization': `Bearer ${token}`
},
signal: controller.signal signal: controller.signal
}); });

View File

@@ -9,6 +9,7 @@ import ClaudeLogo from './ClaudeLogo';
import CursorLogo from './CursorLogo'; import CursorLogo from './CursorLogo';
import CredentialsSettings from './CredentialsSettings'; import CredentialsSettings from './CredentialsSettings';
import LoginModal from './LoginModal'; import LoginModal from './LoginModal';
import { authenticatedFetch } from '../utils/api';
function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) { function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const { isDarkMode, toggleDarkMode } = useTheme(); const { isDarkMode, toggleDarkMode } = useTheme();
@@ -135,14 +136,8 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
// Fetch Cursor MCP servers // Fetch Cursor MCP servers
const fetchCursorMcpServers = async () => { const fetchCursorMcpServers = async () => {
try { try {
const token = localStorage.getItem('auth-token'); const response = await authenticatedFetch('/api/cursor/mcp');
const response = await fetch('/api/cursor/mcp', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
setCursorMcpServers(data.servers || []); setCursorMcpServers(data.servers || []);
@@ -157,16 +152,9 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
// MCP API functions // MCP API functions
const fetchMcpServers = async () => { const fetchMcpServers = async () => {
try { try {
const token = localStorage.getItem('auth-token');
// Try to read directly from config files for complete details // Try to read directly from config files for complete details
const configResponse = await fetch('/api/mcp/config/read', { const configResponse = await authenticatedFetch('/api/mcp/config/read');
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (configResponse.ok) { if (configResponse.ok) {
const configData = await configResponse.json(); const configData = await configResponse.json();
if (configData.success && configData.servers) { if (configData.success && configData.servers) {
@@ -174,15 +162,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
return; return;
} }
} }
// Fallback to Claude CLI // Fallback to Claude CLI
const cliResponse = await fetch('/api/mcp/cli/list', { const cliResponse = await authenticatedFetch('/api/mcp/cli/list');
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (cliResponse.ok) { if (cliResponse.ok) {
const cliData = await cliResponse.json(); const cliData = await cliResponse.json();
if (cliData.success && cliData.servers) { if (cliData.success && cliData.servers) {
@@ -207,15 +190,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
return; return;
} }
} }
// Final fallback to direct config reading // Final fallback to direct config reading
const response = await fetch('/api/mcp/servers?scope=user', { const response = await authenticatedFetch('/api/mcp/servers?scope=user');
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
setMcpServers(data.servers || []); setMcpServers(data.servers || []);
@@ -229,20 +207,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const saveMcpServer = async (serverData) => { const saveMcpServer = async (serverData) => {
try { try {
const token = localStorage.getItem('auth-token');
if (editingMcpServer) { if (editingMcpServer) {
// For editing, remove old server and add new one // For editing, remove old server and add new one
await deleteMcpServer(editingMcpServer.id, 'user'); await deleteMcpServer(editingMcpServer.id, 'user');
} }
// Use Claude CLI to add the server // Use Claude CLI to add the server
const response = await fetch('/api/mcp/cli/add', { const response = await authenticatedFetch('/api/mcp/cli/add', {
method: 'POST', method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ body: JSON.stringify({
name: serverData.name, name: serverData.name,
type: serverData.type, type: serverData.type,
@@ -255,7 +227,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
env: serverData.config?.env || {} env: serverData.config?.env || {}
}) })
}); });
if (response.ok) { if (response.ok) {
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
@@ -276,17 +248,11 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const deleteMcpServer = async (serverId, scope = 'user') => { const deleteMcpServer = async (serverId, scope = 'user') => {
try { try {
const token = localStorage.getItem('auth-token');
// Use Claude CLI to remove the server with proper scope // Use Claude CLI to remove the server with proper scope
const response = await fetch(`/api/mcp/cli/remove/${serverId}?scope=${scope}`, { const response = await authenticatedFetch(`/api/mcp/cli/remove/${serverId}?scope=${scope}`, {
method: 'DELETE', method: 'DELETE'
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}); });
if (response.ok) { if (response.ok) {
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
@@ -307,15 +273,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const testMcpServer = async (serverId, scope = 'user') => { const testMcpServer = async (serverId, scope = 'user') => {
try { try {
const token = localStorage.getItem('auth-token'); const response = await authenticatedFetch(`/api/mcp/servers/${serverId}/test?scope=${scope}`, {
const response = await fetch(`/api/mcp/servers/${serverId}/test?scope=${scope}`, { method: 'POST'
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}); });
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
return data.testResult; return data.testResult;
@@ -332,15 +293,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const discoverMcpTools = async (serverId, scope = 'user') => { const discoverMcpTools = async (serverId, scope = 'user') => {
try { try {
const token = localStorage.getItem('auth-token'); const response = await authenticatedFetch(`/api/mcp/servers/${serverId}/tools?scope=${scope}`, {
const response = await fetch(`/api/mcp/servers/${serverId}/tools?scope=${scope}`, { method: 'POST'
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}); });
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
return data.toolsResult; return data.toolsResult;
@@ -441,13 +397,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const checkClaudeAuthStatus = async () => { const checkClaudeAuthStatus = async () => {
try { try {
const token = localStorage.getItem('auth-token'); const response = await authenticatedFetch('/api/cli/claude/status');
const response = await fetch('/api/cli/claude/status', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
@@ -478,13 +428,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const checkCursorAuthStatus = async () => { const checkCursorAuthStatus = async () => {
try { try {
const token = localStorage.getItem('auth-token'); const response = await authenticatedFetch('/api/cli/cursor/status');
const response = await fetch('/api/cli/cursor/status', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
@@ -647,13 +591,8 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
try { try {
if (mcpFormData.importMode === 'json') { if (mcpFormData.importMode === 'json') {
// Use JSON import endpoint // Use JSON import endpoint
const token = localStorage.getItem('auth-token'); const response = await authenticatedFetch('/api/mcp/cli/add-json', {
const response = await fetch('/api/mcp/cli/add-json', {
method: 'POST', method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ body: JSON.stringify({
name: mcpFormData.name, name: mcpFormData.name,
jsonConfig: mcpFormData.jsonInput, jsonConfig: mcpFormData.jsonInput,
@@ -661,7 +600,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
projectPath: mcpFormData.projectPath projectPath: mcpFormData.projectPath
}) })
}); });
if (response.ok) { if (response.ok) {
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {

View File

@@ -1,15 +1,16 @@
// Utility function for authenticated API calls // Utility function for authenticated API calls
export const authenticatedFetch = (url, options = {}) => { export const authenticatedFetch = (url, options = {}) => {
const isPlatform = import.meta.env.VITE_IS_PLATFORM === 'true';
const token = localStorage.getItem('auth-token'); const token = localStorage.getItem('auth-token');
const defaultHeaders = { const defaultHeaders = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}; };
if (token) { if (!isPlatform && token) {
defaultHeaders['Authorization'] = `Bearer ${token}`; defaultHeaders['Authorization'] = `Bearer ${token}`;
} }
return fetch(url, { return fetch(url, {
...options, ...options,
headers: { headers: {