Security: - Add session management: users can view/revoke active sessions in Settings - Add password change in Settings (requires current password, HIBP check) - Force logout all sessions on password reset - Fix logout to destroy server-side session (was only clearing cookie) - Add trust proxy for correct client IP in rate limiting and audit logs - Add CORS support for multiple domains (CORS_ORIGINS env var) - Add HIBP breach check endpoint and inline warnings on password fields Audit logging: - Add audit logging to all 24 PHI-handling endpoints across 13 route files - Covers: generation, transcription, TTS, refine, encounters, documents, Nextcloud - All fire-and-forget (no response delay) AI improvements: - Refine now includes original source material (transcript, notes, labs) so AI can reference the full input when modifying output - Add correction tracking (trackAIOutput) to sick visit and well visit tabs - Fix sickvisit missing from encounter save noteIdMap UI fixes: - Non-blocking busy bar for transcription and AI generation (replaces full-screen overlay) - Fix encounter recording: hide record button during recording (was showing two stop buttons) - Fix ROS/PE "All WNL" stacking duplicate event handlers; add Clear buttons - Enlarge AI instructions textarea in Learning Hub CMS Domain: - Primary domain now app.pedshub.com, with scribe.pedshub.com and peds.danvics.com as CORS origins
80 lines
2.7 KiB
JavaScript
80 lines
2.7 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const db = require('../db/database');
|
|
const { hashToken } = require('../utils/sessions');
|
|
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-in-production';
|
|
|
|
async function authMiddleware(req, res, next) {
|
|
var token = null;
|
|
|
|
var authHeader = req.headers.authorization;
|
|
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
token = authHeader.substring(7) || null;
|
|
}
|
|
if (!token && req.cookies && req.cookies.ped_auth) {
|
|
token = req.cookies.ped_auth;
|
|
}
|
|
|
|
if (!token) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
try {
|
|
var decoded = jwt.verify(token, JWT_SECRET);
|
|
var user = await db.get('SELECT id, email, name, role, totp_enabled, disabled FROM users WHERE id = ?', [decoded.userId]);
|
|
|
|
if (!user) {
|
|
return res.status(401).json({ error: 'User not found' });
|
|
}
|
|
|
|
if (user.disabled) {
|
|
return res.status(403).json({ error: 'Account disabled. Contact administrator.' });
|
|
}
|
|
|
|
req.user = user;
|
|
|
|
// Session validation — allows revocation
|
|
try {
|
|
var tHash = hashToken(token);
|
|
var session = await db.get('SELECT id, last_activity FROM user_sessions WHERE token_hash = ?', [tHash]);
|
|
if (!session) {
|
|
// Pre-migration user? If they have no sessions at all, allow through
|
|
var sessCount = await db.get('SELECT COUNT(*) as count FROM user_sessions WHERE user_id = ?', [user.id]);
|
|
if (parseInt(sessCount.count) > 0) {
|
|
return res.status(401).json({ error: 'Session revoked' });
|
|
}
|
|
} else {
|
|
req.sessionId = session.id;
|
|
// Throttled last_activity update — at most once per 5 minutes, fire-and-forget
|
|
var lastAct = new Date(session.last_activity).getTime();
|
|
if (Date.now() - lastAct > 5 * 60 * 1000) {
|
|
db.run('UPDATE user_sessions SET last_activity = NOW() WHERE id = ?', [session.id]).catch(function() {});
|
|
}
|
|
}
|
|
} catch (sessErr) {
|
|
// Fail open — table may not exist yet during migration
|
|
}
|
|
|
|
next();
|
|
} catch (err) {
|
|
return res.status(401).json({ error: 'Invalid or expired token' });
|
|
}
|
|
}
|
|
|
|
// Admin-only middleware — use after authMiddleware
|
|
async function adminMiddleware(req, res, next) {
|
|
if (!req.user || req.user.role !== 'admin') {
|
|
return res.status(403).json({ error: 'Admin access required' });
|
|
}
|
|
next();
|
|
}
|
|
|
|
// Moderator or Admin middleware — for Learning Hub CMS
|
|
async function moderatorMiddleware(req, res, next) {
|
|
if (!req.user || (req.user.role !== 'admin' && req.user.role !== 'moderator')) {
|
|
return res.status(403).json({ error: 'Moderator or admin access required' });
|
|
}
|
|
next();
|
|
}
|
|
|
|
module.exports = { authMiddleware, adminMiddleware, moderatorMiddleware, JWT_SECRET };
|