Allow Admin users to temporarily view the app as another user to verify permissions and team scoping without switching accounts. Backend: - Migration: add impersonate_user_id column to sessions table - requireAuth(): when impersonation is active, override req.user with target user's identity; store real admin identity in req.realUser - POST /api/auth/impersonate: start impersonation (Admin only, cannot impersonate self or other Admins) - POST /api/auth/stop-impersonate: end impersonation, revert to real user - GET /api/auth/me: returns impersonating flag and realUser when active - Audit logging on impersonate start/stop Frontend: - AuthContext: add impersonating, realUser state; startImpersonation() and stopImpersonation() helpers - ImpersonationBanner: fixed amber banner showing target user identity with Exit button - UserManagement: Eye icon button on each non-Admin user row to start View As (visible only to Admin, hidden for self and other Admins) - App.js: mount ImpersonationBanner at top of authenticated view
135 lines
4.7 KiB
JavaScript
135 lines
4.7 KiB
JavaScript
// Authentication Middleware
|
|
const pool = require('../db');
|
|
const { teamToIvanti } = require('../helpers/teams');
|
|
|
|
// Require authenticated user — no parameters needed, pool is imported directly
|
|
function requireAuth() {
|
|
return async (req, res, next) => {
|
|
const sessionId = req.cookies?.session_id;
|
|
|
|
if (!sessionId) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`SELECT s.*, s.impersonate_user_id,
|
|
u.id as user_id, u.username, u.email, u.role, u.user_group, u.bu_teams, u.is_active
|
|
FROM sessions s
|
|
JOIN users u ON s.user_id = u.id
|
|
WHERE s.session_id = $1 AND s.expires_at > NOW()`,
|
|
[sessionId]
|
|
);
|
|
|
|
const session = rows[0];
|
|
|
|
if (!session) {
|
|
return res.status(401).json({ error: 'Session expired or invalid' });
|
|
}
|
|
|
|
if (!session.is_active) {
|
|
return res.status(401).json({ error: 'Account is disabled' });
|
|
}
|
|
|
|
// Store the real admin identity (always the session owner)
|
|
req.realUser = {
|
|
id: session.user_id,
|
|
username: session.username,
|
|
email: session.email,
|
|
role: session.role,
|
|
group: session.user_group,
|
|
teams: session.bu_teams ? session.bu_teams.split(',').filter(Boolean) : []
|
|
};
|
|
|
|
// If impersonating, load the target user's identity
|
|
if (session.impersonate_user_id) {
|
|
const { rows: targetRows } = await pool.query(
|
|
`SELECT id, username, email, role, user_group, bu_teams, is_active FROM users WHERE id = $1`,
|
|
[session.impersonate_user_id]
|
|
);
|
|
const target = targetRows[0];
|
|
|
|
if (target && target.is_active) {
|
|
req.user = {
|
|
id: target.id,
|
|
username: target.username,
|
|
email: target.email,
|
|
role: target.role,
|
|
group: target.user_group,
|
|
teams: target.bu_teams ? target.bu_teams.split(',').filter(Boolean) : []
|
|
};
|
|
req.impersonating = true;
|
|
} else {
|
|
// Target user no longer valid — clear impersonation and use real user
|
|
await pool.query(`UPDATE sessions SET impersonate_user_id = NULL WHERE session_id = $1`, [sessionId]);
|
|
req.user = req.realUser;
|
|
req.impersonating = false;
|
|
}
|
|
} else {
|
|
req.user = req.realUser;
|
|
req.impersonating = false;
|
|
}
|
|
|
|
next();
|
|
} catch (err) {
|
|
console.error('Auth middleware error:', err);
|
|
return res.status(500).json({ error: 'Authentication error' });
|
|
}
|
|
};
|
|
}
|
|
|
|
// Require specific group(s)
|
|
function requireGroup(...allowedGroups) {
|
|
return (req, res, next) => {
|
|
if (!req.user) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
if (!allowedGroups.includes(req.user.group)) {
|
|
return res.status(403).json({
|
|
error: 'Insufficient permissions',
|
|
required: allowedGroups,
|
|
current: req.user.group
|
|
});
|
|
}
|
|
|
|
next();
|
|
};
|
|
}
|
|
|
|
// Require team assignment — enforces team-scoped data access.
|
|
// Admin group bypasses (req.teamScope = null means "no filter").
|
|
// Non-admin users without teams get 403.
|
|
// Non-admin users with teams get req.teamScope = { short: [...], ivanti: [...] }.
|
|
function requireTeam() {
|
|
return (req, res, next) => {
|
|
if (!req.user) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
// Admin bypass — full access to all teams
|
|
if (req.user.group === 'Admin') {
|
|
req.teamScope = null;
|
|
return next();
|
|
}
|
|
|
|
// No teams assigned — block access
|
|
if (!req.user.teams || req.user.teams.length === 0) {
|
|
return res.status(403).json({
|
|
error: 'No team assignment. Contact an administrator to assign BU teams to your account.',
|
|
code: 'NO_TEAM_ASSIGNMENT'
|
|
});
|
|
}
|
|
|
|
// Build scope with both naming conventions
|
|
req.teamScope = {
|
|
short: req.user.teams,
|
|
ivanti: req.user.teams.map(t => teamToIvanti(t))
|
|
};
|
|
|
|
next();
|
|
};
|
|
}
|
|
|
|
module.exports = { requireAuth, requireGroup, requireTeam };
|