@@ -33,6 +33,9 @@ const fetch = require('node-fetch');
33
33
const { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache } = require ( './projects' ) ;
34
34
const { spawnClaude, abortClaudeSession } = require ( './claude-cli' ) ;
35
35
const gitRoutes = require ( './routes/git' ) ;
36
+ const authRoutes = require ( './routes/auth' ) ;
37
+ const { initializeDatabase } = require ( './database/db' ) ;
38
+ const { validateApiKey, authenticateToken, authenticateWebSocket } = require ( './middleware/auth' ) ;
36
39
37
40
// File system watcher for projects folder
38
41
let projectsWatcher = null ;
@@ -142,19 +145,43 @@ const wss = new WebSocketServer({
142
145
server,
143
146
verifyClient : ( info ) => {
144
147
console . log ( 'WebSocket connection attempt to:' , info . req . url ) ;
145
- return true ; // Accept all connections for now
148
+
149
+ // Extract token from query parameters or headers
150
+ const url = new URL ( info . req . url , 'http://localhost' ) ;
151
+ const token = url . searchParams . get ( 'token' ) ||
152
+ info . req . headers . authorization ?. split ( ' ' ) [ 1 ] ;
153
+
154
+ // Verify token
155
+ const user = authenticateWebSocket ( token ) ;
156
+ if ( ! user ) {
157
+ console . log ( '❌ WebSocket authentication failed' ) ;
158
+ return false ;
159
+ }
160
+
161
+ // Store user info in the request for later use
162
+ info . req . user = user ;
163
+ console . log ( '✅ WebSocket authenticated for user:' , user . username ) ;
164
+ return true ;
146
165
}
147
166
} ) ;
148
167
149
168
app . use ( cors ( ) ) ;
150
169
app . use ( express . json ( ) ) ;
151
- app . use ( express . static ( path . join ( __dirname , '../dist' ) ) ) ;
152
170
153
- // Git API Routes
154
- app . use ( '/api/git ' , gitRoutes ) ;
171
+ // Optional API key validation (if configured)
172
+ app . use ( '/api' , validateApiKey ) ;
155
173
156
- // API Routes
157
- app . get ( '/api/config' , ( req , res ) => {
174
+ // Authentication routes (public)
175
+ app . use ( '/api/auth' , authRoutes ) ;
176
+
177
+ // Git API Routes (protected)
178
+ app . use ( '/api/git' , authenticateToken , gitRoutes ) ;
179
+
180
+ // Static files served after API routes
181
+ app . use ( express . static ( path . join ( __dirname , '../dist' ) ) ) ;
182
+
183
+ // API Routes (protected)
184
+ app . get ( '/api/config' , authenticateToken , ( req , res ) => {
158
185
// Always use the server's actual IP and port for WebSocket connections
159
186
const serverIP = getServerIP ( ) ;
160
187
const host = `${ serverIP } :${ PORT } ` ;
@@ -168,7 +195,7 @@ app.get('/api/config', (req, res) => {
168
195
} ) ;
169
196
} ) ;
170
197
171
- app . get ( '/api/projects' , async ( req , res ) => {
198
+ app . get ( '/api/projects' , authenticateToken , async ( req , res ) => {
172
199
try {
173
200
const projects = await getProjects ( ) ;
174
201
res . json ( projects ) ;
@@ -177,7 +204,7 @@ app.get('/api/projects', async (req, res) => {
177
204
}
178
205
} ) ;
179
206
180
- app . get ( '/api/projects/:projectName/sessions' , async ( req , res ) => {
207
+ app . get ( '/api/projects/:projectName/sessions' , authenticateToken , async ( req , res ) => {
181
208
try {
182
209
const { limit = 5 , offset = 0 } = req . query ;
183
210
const result = await getSessions ( req . params . projectName , parseInt ( limit ) , parseInt ( offset ) ) ;
@@ -188,7 +215,7 @@ app.get('/api/projects/:projectName/sessions', async (req, res) => {
188
215
} ) ;
189
216
190
217
// Get messages for a specific session
191
- app . get ( '/api/projects/:projectName/sessions/:sessionId/messages' , async ( req , res ) => {
218
+ app . get ( '/api/projects/:projectName/sessions/:sessionId/messages' , authenticateToken , async ( req , res ) => {
192
219
try {
193
220
const { projectName, sessionId } = req . params ;
194
221
const messages = await getSessionMessages ( projectName , sessionId ) ;
@@ -199,7 +226,7 @@ app.get('/api/projects/:projectName/sessions/:sessionId/messages', async (req, r
199
226
} ) ;
200
227
201
228
// Rename project endpoint
202
- app . put ( '/api/projects/:projectName/rename' , async ( req , res ) => {
229
+ app . put ( '/api/projects/:projectName/rename' , authenticateToken , async ( req , res ) => {
203
230
try {
204
231
const { displayName } = req . body ;
205
232
await renameProject ( req . params . projectName , displayName ) ;
@@ -210,7 +237,7 @@ app.put('/api/projects/:projectName/rename', async (req, res) => {
210
237
} ) ;
211
238
212
239
// Delete session endpoint
213
- app . delete ( '/api/projects/:projectName/sessions/:sessionId' , async ( req , res ) => {
240
+ app . delete ( '/api/projects/:projectName/sessions/:sessionId' , authenticateToken , async ( req , res ) => {
214
241
try {
215
242
const { projectName, sessionId } = req . params ;
216
243
await deleteSession ( projectName , sessionId ) ;
@@ -221,7 +248,7 @@ app.delete('/api/projects/:projectName/sessions/:sessionId', async (req, res) =>
221
248
} ) ;
222
249
223
250
// Delete project endpoint (only if empty)
224
- app . delete ( '/api/projects/:projectName' , async ( req , res ) => {
251
+ app . delete ( '/api/projects/:projectName' , authenticateToken , async ( req , res ) => {
225
252
try {
226
253
const { projectName } = req . params ;
227
254
await deleteProject ( projectName ) ;
@@ -232,7 +259,7 @@ app.delete('/api/projects/:projectName', async (req, res) => {
232
259
} ) ;
233
260
234
261
// Create project endpoint
235
- app . post ( '/api/projects/create' , async ( req , res ) => {
262
+ app . post ( '/api/projects/create' , authenticateToken , async ( req , res ) => {
236
263
try {
237
264
const { path : projectPath } = req . body ;
238
265
@@ -249,7 +276,7 @@ app.post('/api/projects/create', async (req, res) => {
249
276
} ) ;
250
277
251
278
// Read file content endpoint
252
- app . get ( '/api/projects/:projectName/file' , async ( req , res ) => {
279
+ app . get ( '/api/projects/:projectName/file' , authenticateToken , async ( req , res ) => {
253
280
try {
254
281
const { projectName } = req . params ;
255
282
const { filePath } = req . query ;
@@ -278,7 +305,7 @@ app.get('/api/projects/:projectName/file', async (req, res) => {
278
305
} ) ;
279
306
280
307
// Serve binary file content endpoint (for images, etc.)
281
- app . get ( '/api/projects/:projectName/files/content' , async ( req , res ) => {
308
+ app . get ( '/api/projects/:projectName/files/content' , authenticateToken , async ( req , res ) => {
282
309
try {
283
310
const { projectName } = req . params ;
284
311
const { path : filePath } = req . query ;
@@ -324,7 +351,7 @@ app.get('/api/projects/:projectName/files/content', async (req, res) => {
324
351
} ) ;
325
352
326
353
// Save file content endpoint
327
- app . put ( '/api/projects/:projectName/file' , async ( req , res ) => {
354
+ app . put ( '/api/projects/:projectName/file' , authenticateToken , async ( req , res ) => {
328
355
try {
329
356
const { projectName } = req . params ;
330
357
const { filePath, content } = req . body ;
@@ -371,7 +398,7 @@ app.put('/api/projects/:projectName/file', async (req, res) => {
371
398
}
372
399
} ) ;
373
400
374
- app . get ( '/api/projects/:projectName/files' , async ( req , res ) => {
401
+ app . get ( '/api/projects/:projectName/files' , authenticateToken , async ( req , res ) => {
375
402
try {
376
403
377
404
const fs = require ( 'fs' ) . promises ;
@@ -409,12 +436,16 @@ wss.on('connection', (ws, request) => {
409
436
const url = request . url ;
410
437
console . log ( '🔗 Client connected to:' , url ) ;
411
438
412
- if ( url === '/shell' ) {
439
+ // Parse URL to get pathname without query parameters
440
+ const urlObj = new URL ( url , 'http://localhost' ) ;
441
+ const pathname = urlObj . pathname ;
442
+
443
+ if ( pathname === '/shell' ) {
413
444
handleShellConnection ( ws ) ;
414
- } else if ( url === '/ws' ) {
445
+ } else if ( pathname === '/ws' ) {
415
446
handleChatConnection ( ws ) ;
416
447
} else {
417
- console . log ( '❌ Unknown WebSocket path:' , url ) ;
448
+ console . log ( '❌ Unknown WebSocket path:' , pathname ) ;
418
449
ws . close ( ) ;
419
450
}
420
451
} ) ;
@@ -629,7 +660,7 @@ function handleShellConnection(ws) {
629
660
} ) ;
630
661
}
631
662
// Audio transcription endpoint
632
- app . post ( '/api/transcribe' , async ( req , res ) => {
663
+ app . post ( '/api/transcribe' , authenticateToken , async ( req , res ) => {
633
664
try {
634
665
const multer = require ( 'multer' ) ;
635
666
const upload = multer ( { storage : multer . memoryStorage ( ) } ) ;
@@ -835,9 +866,24 @@ async function getFileTree(dirPath, maxDepth = 3, currentDepth = 0, showHidden =
835
866
}
836
867
837
868
const PORT = process . env . PORT || 3000 ;
838
- server . listen ( PORT , '0.0.0.0' , ( ) => {
839
- console . log ( `Claude Code UI server running on http://0.0.0.0:${ PORT } ` ) ;
840
-
841
- // Start watching the projects folder for changes
842
- setupProjectsWatcher ( ) ;
843
- } ) ;
869
+
870
+ // Initialize database and start server
871
+ async function startServer ( ) {
872
+ try {
873
+ // Initialize authentication database
874
+ await initializeDatabase ( ) ;
875
+ console . log ( '✅ Database initialized successfully' ) ;
876
+
877
+ server . listen ( PORT , '0.0.0.0' , ( ) => {
878
+ console . log ( `Claude Code UI server running on http://0.0.0.0:${ PORT } ` ) ;
879
+
880
+ // Start watching the projects folder for changes
881
+ setupProjectsWatcher ( ) ;
882
+ } ) ;
883
+ } catch ( error ) {
884
+ console . error ( '❌ Failed to start server:' , error ) ;
885
+ process . exit ( 1 ) ;
886
+ }
887
+ }
888
+
889
+ startServer ( ) ;
0 commit comments