const express = require('express'); const AuthManager = require('../utils/auth'); const { redirectIfAuthenticated } = require('../middleware/auth'); const securityLogger = require('../utils/security-logger'); const paths = require('../utils/paths'); const appConfig = require('../utils/app-config'); const router = express.Router(); const authManager = new AuthManager(); // Initialize auth manager authManager.initialize().catch(console.error); // Login page router.get('/login', redirectIfAuthenticated, async (req, res) => { try { const isFirstUser = await authManager.isFirstUser(); if (isFirstUser) { // No users exist yet - redirect to registration return res.redirect('/register'); } const redirectUrl = req.query.redirect || '/'; res.render('auth/login', { title: 'Login', redirectUrl: redirectUrl, currentPage: 'login' }); } catch (error) { console.error('Error checking first user on login:', error); const redirectUrl = req.query.redirect || '/'; res.render('auth/login', { title: 'Login', redirectUrl: redirectUrl, currentPage: 'login' }); } }); // Register page (only for first user) router.get('/register', redirectIfAuthenticated, async (req, res) => { try { const isFirstUser = await authManager.isFirstUser(); if (!isFirstUser) { return res.status(403).render('error', { error: 'Registration Not Available', message: 'New accounts can only be created by existing administrators. Please contact an admin to create your account.' }); } // Detect available Luanti data directories const detectedDirectories = paths.detectLuantiDataDirectories(); res.render('auth/register', { title: 'Setup Administrator Account', isFirstUser: isFirstUser, currentPage: 'register', detectedDirectories: detectedDirectories, defaultDataDir: paths.getDefaultDataDirectory() }); } catch (error) { console.error('Error checking first user:', error); res.status(500).render('error', { error: 'Failed to load registration page', message: error.message }); } }); // Process login router.post('/login', redirectIfAuthenticated, async (req, res) => { try { const { username, password, redirect } = req.body; if (!username || !password) { return res.render('auth/login', { title: 'Login', error: 'Username and password are required', redirectUrl: redirect || '/', currentPage: 'login', formData: { username } }); } const user = await authManager.authenticateUser(username, password); // Log successful authentication await securityLogger.logAuthSuccess(req, username); // Create session req.session.user = user; // Redirect to intended page or dashboard const redirectUrl = redirect && redirect !== '/login' ? redirect : '/'; res.redirect(redirectUrl); } catch (error) { console.error('Login error:', error); // Log failed authentication await securityLogger.logAuthFailure(req, username, error.message); res.render('auth/login', { title: 'Login', error: error.message, redirectUrl: req.body.redirect || '/', currentPage: 'login', formData: { username: req.body.username } }); } }); // Process registration (only for first user) router.post('/register', redirectIfAuthenticated, async (req, res) => { try { const isFirstUser = await authManager.isFirstUser(); if (!isFirstUser) { return res.status(403).render('error', { error: 'Registration Not Available', message: 'New accounts can only be created by existing administrators.' }); } const { username, password, confirmPassword, dataDirectory, customDataDirectory } = req.body; // Validate inputs if (!username || !password || !confirmPassword) { const detectedDirectories = paths.detectLuantiDataDirectories(); return res.render('auth/register', { title: 'Setup Administrator Account', error: 'All fields are required', isFirstUser: true, currentPage: 'register', formData: { username }, detectedDirectories: detectedDirectories, defaultDataDir: paths.getDefaultDataDirectory() }); } if (password !== confirmPassword) { const detectedDirectories = paths.detectLuantiDataDirectories(); return res.render('auth/register', { title: 'Setup Administrator Account', error: 'Passwords do not match', isFirstUser: true, currentPage: 'register', formData: { username }, detectedDirectories: detectedDirectories, defaultDataDir: paths.getDefaultDataDirectory() }); } // Handle data directory selection let selectedDataDir = dataDirectory; if (dataDirectory === 'custom' && customDataDirectory) { selectedDataDir = customDataDirectory; } // Validate the selected data directory if (selectedDataDir && selectedDataDir !== 'custom') { try { // Ensure the directory exists or can be created const fs = require('fs'); if (!fs.existsSync(selectedDataDir)) { await fs.promises.mkdir(selectedDataDir, { recursive: true }); } // Save the data directory to app config await appConfig.load(); appConfig.setDataDirectory(selectedDataDir); await appConfig.save(); // Update paths to use the new directory paths.setDataDirectory(selectedDataDir); console.log('Data directory set to:', selectedDataDir); } catch (error) { console.error('Error setting data directory:', error); const detectedDirectories = paths.detectLuantiDataDirectories(); return res.render('auth/register', { title: 'Setup Administrator Account', error: `Invalid data directory: ${error.message}`, isFirstUser: true, currentPage: 'register', formData: { username }, detectedDirectories: detectedDirectories, defaultDataDir: paths.getDefaultDataDirectory() }); } } const user = await authManager.createUser(username, password); // Create session for new user req.session.user = { id: user.id, username: user.username, created_at: user.created_at }; // Redirect to dashboard res.redirect('/?registered=true'); } catch (error) { console.error('Registration error:', error); const detectedDirectories = paths.detectLuantiDataDirectories(); res.render('auth/register', { title: 'Register', error: error.message, isFirstUser: await authManager.isFirstUser(), currentPage: 'register', formData: { username: req.body.username }, detectedDirectories: detectedDirectories, defaultDataDir: paths.getDefaultDataDirectory() }); } }); // Logout router.post('/logout', (req, res) => { req.session.destroy((err) => { if (err) { console.error('Logout error:', err); return res.status(500).json({ error: 'Failed to logout' }); } if (req.headers.accept && req.headers.accept.includes('application/json')) { res.json({ message: 'Logged out successfully' }); } else { res.redirect('/login?message=You have been logged out'); } }); }); // Get logout (for convenience) router.get('/logout', (req, res) => { req.session.destroy((err) => { if (err) { console.error('Logout error:', err); } res.redirect('/login?message=You have been logged out'); }); }); // User profile page router.get('/profile', async (req, res) => { if (!req.session || !req.session.user) { return res.redirect('/login'); } try { const user = await authManager.getUserById(req.session.user.id); if (!user) { req.session.destroy(); return res.redirect('/login?error=User not found'); } res.render('auth/profile', { title: 'Profile', user: user, currentPage: 'profile' }); } catch (error) { console.error('Profile error:', error); res.status(500).render('error', { error: 'Failed to load profile', message: error.message }); } }); // Change password router.post('/change-password', async (req, res) => { if (!req.session || !req.session.user) { return res.status(401).json({ error: 'Authentication required' }); } try { const { currentPassword, newPassword, confirmPassword } = req.body; if (!currentPassword || !newPassword || !confirmPassword) { throw new Error('All fields are required'); } if (newPassword !== confirmPassword) { throw new Error('New passwords do not match'); } await authManager.changePassword(req.session.user.id, currentPassword, newPassword); if (req.headers.accept && req.headers.accept.includes('application/json')) { res.json({ message: 'Password changed successfully' }); } else { res.redirect('/profile?success=Password changed successfully'); } } catch (error) { console.error('Change password error:', error); if (req.headers.accept && req.headers.accept.includes('application/json')) { res.status(400).json({ error: error.message }); } else { res.redirect('/profile?error=' + encodeURIComponent(error.message)); } } }); module.exports = router;