- 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
107 lines
3.3 KiB
JavaScript
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}`);
|
|
}); |