Files
Christopher Mayor 0910c966a5 feat: migrate from static config to database and add authentication
- Replaced static hosts.json and staticConfig.ts with SQLite database (Prisma)

- Implemented JWT authentication and Login UI

- Added dynamic API routes for hosts, topology, and settings

- Updated UI components to fetch and manage state dynamically

- Added Settings interface for managing hosts and topology nodes
2026-02-25 14:07:11 -08:00

79 lines
2.9 KiB
TypeScript

import { Router } from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { PrismaClient } from '@prisma/client';
import rateLimit from 'express-rate-limit';
const router = Router();
const prisma = new PrismaClient();
const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret-for-development-only';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Limit each IP to 5 failed login attempts per window
message: { status: 'error', message: 'Too many login attempts, please try again later' },
skipSuccessfulRequests: true,
});
// Check if setup is required (no users exist)
router.get('/auth/status', async (req, res) => {
try {
const userCount = await prisma.user.count();
res.json({ setupRequired: userCount === 0 });
} catch (error) {
res.status(500).json({ status: 'error', message: 'Database error' });
}
});
// Initial Setup (Only works if no users exist)
router.post('/auth/setup', async (req, res) => {
try {
const userCount = await prisma.user.count();
if (userCount > 0) {
return res.status(403).json({ status: 'error', message: 'Setup has already been completed' });
}
const { username, password } = req.body;
if (!username || !password || password.length < 8) {
return res.status(400).json({ status: 'error', message: 'Invalid username or password must be at least 8 characters' });
}
const hashedPassword = await bcrypt.hash(password, 10);
const user = await prisma.user.create({
data: { username, password: hashedPassword },
});
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: '7d' });
res.json({ status: 'success', token, user: { id: user.id, username: user.username } });
} catch (error) {
res.status(500).json({ status: 'error', message: 'Failed to create user' });
}
});
// Login
router.post('/auth/login', loginLimiter, async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ status: 'error', message: 'Username and password required' });
}
const user = await prisma.user.findUnique({ where: { username } });
if (!user) {
return res.status(401).json({ status: 'error', message: 'Invalid credentials' });
}
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
return res.status(401).json({ status: 'error', message: 'Invalid credentials' });
}
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: '7d' });
res.json({ status: 'success', token, user: { id: user.id, username: user.username } });
} catch (error) {
res.status(500).json({ status: 'error', message: 'Login failed' });
}
});
export default router;