feat: replace token-based impersonation with enterprise-grade header-based system

Replaces the insecure token-in-localStorage approach with a header-based
impersonation system backed by cache sessions and MFA verification.

Key changes:
- New impersonation_sessions audit table (immutable, ULID PK)
- MFA verification required to start impersonation (TOTP/email/backup)
- X-Impersonate-User header + HandleImpersonation middleware
- Per-request auth context swap (admin session never modified)
- IP pinning, sensitive route blocking, no nesting, sliding 60-min TTL
- Activity log auto-tagged with impersonated_by during sessions
- Frontend: sessionStorage, BroadcastChannel sync, countdown timer
- ImpersonateDialog with reason + MFA verification flow
- 26 comprehensive tests covering core, middleware, audit, lifecycle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 02:42:53 +02:00
parent 47cb6b83d4
commit 4df668b5b8
25 changed files with 1813 additions and 269 deletions

View File

@@ -8,7 +8,6 @@ import type {
AdminOrganisationMember,
AdminUser,
CreateOrganisationPayload,
ImpersonationResponse,
InviteMemberPayload,
PlatformStats,
UpdateAdminOrganisationPayload,
@@ -245,25 +244,4 @@ export function useAdminActivityLog(params: Ref<Record<string, string | number |
}
// ─── Impersonation ──────────────────────────────────────────
export function useStartImpersonation() {
return useMutation({
mutationFn: async (userId: string) => {
const { data } = await apiClient.post<ApiResponse<ImpersonationResponse>>(
`/admin/impersonate/${userId}`,
)
return data.data
},
})
}
export function useStopImpersonation() {
return useMutation({
mutationFn: async () => {
const { data } = await apiClient.post<ApiResponse<{ user: AdminUser }>>(
'/admin/stop-impersonation',
)
return data.data
},
})
}
// Impersonation API calls are now handled directly by useImpersonationStore.