mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-01-29 21:07:33 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41ef84c283 | ||
|
|
53224e47b6 | ||
|
|
bbb51dbf99 |
@@ -70,7 +70,7 @@ import mcpUtilsRoutes from './routes/mcp-utils.js';
|
|||||||
import commandsRoutes from './routes/commands.js';
|
import commandsRoutes from './routes/commands.js';
|
||||||
import settingsRoutes from './routes/settings.js';
|
import settingsRoutes from './routes/settings.js';
|
||||||
import agentRoutes from './routes/agent.js';
|
import agentRoutes from './routes/agent.js';
|
||||||
import projectsRoutes, { FORBIDDEN_PATHS } from './routes/projects.js';
|
import projectsRoutes, { WORKSPACES_ROOT, validateWorkspacePath } from './routes/projects.js';
|
||||||
import cliAuthRoutes from './routes/cli-auth.js';
|
import cliAuthRoutes from './routes/cli-auth.js';
|
||||||
import userRoutes from './routes/user.js';
|
import userRoutes from './routes/user.js';
|
||||||
import codexRoutes from './routes/codex.js';
|
import codexRoutes from './routes/codex.js';
|
||||||
@@ -484,22 +484,42 @@ app.post('/api/projects/create', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const expandWorkspacePath = (inputPath) => {
|
||||||
|
if (!inputPath) return inputPath;
|
||||||
|
if (inputPath === '~') {
|
||||||
|
return WORKSPACES_ROOT;
|
||||||
|
}
|
||||||
|
if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
|
||||||
|
return path.join(WORKSPACES_ROOT, inputPath.slice(2));
|
||||||
|
}
|
||||||
|
return inputPath;
|
||||||
|
};
|
||||||
|
|
||||||
// Browse filesystem endpoint for project suggestions - uses existing getFileTree
|
// Browse filesystem endpoint for project suggestions - uses existing getFileTree
|
||||||
app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { path: dirPath } = req.query;
|
const { path: dirPath } = req.query;
|
||||||
|
|
||||||
|
console.log('[API] Browse filesystem request for path:', dirPath);
|
||||||
|
console.log('[API] WORKSPACES_ROOT is:', WORKSPACES_ROOT);
|
||||||
// Default to home directory if no path provided
|
// Default to home directory if no path provided
|
||||||
const homeDir = os.homedir();
|
const defaultRoot = WORKSPACES_ROOT;
|
||||||
let targetPath = dirPath ? dirPath.replace('~', homeDir) : homeDir;
|
let targetPath = dirPath ? expandWorkspacePath(dirPath) : defaultRoot;
|
||||||
|
|
||||||
// Resolve and normalize the path
|
// Resolve and normalize the path
|
||||||
targetPath = path.resolve(targetPath);
|
targetPath = path.resolve(targetPath);
|
||||||
|
|
||||||
|
// Security check - ensure path is within allowed workspace root
|
||||||
|
const validation = await validateWorkspacePath(targetPath);
|
||||||
|
if (!validation.valid) {
|
||||||
|
return res.status(403).json({ error: validation.error });
|
||||||
|
}
|
||||||
|
const resolvedPath = validation.resolvedPath || targetPath;
|
||||||
|
|
||||||
// Security check - ensure path is accessible
|
// Security check - ensure path is accessible
|
||||||
try {
|
try {
|
||||||
await fs.promises.access(targetPath);
|
await fs.promises.access(resolvedPath);
|
||||||
const stats = await fs.promises.stat(targetPath);
|
const stats = await fs.promises.stat(resolvedPath);
|
||||||
|
|
||||||
if (!stats.isDirectory()) {
|
if (!stats.isDirectory()) {
|
||||||
return res.status(400).json({ error: 'Path is not a directory' });
|
return res.status(400).json({ error: 'Path is not a directory' });
|
||||||
@@ -509,7 +529,7 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use existing getFileTree function with shallow depth (only direct children)
|
// Use existing getFileTree function with shallow depth (only direct children)
|
||||||
const fileTree = await getFileTree(targetPath, 1, 0, false); // maxDepth=1, showHidden=false
|
const fileTree = await getFileTree(resolvedPath, 1, 0, false); // maxDepth=1, showHidden=false
|
||||||
|
|
||||||
// Filter only directories and format for suggestions
|
// Filter only directories and format for suggestions
|
||||||
const directories = fileTree
|
const directories = fileTree
|
||||||
@@ -529,7 +549,13 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
|||||||
|
|
||||||
// Add common directories if browsing home directory
|
// Add common directories if browsing home directory
|
||||||
const suggestions = [];
|
const suggestions = [];
|
||||||
if (targetPath === homeDir) {
|
let resolvedWorkspaceRoot = defaultRoot;
|
||||||
|
try {
|
||||||
|
resolvedWorkspaceRoot = await fsPromises.realpath(defaultRoot);
|
||||||
|
} catch (error) {
|
||||||
|
// Use default root as-is if realpath fails
|
||||||
|
}
|
||||||
|
if (resolvedPath === resolvedWorkspaceRoot) {
|
||||||
const commonDirs = ['Desktop', 'Documents', 'Projects', 'Development', 'Dev', 'Code', 'workspace'];
|
const commonDirs = ['Desktop', 'Documents', 'Projects', 'Development', 'Dev', 'Code', 'workspace'];
|
||||||
const existingCommon = directories.filter(dir => commonDirs.includes(dir.name));
|
const existingCommon = directories.filter(dir => commonDirs.includes(dir.name));
|
||||||
const otherDirs = directories.filter(dir => !commonDirs.includes(dir.name));
|
const otherDirs = directories.filter(dir => !commonDirs.includes(dir.name));
|
||||||
@@ -540,7 +566,7 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
path: targetPath,
|
path: resolvedPath,
|
||||||
suggestions: suggestions
|
suggestions: suggestions
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -556,22 +582,13 @@ app.post('/api/create-folder', authenticateToken, async (req, res) => {
|
|||||||
if (!folderPath) {
|
if (!folderPath) {
|
||||||
return res.status(400).json({ error: 'Path is required' });
|
return res.status(400).json({ error: 'Path is required' });
|
||||||
}
|
}
|
||||||
const homeDir = os.homedir();
|
const expandedPath = expandWorkspacePath(folderPath);
|
||||||
const targetPath = path.resolve(folderPath.replace('~', homeDir));
|
const resolvedInput = path.resolve(expandedPath);
|
||||||
const normalizedPath = path.normalize(targetPath);
|
const validation = await validateWorkspacePath(resolvedInput);
|
||||||
const comparePath = normalizedPath.toLowerCase();
|
if (!validation.valid) {
|
||||||
const forbiddenLower = FORBIDDEN_PATHS.map(p => p.toLowerCase());
|
return res.status(403).json({ error: validation.error });
|
||||||
if (forbiddenLower.includes(comparePath) || comparePath === '/') {
|
|
||||||
return res.status(403).json({ error: 'Cannot create folders in system directories' });
|
|
||||||
}
|
|
||||||
for (const forbidden of forbiddenLower) {
|
|
||||||
if (comparePath.startsWith(forbidden + path.sep)) {
|
|
||||||
if (forbidden === '/var' && (comparePath.startsWith('/var/tmp') || comparePath.startsWith('/var/folders'))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return res.status(403).json({ error: `Cannot create folders in system directory: ${forbidden}` });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const targetPath = validation.resolvedPath || resolvedInput;
|
||||||
const parentDir = path.dirname(targetPath);
|
const parentDir = path.dirname(targetPath);
|
||||||
try {
|
try {
|
||||||
await fs.promises.access(parentDir);
|
await fs.promises.access(parentDir);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function sanitizeGitError(message, token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Configure allowed workspace root (defaults to user's home directory)
|
// Configure allowed workspace root (defaults to user's home directory)
|
||||||
const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || os.homedir();
|
export const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || os.homedir();
|
||||||
|
|
||||||
// System-critical paths that should never be used as workspace directories
|
// System-critical paths that should never be used as workspace directories
|
||||||
export const FORBIDDEN_PATHS = [
|
export const FORBIDDEN_PATHS = [
|
||||||
@@ -48,7 +48,7 @@ export const FORBIDDEN_PATHS = [
|
|||||||
* @param {string} requestedPath - The path to validate
|
* @param {string} requestedPath - The path to validate
|
||||||
* @returns {Promise<{valid: boolean, resolvedPath?: string, error?: string}>}
|
* @returns {Promise<{valid: boolean, resolvedPath?: string, error?: string}>}
|
||||||
*/
|
*/
|
||||||
async function validateWorkspacePath(requestedPath) {
|
export async function validateWorkspacePath(requestedPath) {
|
||||||
try {
|
try {
|
||||||
// Resolve to absolute path
|
// Resolve to absolute path
|
||||||
let absolutePath = path.resolve(requestedPath);
|
let absolutePath = path.resolve(requestedPath);
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ function LoginModal({
|
|||||||
|
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case 'claude':
|
case 'claude':
|
||||||
return isAuthenticated ? 'claude setup-token --dangerously-skip-permissions' : 'claude /login --dangerously-skip-permissions';
|
return isAuthenticated ? 'claude setup-token --dangerously-skip-permissions' : 'claude /exit --dangerously-skip-permissions --no-session-persistence';
|
||||||
case 'cursor':
|
case 'cursor':
|
||||||
return 'cursor-agent login';
|
return 'cursor-agent login';
|
||||||
case 'codex':
|
case 'codex':
|
||||||
return isPlatform ? 'codex login --device-auth' : 'codex login';
|
return isPlatform ? 'codex login --device-auth' : 'codex login';
|
||||||
default:
|
default:
|
||||||
return isAuthenticated ? 'claude setup-token --dangerously-skip-permissions' : 'claude /login --dangerously-skip-permissions';
|
return isAuthenticated ? 'claude setup-token --dangerously-skip-permissions' : 'claude /exit --dangerously-skip-permissions --no-session-persistence';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ const Onboarding = ({ onComplete }) => {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
const [activeLoginProvider, setActiveLoginProvider] = useState(null);
|
const [activeLoginProvider, setActiveLoginProvider] = useState(null);
|
||||||
const [selectedProject] = useState({ name: 'default', fullPath: '' });
|
const isPlatform = import.meta.env.VITE_IS_PLATFORM === 'true';
|
||||||
|
const [selectedProject] = useState({ name: 'default', fullPath: isPlatform ? '/workspace' : '' });
|
||||||
|
|
||||||
const [claudeAuthStatus, setClaudeAuthStatus] = useState({
|
const [claudeAuthStatus, setClaudeAuthStatus] = useState({
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(data.error || t('projectWizard.errors.failedToCreate'));
|
throw new Error(data.details || data.error || t('projectWizard.errors.failedToCreate'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onProjectCreated) {
|
if (onProjectCreated) {
|
||||||
|
|||||||
Reference in New Issue
Block a user