Files
OLD-server-architecture/auth/src/index.js

156 lines
6.0 KiB
JavaScript

const express = require('express');
const nunjucks = require('nunjucks');
const path = require('path');
const parser = require('cookie-parser');
const database = require('./storage/database');
const app = express();
const PORT = process.env.PORT || 3006;
const version = process.env.VERSION;
const vBuild = process.env.VERSION_BUILD;
const vState = process.env.VERSION_STATE;
// ─── 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');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; style-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'"
);
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 ───────────────────────────────────────────────────
const staticFolder = path.join(__dirname, 'static');
app.use('/static', express.static(staticFolder));
app.use('/api/static', express.static(staticFolder));
// ─── NUNJUCKS TEMPLATES ─────────────────────────────────────────────
const templatesFolder = path.join(__dirname, 'templates');
nunjucks.configure(templatesFolder, {
autoescape: true,
express: app,
noCache: true,
watch: false
});
app.set('views', templatesFolder);
app.set('view engine', 'html');
// ─── 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');
app.use('/api/auth/login', authRateLimit);
app.use('/api/auth/register', authRateLimit);
app.use('/api/auth', authRoutes);
// 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');
const redisConnected = await redisHelper.checkRedis();
res.json({
status: dbConnected && redisConnected ? "ok" : "degraded",
service: "auth",
database: dbConnected ? "connected" : "disconnected",
redis: redisConnected ? "connected" : "disconnected",
version: version,
build_number: vBuild,
version_state: vState
});
});
// ─── 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('[ERROR]', err.message, '| code:', err.code);
res.status(500).json({ error: 'Errore interno del server' });
});
// ─── STARTUP ────────────────────────────────────────────────────────
async function start() {
await database.initDb();
app.listen(PORT, '0.0.0.0', () => {
console.log(`[AUTH] Started on port ${PORT}`);
});
}
start().catch(err => {
console.error('[AUTH] Failed to start:', err);
process.exit(1);
});