refactor: reorganize auth routes by separating view and API endpoints. Added some layer security to the most private apis
This commit is contained in:
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user