refactor: reorganize auth routes by separating view and API endpoints. Added some layer security to the most private apis

This commit is contained in:
Giuseppe Raffa
2026-04-04 19:21:12 +02:00
parent 07673586c2
commit a07abbfeea
6 changed files with 265 additions and 41 deletions

View File

@@ -12,15 +12,76 @@ const version = process.env.VERSION;
const vBuild = process.env.VERSION_BUILD;
const vState = process.env.VERSION_STATE;
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// ─── SICUREZZA GLOBALE ──────────────────────────────────────────────
// Trust proxy (necessario dietro Nginx/Traefik per avere il vero IP del client)
app.set('trust proxy', 1);
// Rate Limiter in-memory (protezione DoS base, senza dipendenze esterne)
const rateLimitStore = new Map();
const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minuto
const RATE_LIMIT_MAX = 100; // max richieste per finestra
const RATE_LIMIT_AUTH_MAX = 10; // max tentativi login/register per finestra
// Pulizia periodica dello store
setInterval(() => {
const now = Date.now();
for (const [key, entry] of rateLimitStore) {
if (now - entry.start > RATE_LIMIT_WINDOW_MS) {
rateLimitStore.delete(key);
}
}
}, RATE_LIMIT_WINDOW_MS);
function createRateLimiter(maxRequests) {
return (req, res, next) => {
const key = `${req.ip}:${maxRequests}`;
const now = Date.now();
const entry = rateLimitStore.get(key);
if (!entry || now - entry.start > RATE_LIMIT_WINDOW_MS) {
rateLimitStore.set(key, { count: 1, start: now });
return next();
}
entry.count++;
if (entry.count > maxRequests) {
res.set('Retry-After', Math.ceil((RATE_LIMIT_WINDOW_MS - (now - entry.start)) / 1000));
return res.status(429).json({ error: 'Troppe richieste, riprova più tardi' });
}
next();
};
}
const globalRateLimit = createRateLimiter(RATE_LIMIT_MAX);
const authRateLimit = createRateLimiter(RATE_LIMIT_AUTH_MAX);
// Security headers (equivalente leggero di Helmet, senza dipendenze)
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '0'); // Disabilitato a favore di CSP
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
// Rimuovi header che rivelano info sul server
res.removeHeader('X-Powered-By');
next();
});
// Rate limit globale
app.use(globalRateLimit);
// Limiti dimensione body per prevenire payload eccessivi
app.use(express.json({ limit: '16kb' }));
app.use(express.urlencoded({ extended: true, limit: '16kb' }));
app.use(parser());
// Static files
// ─── STATIC FILES ───────────────────────────────────────────────────
const staticFolder = path.join(__dirname, 'static');
app.use('/static', express.static(staticFolder));
// Nunjucks templates
// ─── NUNJUCKS TEMPLATES ─────────────────────────────────────────────
const templatesFolder = path.join(__dirname, 'templates');
nunjucks.configure(templatesFolder, {
autoescape: true,
@@ -31,19 +92,24 @@ nunjucks.configure(templatesFolder, {
app.set('views', templatesFolder);
app.set('view engine', 'html');
// Routes
// ─── ROUTES ─────────────────────────────────────────────────────────
// Views (pagine HTML)
app.use('/', require('./routes/views/auth'));
app.use('/sessions', require('./routes/views/sessions'));
app.use('/user', require('./routes/views/user'));
// API - Auth (con rate limit più stretto su login/register)
const authRoutes = require('./routes/auth');
const usersRoutes = require('./routes/users');
const sessionsRoutes = require('./routes/sessions');
app.use('/api/auth/login', authRateLimit);
app.use('/api/auth/register', authRateLimit);
app.use('/api/auth', authRoutes);
app.use('/api/users', usersRoutes);
app.use('/api/sessions', sessionsRoutes);
// API - Risorse
app.use('/api/users', require('./routes/users'));
app.use('/api/sessions', require('./routes/sessions'));
// ─── HEALTH CHECK ───────────────────────────────────────────────────
app.get('/health', async (req, res) => {
const dbConnected = await database.checkPostgres();
const redisHelper = require('./storage/redis');
@@ -60,7 +126,18 @@ app.get('/health', async (req, res) => {
});
});
// Startup
// ─── 404 HANDLER ────────────────────────────────────────────────────
app.use((req, res) => {
res.status(404).json({ error: 'Risorsa non trovata' });
});
// ─── ERROR HANDLER GLOBALE ──────────────────────────────────────────
app.use((err, req, res, _next) => {
console.error('[AUTH] Errore non gestito:', err);
res.status(500).json({ error: 'Errore interno del server' });
});
// ─── STARTUP ────────────────────────────────────────────────────────
async function start() {
await database.initDb();
app.listen(PORT, '0.0.0.0', () => {