refactor: implement centralized auth middleware and standardize cross-subdomain session management
This commit is contained in:
@@ -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', {
|
||||
|
||||
74
console/src/middlewares/auth.js
Normal file
74
console/src/middlewares/auth.js
Normal 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 };
|
||||
Reference in New Issue
Block a user