Initial commit: LuHost - Luanti Server Management Web Interface
A modern web interface for Luanti (Minetest) server management with ContentDB integration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
262
routes/auth.js
Normal file
262
routes/auth.js
Normal file
@@ -0,0 +1,262 @@
|
||||
const express = require('express');
|
||||
const AuthManager = require('../utils/auth');
|
||||
const { redirectIfAuthenticated } = require('../middleware/auth');
|
||||
const securityLogger = require('../utils/security-logger');
|
||||
|
||||
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.'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('auth/register', {
|
||||
title: 'Setup Administrator Account',
|
||||
isFirstUser: isFirstUser,
|
||||
currentPage: 'register'
|
||||
});
|
||||
} 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 } = req.body;
|
||||
|
||||
// Validate inputs
|
||||
if (!username || !password || !confirmPassword) {
|
||||
return res.render('auth/register', {
|
||||
title: 'Setup Administrator Account',
|
||||
error: 'All fields are required',
|
||||
isFirstUser: true,
|
||||
currentPage: 'register',
|
||||
formData: { username }
|
||||
});
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
return res.render('auth/register', {
|
||||
title: 'Setup Administrator Account',
|
||||
error: 'Passwords do not match',
|
||||
isFirstUser: true,
|
||||
currentPage: 'register',
|
||||
formData: { username }
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
res.render('auth/register', {
|
||||
title: 'Register',
|
||||
error: error.message,
|
||||
isFirstUser: await authManager.isFirstUser(),
|
||||
currentPage: 'register',
|
||||
formData: {
|
||||
username: req.body.username
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
Reference in New Issue
Block a user