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,6 +1,7 @@
const express = require('express');
const parser = require('cookie-parser');
const jwt = require('jsonwebtoken');
const { requireAuth } = require('./middlewares/auth');
const app = express();
const PORT = process.env.PORT;
@@ -56,33 +57,8 @@ app.get('/health', async (req, res) => {
const paramsSensorRoutes = require('./routes/params.sensor');
app.use('/params/sensor', paramsSensorRoutes);
// Middleware di autenticazione per le API
app.use((req, res, next) => {
if (req.path === '/health' || req.path === '/') return next();
// 1. Service-to-service: x-api-key header
const apiKey = req.headers['x-api-key'];
if (apiKey && apiKey === process.env.INTERNAL_API_KEY) {
req.internal = true;
return next();
}
// 2. User auth: cookie o Authorization header
const token = req.cookies?.auth_token
|| (req.headers.authorization?.startsWith('Bearer ') && req.headers.authorization.slice(7));
if (!token) {
return res.status(401).json({ error: 'Unauthorized: Nessun token di autenticazione fornito' });
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });
req.user = payload;
next();
} catch (err) {
return res.status(401).json({ error: 'Unauthorized: Token non valido o scaduto' });
}
});
// Middleware di autenticazione per tutte le API protette
app.use(requireAuth);
const dataRoutes = require('./routes/data');
app.use('/data', dataRoutes);

View File

@@ -0,0 +1,70 @@
/**
* Middleware di autenticazione per API REST.
* Supporta tre modalità:
* - x-api-key (service-to-service, INTERNAL_API_KEY)
* - cookie auth_token (utenti loggati dal browser, SSO via .mebboat.it)
* - Authorization: Bearer <jwt>
*
* Il JWT viene firmato da auth.mebboat.it con JWT_SECRET e verificato localmente.
* Il cookie è condiviso tra i sottodomini grazie a domain=.mebboat.it
*/
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
const INTERNAL_KEY = process.env.INTERNAL_API_KEY;
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;
}
}
/**
* Accetta utente loggato (cookie/bearer) o chiamata interna (x-api-key).
* Imposta req.user con i dati dell'utente, oppure req.internal = true.
*/
function requireAuth(req, res, next) {
// 1. Service-to-service
const apiKey = req.headers['x-api-key'];
if (apiKey && INTERNAL_KEY && apiKey === INTERNAL_KEY) {
req.internal = true;
return next();
}
// 2. User auth (cookie o Bearer)
const user = verifyToken(extractToken(req));
if (!user) return res.status(401).json({ error: 'unauthorized' });
req.user = user;
next();
}
/**
* Solo service-to-service (x-api-key).
*/
function requireInternal(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (!INTERNAL_KEY || !apiKey || apiKey !== INTERNAL_KEY) {
return res.status(403).json({ error: 'forbidden' });
}
req.internal = true;
next();
}
module.exports = { requireAuth, requireInternal, verifyToken, extractToken };