93 lines
3.1 KiB
JavaScript
93 lines
3.1 KiB
JavaScript
const rateLimit = require('express-rate-limit');
|
|
const zod = require('zod');
|
|
const { AppError } = require('../utils/errors');
|
|
const { register, verifyLogin } = require('../services/authService');
|
|
|
|
// Validation schemas
|
|
const registerSchema = zod.strictObject({
|
|
username: zod.string().min(3).max(20).regex(/^[A-Za-z0-9_]+$/),
|
|
password: zod.string().min(8).max(72),
|
|
});
|
|
|
|
const loginSchema = zod.strictObject({
|
|
username: zod.string().min(1).max(20),
|
|
password: zod.string().min(1).max(72),
|
|
});
|
|
|
|
// Rate limiters — temporarily disabled for debugging
|
|
// const registerLimiter = rateLimit({
|
|
// windowMs: 60 * 60 * 1000, // 1 hour
|
|
// max: process.env.NODE_ENV === 'test' ? 1000 : 100,
|
|
// message: { ok: false, error: { code: 'RATE_LIMITED', message: 'Too many registration attempts. Try again later.' } },
|
|
// });
|
|
|
|
// const loginLimiter = rateLimit({
|
|
// windowMs: 15 * 60 * 1000, // 15 minutes
|
|
// max: process.env.NODE_ENV === 'test' ? 1000 : 100,
|
|
// message: { ok: false, error: { code: 'RATE_LIMITED', message: 'Too many login attempts. Try again later.' } },
|
|
// });
|
|
const registerLimiter = (_req, _res, next) => next();
|
|
const loginLimiter = (_req, _res, next) => next();
|
|
|
|
function wrap(fn) {
|
|
return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
|
|
}
|
|
|
|
async function handleRegister(req, res) {
|
|
const parsed = registerSchema.safeParse(req.body);
|
|
if (!parsed.success) {
|
|
throw new AppError('VALIDATION_ERROR', 'Request validation failed.', 400);
|
|
}
|
|
|
|
const { username, password } = parsed.data;
|
|
const user = await register(username, password);
|
|
|
|
// Regenerate session to prevent fixation
|
|
req.session.regenerate((err) => {
|
|
if (err) throw new AppError('INTERNAL_ERROR', 'Session regeneration failed.', 500);
|
|
req.session.userId = user.id;
|
|
res.status(201).json({ ok: true, data: { user: { id: user.id, username: user.username } } });
|
|
});
|
|
}
|
|
|
|
async function handleLogin(req, res) {
|
|
const parsed = loginSchema.safeParse(req.body);
|
|
if (!parsed.success) {
|
|
throw new AppError('VALIDATION_ERROR', 'Request validation failed.', 400);
|
|
}
|
|
|
|
const { username, password } = parsed.data;
|
|
const user = await verifyLogin(username, password);
|
|
|
|
// Regenerate session to prevent fixation
|
|
req.session.regenerate((err) => {
|
|
if (err) throw new AppError('INTERNAL_ERROR', 'Session regeneration failed.', 500);
|
|
req.session.userId = user.id;
|
|
res.json({ ok: true, data: { user: { id: user.id, username: user.username }, hasHero: false } });
|
|
});
|
|
}
|
|
|
|
async function handleLogout(req, res) {
|
|
req.session.destroy((err) => {
|
|
if (err) throw new AppError('INTERNAL_ERROR', 'Logout failed.', 500);
|
|
res.clearCookie('emberfall_session');
|
|
res.json({ ok: true, data: {} });
|
|
});
|
|
}
|
|
|
|
async function handleMe(req, res) {
|
|
const userId = req.session.userId;
|
|
res.json({ ok: true, data: { user: { id: userId, username: req.session.username || '' }, hasHero: false } });
|
|
}
|
|
|
|
module.exports = {
|
|
registerLimiter,
|
|
loginLimiter,
|
|
routes: {
|
|
postRegister: wrap(handleRegister),
|
|
postLogin: wrap(handleLogin),
|
|
postLogout: wrap(handleLogout),
|
|
getMe: wrap(handleMe),
|
|
},
|
|
};
|