Endpoint for Respan admins to impersonate users for support purposes.
Security: gated on the staff_impersonate session scope (Phase 9
prereq #4 of the staff step-up auth roadmap). The scope is held
SEPARATELY from staff_read / staff_write so operators can
revoke impersonation from a specific admin via
APIUser.has_impersonation_access without revoking cross-org
read/write powers.
Per-user authority:
has_impersonation_access=True (default) → user can claim the
staff_impersonate scope via /auth/jwt/scope/.has_impersonation_access=False → get_staff_authority
excludes staff_impersonate from the user’s set, so the scope
cannot be claimed. Existing API-key admins on the transitional
flag belt re-check the field too.Start-impersonation requires the scope; stop-impersonation is deliberately ALLOWED without it so an operator can flip the lever to terminate in-flight sessions and the actor retains clean termination of their own session.
ACTOR vs EFFECTIVE identity: during impersonation the JWT get_user()
hook (KeywordsAIJWTAuthentication.get_user) swaps request.user
to the TARGET and stashes the real admin on impersonated_by_admin.
Start/stop state lives on the ACTOR (the admin), not the target — so
we resolve actor here before calling impersonate_user().
Without this, stop-impersonation hits the target (who is not
impersonating anyone) and returns success=False (HTTP 400).