Add View As (impersonation) feature for Admin users
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
This commit is contained in:
@@ -13,7 +13,8 @@ function requireAuth() {
|
||||
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT s.*, u.id as user_id, u.username, u.email, u.role, u.user_group, u.bu_teams, u.is_active
|
||||
`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()`,
|
||||
@@ -30,8 +31,8 @@ function requireAuth() {
|
||||
return res.status(401).json({ error: 'Account is disabled' });
|
||||
}
|
||||
|
||||
// Attach user to request
|
||||
req.user = {
|
||||
// Store the real admin identity (always the session owner)
|
||||
req.realUser = {
|
||||
id: session.user_id,
|
||||
username: session.username,
|
||||
email: session.email,
|
||||
@@ -40,6 +41,35 @@ function requireAuth() {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user