Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,13 @@
#API server
PORT=3001
#Frontend port
VITE_PORT=5173
VITE_PORT=5173

# =============================================================================
# AUTHENTICATION CONFIGURATION
# =============================================================================

# Disable authentication entirely (default: false)
# When set to true, all features become accessible without login
# WARNING: Only use in secure, private environments
AUTH_DISABLED=false
13 changes: 12 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import authRoutes from './routes/auth.js';
import mcpRoutes from './routes/mcp.js';
import cursorRoutes from './routes/cursor.js';
import { initializeDatabase } from './database/db.js';
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
import { validateApiKey, authenticateToken, authenticateWebSocket, isAuthDisabled } from './middleware/auth.js';

// File system watcher for projects folder
let projectsWatcher = null;
Expand Down Expand Up @@ -143,6 +143,17 @@ const wss = new WebSocketServer({
verifyClient: (info) => {
console.log('WebSocket connection attempt to:', info.req.url);

// Skip authentication if AUTH_DISABLED is true
if (isAuthDisabled()) {
// Store mock user info for compatibility
info.req.user = {
userId: 1,
username: 'admin'
};
console.log('✅ WebSocket authentication bypassed (AUTH_DISABLED=true)');
return true;
}

// Extract token from query parameters or headers
const url = new URL(info.req.url, 'http://localhost');
const token = url.searchParams.get('token') ||
Expand Down
25 changes: 25 additions & 0 deletions server/middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { userDb } from '../database/db.js';
// Get JWT secret from environment or use default (for development)
const JWT_SECRET = process.env.JWT_SECRET || 'claude-ui-dev-secret-change-in-production';

// Check if authentication is disabled
const isAuthDisabled = () => {
return process.env.AUTH_DISABLED === 'true';
};

// Optional API key middleware
const validateApiKey = (req, res, next) => {
// Skip API key validation if not configured
Expand All @@ -20,6 +25,16 @@ const validateApiKey = (req, res, next) => {

// JWT authentication middleware
const authenticateToken = async (req, res, next) => {
// Skip authentication if AUTH_DISABLED is true
if (isAuthDisabled()) {
// Create a mock user object for compatibility
req.user = {
id: 1,
username: 'admin'
};
return next();
}

const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN

Expand Down Expand Up @@ -58,6 +73,15 @@ const generateToken = (user) => {

// WebSocket authentication function
const authenticateWebSocket = (token) => {
// Skip authentication if AUTH_DISABLED is true
if (isAuthDisabled()) {
// Return a mock user object for compatibility
return {
userId: 1,
username: 'admin'
};
}

if (!token) {
return null;
}
Expand All @@ -76,5 +100,6 @@ export {
authenticateToken,
generateToken,
authenticateWebSocket,
isAuthDisabled,
JWT_SECRET
};
30 changes: 27 additions & 3 deletions server/routes/auth.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import express from 'express';
import bcrypt from 'bcrypt';
import { userDb, db } from '../database/db.js';
import { generateToken, authenticateToken } from '../middleware/auth.js';
import { generateToken, authenticateToken, isAuthDisabled } from '../middleware/auth.js';

const router = express.Router();

// Check auth status and setup requirements
router.get('/status', async (req, res) => {
try {
// If authentication is disabled, return authenticated status
if (isAuthDisabled()) {
res.json({
needsSetup: false,
isAuthenticated: true,
authDisabled: true
});
return;
}

const hasUsers = await userDb.hasUsers();
res.json({
needsSetup: !hasUsers,
isAuthenticated: false // Will be overridden by frontend if token exists
isAuthenticated: false, // Will be overridden by frontend if token exists
authDisabled: false
});
} catch (error) {
console.error('Auth status error:', error);
Expand Down Expand Up @@ -120,8 +131,21 @@ router.post('/login', async (req, res) => {

// Get current user (protected route)
router.get('/user', authenticateToken, (req, res) => {
// If authentication is disabled, return mock user
if (isAuthDisabled()) {
res.json({
user: {
id: 1,
username: 'admin'
},
authDisabled: true
});
return;
}

res.json({
user: req.user
user: req.user,
authDisabled: false
});
});

Expand Down
7 changes: 6 additions & 1 deletion src/components/ProtectedRoute.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ const LoadingScreen = () => (
);

const ProtectedRoute = ({ children }) => {
const { user, isLoading, needsSetup } = useAuth();
const { user, isLoading, needsSetup, authDisabled } = useAuth();

if (isLoading) {
return <LoadingScreen />;
}

// If authentication is disabled, allow access immediately
if (authDisabled) {
return children;
}

if (needsSetup) {
return <SetupForm />;
}
Expand Down
14 changes: 14 additions & 0 deletions src/contexts/AuthContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const AuthContext = createContext({
logout: () => {},
isLoading: true,
needsSetup: false,
authDisabled: false,
error: null
});

Expand All @@ -25,6 +26,7 @@ export const AuthProvider = ({ children }) => {
const [token, setToken] = useState(localStorage.getItem('auth-token'));
const [isLoading, setIsLoading] = useState(true);
const [needsSetup, setNeedsSetup] = useState(false);
const [authDisabled, setAuthDisabled] = useState(false);
const [error, setError] = useState(null);

// Check authentication status on mount
Expand All @@ -41,6 +43,17 @@ export const AuthProvider = ({ children }) => {
const statusResponse = await api.auth.status();
const statusData = await statusResponse.json();

// Handle authentication disabled mode
if (statusData.authDisabled) {
setAuthDisabled(true);
setUser({ id: 1, username: 'admin' });
setNeedsSetup(false);
// Create a dummy token for frontend compatibility
setToken('auth-disabled-token');
setIsLoading(false);
return;
}

if (statusData.needsSetup) {
setNeedsSetup(true);
setIsLoading(false);
Expand Down Expand Up @@ -147,6 +160,7 @@ export const AuthProvider = ({ children }) => {
logout,
isLoading,
needsSetup,
authDisabled,
error
};

Expand Down
2 changes: 1 addition & 1 deletion src/utils/api.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Utility function for authenticated API calls
export const authenticatedFetch = (url, options = {}) => {
const token = localStorage.getItem('auth-token');
const token = localStorage.getItem('auth-token') || 'auth-disabled-token';

const defaultHeaders = {
'Content-Type': 'application/json',
Expand Down
22 changes: 20 additions & 2 deletions src/utils/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,31 @@ export function useWebSocket() {

const connect = async () => {
try {
// Check if authentication is disabled by checking auth status
let authDisabled = false;
let token = localStorage.getItem('auth-token');

try {
const statusResponse = await fetch('/api/auth/status');
if (statusResponse.ok) {
const statusData = await statusResponse.json();
authDisabled = statusData.authDisabled;
}
} catch (error) {
console.warn('Could not check auth status:', error);
}

// Get authentication token
const token = localStorage.getItem('auth-token');
if (!token) {
if (!authDisabled && !token) {
console.warn('No authentication token found for WebSocket connection');
return;
}

// Use dummy token if authentication is disabled
if (authDisabled && !token) {
token = 'auth-disabled-token';
}

// Fetch server configuration to get the correct WebSocket URL
let wsBaseUrl;
try {
Expand Down