78 lines
2.5 KiB
JavaScript
78 lines
2.5 KiB
JavaScript
// backend/shared/requireAuth.js
|
|
import jwt from 'jsonwebtoken';
|
|
import pool from '../config/mysqlPool.js';
|
|
|
|
function readSessionCookie(req, cookieName) {
|
|
if (req.cookies && req.cookies[cookieName]) return req.cookies[cookieName];
|
|
const raw = req.headers.cookie || '';
|
|
for (const part of raw.split(';')) {
|
|
const [k, ...rest] = part.trim().split('=');
|
|
if (k === cookieName) return decodeURIComponent(rest.join('='));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function toMs(v) {
|
|
if (v == null) return 0;
|
|
const n = typeof v === 'number' ? v : parseInt(String(v), 10);
|
|
if (!Number.isFinite(n) || Number.isNaN(n)) return 0;
|
|
return n < 1e12 ? n * 1000 : n; // convert seconds to ms if small
|
|
}
|
|
|
|
export async function requireAuth(req, res, next) {
|
|
try {
|
|
const JWT_SECRET = process.env.JWT_SECRET;
|
|
const COOKIE_NAME = process.env.SESSION_COOKIE_NAME || 'aptiva_session';
|
|
const MAX_AGE = Number(process.env.TOKEN_MAX_AGE_MS || 0);
|
|
|
|
if (!JWT_SECRET) {
|
|
console.error('[requireAuth] JWT_SECRET missing');
|
|
return res.status(500).json({ error: 'Server misconfig' });
|
|
}
|
|
|
|
// 1) Grab token
|
|
const authz = req.headers.authorization || '';
|
|
const token = authz.startsWith('Bearer ')
|
|
? authz.slice(7)
|
|
: readSessionCookie(req, COOKIE_NAME);
|
|
|
|
if (!token) return res.status(401).json({ error: 'Auth required' });
|
|
|
|
// 2) Verify
|
|
let payload;
|
|
try { payload = jwt.verify(token, JWT_SECRET); }
|
|
catch { return res.status(401).json({ error: 'Invalid or expired token' }); }
|
|
|
|
const userId = payload.id;
|
|
const iatMs = (payload.iat || 0) * 1000;
|
|
|
|
// 3) Absolute max token age
|
|
if (MAX_AGE && Date.now() - iatMs > MAX_AGE) {
|
|
return res.status(401).json({ error: 'Session expired. Please sign in again.' });
|
|
}
|
|
|
|
// 4) Password change invalidation
|
|
let changedAtMs = 0;
|
|
try {
|
|
const sql = pool.raw || pool;
|
|
const [rows] = await sql.query(
|
|
'SELECT password_changed_at FROM user_auth WHERE user_id = ? ORDER BY id DESC LIMIT 1',
|
|
[userId]
|
|
);
|
|
changedAtMs = toMs(rows?.[0]?.password_changed_at);
|
|
} catch (e) {
|
|
console.warn('[requireAuth] password_changed_at check skipped:', e?.message || e);
|
|
}
|
|
|
|
if (changedAtMs && iatMs < changedAtMs) {
|
|
return res.status(401).json({ error: 'Session invalidated. Please sign in again.' });
|
|
}
|
|
|
|
req.userId = userId;
|
|
return next();
|
|
} catch (e) {
|
|
console.error('[requireAuth]', e?.message || e);
|
|
return res.status(500).json({ error: 'Server error' });
|
|
}
|
|
}
|