Skip to content

Improve Impersonation UX

Current State Analysis

What exists today

Admin page (/admin/impersonation): - Loads ALL investigators from the API into memory and builds a Lunr.js full-text index - Search bar filters the full list client-side - Clicking a user dispatches startUserImpersonation which stores the user in NgRx state + localStorage - When impersonating, shows raw JSON (<pre>{{ impersonatedUser | json }}</pre>) and a plain "Stop Impersonation" button - Dead code: an old manual form (#oldNotImpersonated ng-template) that required typing user ID, name, and email fields manually — never rendered

Global indicator (nav bar): - Small inline text in the user menu button: (currently impersonating {preferred name}) - No visual distinction (no color, no banner, no urgency) - Easy to forget you're impersonating

HTTP interceptor: - Attaches syrf-imp-* headers to all protected API requests when impersonating - Backend middleware validates admin role, sanitizes inputs, stores in HttpContext

Password page: - Shows a warning that password reset will go to the authenticated account, not the impersonated user

Problems

  1. No global visual indicator — impersonation status is buried in the nav dropdown text. Admins can forget they're impersonating and accidentally take actions as another user.
  2. Raw JSON display — when impersonating, the admin page shows <pre>{{ impersonatedUser | json }}</pre> instead of a readable card.
  3. No confirmation before impersonating — clicking a user immediately starts impersonation with no confirmation dialog.
  4. No quick stop — the only way to stop impersonating is to navigate back to /admin/impersonation. There's no global "Stop" button.
  5. Dead code — the #oldNotImpersonated template with manual form fields is unused and adds confusion.
  6. Debug components in production<app-debugger-group> with raw observable dumps is rendered at the top of the page.
  7. Untyped forms — uses UntypedFormControl/UntypedFormGroup instead of typed equivalents.
  8. No audit trail — no logging of who impersonated whom and when.

Plan

Step 1: Add a global impersonation banner

Files to modify: - src/services/web/src/app/core/nav/nav.component.html - src/services/web/src/app/core/nav/nav.component.ts - src/services/web/src/app/core/nav/nav.component.scss

Changes: - Add a prominent fixed banner at the top of the page (above the toolbar) when isImpersonatingUser$ is true - Banner should be a warning color (amber/orange) with text like: "You are impersonating {name} ({email})" and a "Stop Impersonation" button - The banner dispatches authApiActions.stopUserImpersonation() on click - This ensures admins always see they're impersonating regardless of which page they're on

Step 2: Improve the impersonation admin page

Files to modify: - src/services/web/src/app/admin/impersonation/impersonation.component.html - src/services/web/src/app/admin/impersonation/impersonation.component.ts - src/services/web/src/app/admin/impersonation/impersonation.component.scss

Changes: - Remove debug components: Delete the <app-debugger-group> block and remove DebuggerComponent/DebuggerGroupComponent from imports - Remove dead code: Delete the #oldNotImpersonated ng-template (manual form), remove unused impersonationFormGroup and related form imports - Improve active impersonation display: Replace raw JSON with a styled mat-card showing the impersonated user's name, email, and avatar in a readable format, plus a prominent "Stop Impersonation" button - Add confirmation dialog: Before impersonating, show a Material confirmation dialog with the user's details and a confirm/cancel action - Improve list items: Show avatar/initials, email as subtitle, and make the list more scannable - Add empty state: Show a message when no search results match

Step 3: Add confirmation dialog component

Files to create: - src/services/web/src/app/admin/impersonation/confirm-impersonation-dialog.component.ts

Changes: - Standalone Angular component using MatDialogModule - Accepts impersonation target user data via MAT_DIALOG_DATA - Displays user name, email, and a warning that all actions will be performed as this user - Returns true on confirm, closes on cancel - The impersonation component opens this dialog before dispatching the action

Step 4: Improve nav impersonation indicator

Files to modify: - src/services/web/src/app/core/nav/nav.component.html

Changes: - Remove the inline (currently impersonating ...) text from the user menu button (replaced by the global banner from Step 1) - Alternatively, keep it but make it visually distinct (different color icon)

Step 5: Clean up and modernize

Files to modify: - src/services/web/src/app/admin/impersonation/impersonation.component.ts - src/services/web/src/app/admin/impersonation/impersonation.component.spec.ts

Changes: - Remove unused imports (UntypedFormBuilder, UntypedFormGroup, Validators, FormsModule, ReactiveFormsModule, ExtendedModule, NgClass, JsonPipe, GridModule, MatFormFieldModule, MatInputModule) - Use typed FormControl<string> for the search control - Update test to cover confirmation dialog interaction

Step 6: Backend audit logging (optional enhancement)

Files to modify: - src/libs/webhostconfig/SyRF.WebHostConfig.Common/Infrastructure/UserImpersonationMiddleware.cs

Changes: - Add structured logging when impersonation is activated: log the admin user ID, impersonated user ID, and timestamp - This provides an audit trail for security review


Implementation Order

  1. Step 3 (confirmation dialog) — no dependencies
  2. Step 2 (admin page improvements) — depends on Step 3
  3. Step 1 (global banner) — independent
  4. Step 4 (nav cleanup) — depends on Step 1
  5. Step 5 (code cleanup) — depends on Steps 2 & 3
  6. Step 6 (audit logging) — independent, optional

Steps 1 and 3 can be done in parallel. Steps 2, 4, 5 are sequential.