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¶
- 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.
- Raw JSON display — when impersonating, the admin page shows
<pre>{{ impersonatedUser | json }}</pre>instead of a readable card. - No confirmation before impersonating — clicking a user immediately starts impersonation with no confirmation dialog.
- No quick stop — the only way to stop impersonating is to navigate back to
/admin/impersonation. There's no global "Stop" button. - Dead code — the
#oldNotImpersonatedtemplate with manual form fields is unused and adds confusion. - Debug components in production —
<app-debugger-group>with raw observable dumps is rendered at the top of the page. - Untyped forms — uses
UntypedFormControl/UntypedFormGroupinstead of typed equivalents. - 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¶
- Step 3 (confirmation dialog) — no dependencies
- Step 2 (admin page improvements) — depends on Step 3
- Step 1 (global banner) — independent
- Step 4 (nav cleanup) — depends on Step 1
- Step 5 (code cleanup) — depends on Steps 2 & 3
- Step 6 (audit logging) — independent, optional
Steps 1 and 3 can be done in parallel. Steps 2, 4, 5 are sequential.