refactor: implement centralized auth middleware and standardize cross-subdomain session management

This commit is contained in:
Giuseppe Raffa
2026-04-21 22:17:48 +02:00
parent 69012029ad
commit 924c2b5367
22 changed files with 670 additions and 530 deletions

View File

@@ -1,10 +1,10 @@
const express = require('express');
const nunjucks = require('nunjucks');
const path = require('path');
const jwt = require('jsonwebtoken');
const parser = require('cookie-parser');
const { requireAuthHtml } = require('./middlewares/auth');
const app = express();
const PORT = process.env.PORT;
@@ -47,39 +47,9 @@ const renderPage = (page, extra = {}) => (req, res) => {
res.render(page, {current_path: req.path, ...extra})
}
// Middleware di autenticazione per le pagine
app.use((req, res, next) => {
if (req.path === '/health' || req.path.startsWith('/static')) {
return next();
}
const authBase = process.env.AUTH_LOGIN_URL || 'http://localhost:3006/login';
// Costruisci l'URL di redirect-back: protocollo + host + path originale
const proto = req.protocol;
const host = req.get('host');
const redirectBack = `${proto}://${host}${req.originalUrl}`;
const loginUrl = `${authBase}?redirect=${encodeURIComponent(redirectBack)}`;
const token = req.cookies && req.cookies.auth_token;
if (!token) {
return res.redirect(loginUrl);
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });
req.user = payload;
next();
} catch (err) {
const clearOptions = { httpOnly: true, sameSite: 'lax' };
if (process.env.COOKIE_DOMAIN) {
clearOptions.domain = process.env.COOKIE_DOMAIN;
}
res.clearCookie('auth_token', clearOptions);
return res.redirect(loginUrl);
}
});
// Middleware di autenticazione per tutte le pagine protette
// Le route /health e /static sono già gestite sopra
app.use(requireAuthHtml);
app.get('/dashboard', renderPage('dashboard'));
app.get('/live', renderPage('live', {

View File

@@ -0,0 +1,74 @@
/**
* Middleware di autenticazione condiviso per la console.
* Usa il JWT in cookie `auth_token` (condiviso tra i sottodomini via COOKIE_DOMAIN = .mebboat.it)
* oppure il header `Authorization: Bearer <token>`.
*
* Il JWT viene firmato da auth.mebboat.it con JWT_SECRET; questo servizio lo verifica
* localmente usando lo stesso secret. Nessuna chiamata di rete richiesta.
*/
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
const AUTH_LOGIN_URL = process.env.AUTH_LOGIN_URL || 'http://localhost:3006/login';
const COOKIE_DOMAIN = process.env.COOKIE_DOMAIN || undefined;
function extractToken(req) {
const header = req.headers.authorization;
const bearer = header && header.startsWith('Bearer ') ? header.slice(7) : null;
return (req.cookies && req.cookies.auth_token) || bearer || null;
}
function verifyToken(token) {
if (!token || typeof token !== 'string' || token.length > 2048) return null;
try {
const p = jwt.verify(token, SECRET, { algorithms: ['HS256'] });
return {
user_id: p.sub,
username: p.username,
session_id: p.session_id,
iat: p.iat,
exp: p.exp
};
} catch {
return null;
}
}
function clearAuthCookie(res) {
const opts = { httpOnly: true, sameSite: 'lax', path: '/' };
if (COOKIE_DOMAIN) opts.domain = COOKIE_DOMAIN;
res.clearCookie('auth_token', opts);
}
function loginRedirectUrl(req) {
const back = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
return `${AUTH_LOGIN_URL}?redirect=${encodeURIComponent(back)}`;
}
/**
* Pagine HTML: su fallimento redirige all'auth service (SSO).
* Il redirect-back URL viene costruito automaticamente dalla richiesta corrente.
*/
function requireAuthHtml(req, res, next) {
const token = extractToken(req);
const user = verifyToken(token);
if (!user) {
if (token) clearAuthCookie(res);
return res.redirect(loginRedirectUrl(req));
}
req.user = user;
next();
}
/**
* API JSON: su fallimento risponde 401.
*/
function requireAuthApi(req, res, next) {
const user = verifyToken(extractToken(req));
if (!user) return res.status(401).json({ error: 'unauthorized' });
req.user = user;
next();
}
module.exports = { requireAuthHtml, requireAuthApi, clearAuthCookie, verifyToken, extractToken };