Files
bookhoard/backend/server.js
T
john-okeefe 55c42f1f99 feat: Add comprehensive backend validation, toast notifications, and Tokyo Night theme
- Backend: Add server-side validation with go-playground/validator/v10
- Frontend: Add toast notifications for API errors with @zerodevx/svelte-toast
- UI: Complete Tokyo Night theme redesign with modern animations
- Docs: Update COMPLETE_DOCUMENTATION.md and README.md with all enhancements
- Validation: Email format, password strength, and input sanitization
- UX: Real-time error feedback, loading states, and responsive design
2026-01-21 20:01:18 -05:00

107 lines
3.3 KiB
JavaScript

const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const bodyParser = require('body-parser');
const app = express();
const PORT = 8080;
const JWT_SECRET = 'your-secret-key'; // In production, use env var
app.use(cors());
app.use(bodyParser.json());
// In-memory user store (for demo purposes)
const users = [];
// Password validation regex
const passwordRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
// Middleware to verify token
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Access token required' });
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ error: 'Invalid token' });
req.user = user;
next();
});
}
// Auth routes
app.post('/api/auth/register', async (req, res) => {
const { email, username, password } = req.body;
if (!email || !username || !password) {
return res.status(400).json({ error: 'All fields are required' });
}
// Check if password meets requirements
if (!passwordRegex.test(password)) {
return res.status(400).json({
error: 'Password must be at least 8 characters and include at least one uppercase letter, one number, and one symbol'
});
}
// Check if user already exists
const existingUser = users.find(u => u.email === email || u.username === username);
if (existingUser) {
return res.status(400).json({ error: 'User already exists' });
}
try {
const passwordHash = await bcrypt.hash(password, 10);
const user = { id: users.length + 1, email, username, passwordHash };
users.push(user);
const token = jwt.sign({ id: user.id, email: user.email, username: user.username }, JWT_SECRET);
res.json({ token, user: { id: user.id, email: user.email, username: user.username } });
} catch (error) {
res.status(500).json({ error: 'Registration failed' });
}
});
app.post('/api/auth/login', async (req, res) => {
const { login, password } = req.body; // login can be email or username
if (!login || !password) {
return res.status(400).json({ error: 'Username and password are required' });
}
const user = users.find(u => u.email === login || u.username === login);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
try {
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ id: user.id, email: user.email, username: user.username }, JWT_SECRET);
res.json({ token, user: { id: user.id, email: user.email, username: user.username } });
} catch (error) {
res.status(500).json({ error: 'Login failed' });
}
});
app.get('/api/auth/profile', authenticateToken, (req, res) => {
res.json(req.user);
});
// Stub routes for ebooks (to avoid errors)
app.get('/api/ebooks', authenticateToken, (req, res) => {
res.json([]);
});
app.get('/api/ebooks/:id', authenticateToken, (req, res) => {
res.json({ id: req.params.id, title: 'Stub' });
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});