Hackorda Docs
Flows

F 03 Admin Impersonation

Status: ๐ŸŸข ready to test Owner: Adilet Last updated: 2026-05-08 Last verified on production: โ€”

Goal

An admin can temporarily browse the app as another user, to debug what the user sees, troubleshoot a bug report, or spot-check a tester's balance. The session is clearly marked with a banner; impersonation is fully reversible without signing out.

Actors

  • Primary โ€” Admin
  • Secondary โ€” The impersonated target (no notification by design; this is a read-only debug tool)

Preconditions

  • Admin is signed in.
  • Target user exists.

Trigger

Admin opens the target user's profile (/app/admin/users/:id) and clicks the "View as user" action.

Happy path โ€” start impersonation

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Admin/app/admin/users/:idClick "View as user"POST /api/admin/impersonate body { targetUserId }INSERT INTO admin_impersonation_logimpersonation cookie setadmin.impersonation_started
2Appevery pageTop banner: "Viewing as ยท End session"โ€”โ€”sidebar reflects target's roleโ€”
3Adminany pageBrowse as target โ€” issues, balance, etc.(whatever they navigate to)โ€”โ€”โ€”

Happy path โ€” end impersonation

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Adminimpersonation bannerClick "End session"DELETE /api/admin/impersonateUPDATE admin_impersonation_log SET ended_at = NOW()impersonation cookie clearedadmin.impersonation_ended
2Appbanner gone, sidebar revertsโ€”โ€”โ€”โ€”โ€”

Acceptance criteria

  • AC-1 โ€” Given the admin clicks "View as user" on a Tester profile, when the page reloads, then the impersonation banner is visible at the top and the sidebar shows the Tester's role-appropriate menu.
  • AC-2 โ€” Given an active impersonation, when the admin navigates to /app/test-cycles, then they see only the cycles the target is a member of (not all cycles).
  • AC-3 โ€” Given an active impersonation, when the admin attempts a destructive write that requires admin role (e.g. POST a tester assignment), then it is blocked โ€” impersonation grants read parity, not write parity.
  • AC-4 โ€” Given an active impersonation, when the admin clicks "End session", then the cookie is cleared and the next page render shows the admin's own sidebar.
  • AC-5 โ€” Given an admin signs out while impersonating, when the session ends, then admin_impersonation_log.ended_at is also set.

Test data / fixtures

  • Baseline seed
  • At least one tester with at least one filed issue, so impersonation has something to display.

Negative paths

#ScenarioExpected behavior
N-1Non-admin calls POST /api/admin/impersonate403
N-2Admin tries to impersonate themselves400 "Cannot impersonate self"
N-3targetUserId does not exist404
N-4Two admins impersonate same target simultaneouslyEach gets independent cookie + log row; no conflict
N-5Admin clears cookies manuallyBanner disappears, role reverts on next request

Manual QA checklist

  • Open /app/admin/users/:id for a tester who has filed issues
  • Click "View as user" โ€” banner appears, redirected to /app
  • Sidebar shows Tester's items (Test Cycles ยท Balance ยท Browse)
  • Navigate to /app/test-cycles/balance โ€” see target's earnings, not your own
  • Navigate to /app/test-cycles โ€” see only target's cycles
  • Try to create a new cycle from /app/admin/test-cycles/new โ€” should work (admin write parity persists for the admin's own actions, not for target's actions; verify what the policy actually does on prod)
  • Click "End session" in banner โ€” banner disappears, sidebar reverts to admin's
  • Verify admin_impersonation_log has start + end timestamps for the session

Automated test outline

test.describe("F-03 impersonation", () => {
  test("admin starts and ends impersonation", async ({ page }) => { โ€ฆ });
  test("impersonating reflects target's data", async ({ page }) => { โ€ฆ });
  test("self-impersonation blocked", async ({ request }) => { โ€ฆ });
});

Code references

  • UI: src/components/test-cycles/ImpersonationBanner.tsx
  • API: src/app/api/admin/impersonate/route.ts (POST, DELETE)
  • Hooks: src/hooks/test-cycles/impersonation.ts
  • Auth helper: src/lib/auth/resolve-user.ts โ€” reads impersonation cookie
  • Schema: admin_impersonation_log table

Events emitted (proposed)

  • admin.impersonation_started โ€” { admin_user_id, target_user_id, ip, user_agent }
  • admin.impersonation_ended โ€” { admin_user_id, target_user_id, duration_seconds }

Open questions / known gaps

  • No "frozen-write" mode flag: writes performed during impersonation are attributed to the admin in DB columns like created_by, but the user in the audit trail is the admin (correct behavior โ€” verify in tests).
  • No notification to the impersonated user (deliberate โ€” debug tool).
  • Admin sidebar leak fix shipped in #144.

On this page