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:
@@ -9,7 +9,7 @@
|
||||
// - JSX that uses the imported lucide-react icons (X, Plus, Edit2, Trash2, etc.)
|
||||
// - The ConfirmModal integration for delete/group-change confirmations
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { X, Plus, Edit2, Trash2, Loader, AlertCircle, CheckCircle, User, Mail, Shield } from 'lucide-react';
|
||||
import { X, Plus, Edit2, Trash2, Loader, AlertCircle, CheckCircle, User, Mail, Shield, Eye } from 'lucide-react';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import ConfirmModal from './ConfirmModal';
|
||||
|
||||
@@ -170,7 +170,7 @@ const styles = {
|
||||
|
||||
|
||||
export default function UserManagement({ onClose }) {
|
||||
const { user: currentUser } = useAuth();
|
||||
const { user: currentUser, startImpersonation } = useAuth();
|
||||
const [users, setUsers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
@@ -665,6 +665,20 @@ export default function UserManagement({ onClose }) {
|
||||
</td>
|
||||
<td style={styles.tdRight}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '0.25rem' }}>
|
||||
{currentUser.group === 'Admin' && user.id !== currentUser.id && user.group !== 'Admin' && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
const result = await startImpersonation(user.id);
|
||||
if (result.success) window.location.reload();
|
||||
}}
|
||||
style={styles.actionBtn}
|
||||
title={`View as ${user.username}`}
|
||||
onMouseEnter={e => { e.currentTarget.style.color = '#D97706'; e.currentTarget.style.background = 'rgba(217,119,6,0.1)'; }}
|
||||
onMouseLeave={e => { e.currentTarget.style.color = '#94A3B8'; e.currentTarget.style.background = 'none'; }}
|
||||
>
|
||||
<Eye style={{ width: '1rem', height: '1rem' }} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleEdit(user)}
|
||||
style={styles.actionBtn}
|
||||
|
||||
Reference in New Issue
Block a user