2 Impersonation
jlightner edited this page 2026-04-04 06:40:26 +00:00

Admin Impersonation

Admins can view the site as any creator to debug permission issues, verify consent settings, and test the creator experience. Added in M020/S04.

Overview

Admin clicks "View As" on /admin/users
        │
        ▼
POST /api/v1/admin/impersonate/{user_id}
        │
        ├─ Verify caller is admin
        ├─ Create impersonation JWT (1h expiry, original_user_id claim)
        ├─ Log to impersonation_log table
        │
        ▼
Frontend swaps JWT, stashes admin token in sessionStorage
        │
        ▼
Amber banner: "Viewing as {Creator Name} — Exit"
All API calls use target user's identity
Write endpoints blocked by reject_impersonation guard
        │
        ▼
Click "Exit" → POST /api/v1/admin/impersonate/stop
        │
        ├─ Log stop action to impersonation_log
        ├─ Restore admin token from sessionStorage
        │
        ▼
Back to normal admin session

Security Model

  • Read-only: Write endpoints are guarded by reject_impersonation FastAPI dependency. Returns 403 if the current token has original_user_id claim
  • Short-lived: Impersonation tokens expire in 1 hour (vs 24h for normal tokens)
  • Transparent: The impersonation JWT uses sub=target_user_id, so existing get_current_user loads the target user transparently. No special-casing in endpoint handlers
  • Admin-only: Only users with admin role can call the impersonate endpoint

Audit Trail

Every impersonation action is logged to the impersonation_log table:

  • admin_user_id — Who initiated
  • target_user_id — Who is being impersonated
  • action — "start" or "stop"
  • ip_address — Client IP
  • created_at — Timestamp

Indexes on admin_user_id and target_user_id for efficient querying.

Token Structure

Normal JWT:

{
  "sub": "user-uuid",
  "role": "admin",
  "iat": 1234567890,
  "exp": 1234654290
}

Impersonation JWT:

{
  "sub": "target-user-uuid",
  "role": "creator",
  "original_user_id": "admin-user-uuid",
  "iat": 1234567890,
  "exp": 1234571490
}

The original_user_id claim is what reject_impersonation checks.

API Endpoints

  • GET /api/v1/admin/users — List all users (admin only)
  • POST /api/v1/admin/impersonate/{user_id} — Start impersonation, returns new JWT
  • POST /api/v1/admin/impersonate/stop — Stop impersonation, logs action

Frontend Integration

AuthContext Extensions

  • startImpersonation(userId, writeMode?) — Calls impersonate API (with optional write_mode body), saves current admin token to sessionStorage, swaps to impersonation token
  • exitImpersonation() — Calls stop API, restores admin token from sessionStorage
  • user.impersonating (boolean) — True when viewing as another user
  • isWriteMode (boolean) — True when impersonation session has write access (M021/S07)

ImpersonationBanner

Fixed bar at page top when impersonating. Two visual states (M021/S07):

  • Amber "👁 Viewing as {name}" — read-only mode
  • Red "✏️ Editing as {name}" — write mode (adds body.impersonating-write class)

Uses data-write-mode attribute for CSS color switching.

ConfirmModal (M021/S07)

Reusable confirmation dialog component used for "Edit As" write-mode impersonation:

  • Backdrop + Escape/backdrop-click dismiss
  • variant prop: warning (amber) or danger (red confirm button)
  • Uses data-variant attribute for CSS variant styling

AdminUsers Page

Route: /admin/users. Table of all users with two action buttons per creator:

  • "View As" — starts read-only impersonation (no confirmation modal)
  • "Edit As" — opens ConfirmModal with danger variant before starting write-mode impersonation

AdminAuditLog Page (M021/S07)

Route: /admin/audit-log. Six-column table:

  • Date/Time, Admin, Target User, Action, Write Mode, IP Address
  • Badge styling via data-variant / data-action / data-write-mode attributes
  • Loading/error/empty states, Previous/Next pagination
  • Linked from AdminDropdown after "Users"

Key Files

  • backend/auth.pycreate_impersonation_token(), reject_impersonation dependency
  • backend/routers/admin.py — Impersonate/stop endpoints, user list
  • backend/models.pyImpersonationLog model
  • alembic/versions/018_add_impersonation_log.py — Migration
  • frontend/src/context/AuthContext.tsx — Impersonation state management
  • frontend/src/components/ImpersonationBanner.tsx — Amber warning banner
  • frontend/src/pages/AdminUsers.tsx — User management page

Database

Table: impersonation_log (Migration 018)

  • id UUID PK
  • admin_user_id UUID FK → users
  • target_user_id UUID FK → users
  • action VARCHAR (start/stop)
  • ip_address VARCHAR
  • created_at TIMESTAMP

See also: Authentication, API-Surface