From 05de552820a571d2c25de16607c7ebc10df9378e Mon Sep 17 00:00:00 2001 From: Sicherhaven Date: Tue, 21 Apr 2026 22:55:08 +0530 Subject: [PATCH] feat(partners): add PartnerImpersonateView for admin Login-as-Partner POST /api/v1/partners//impersonate/ mints a short-lived JWT for the partner's primary partner_manager user. Returns access + refresh tokens so the partner portal can create a session without requiring a password. Writes a partner.impersonated audit log row with admin username, partner name, and impersonated user for traceability. Closes: admin Login-as-Partner showing "Partner not found" (mock data) --- admin_api/urls.py | 1 + admin_api/views.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/admin_api/urls.py b/admin_api/urls.py index 211188a..8705671 100644 --- a/admin_api/urls.py +++ b/admin_api/urls.py @@ -18,6 +18,7 @@ urlpatterns = [ path('partners//', views.PartnerDetailView.as_view(), name='partner-detail'), path('partners//status/', views.PartnerStatusView.as_view(), name='partner-status'), path('partners//kyc/review/', views.PartnerKYCReviewView.as_view(), name='partner-kyc-review'), + path('partners//impersonate/', views.PartnerImpersonateView.as_view(), name='partner-impersonate'), path('partners/onboard/', views.PartnerOnboardView.as_view(), name='partner-onboard'), path('partners//staff/', views.PartnerStaffCreateView.as_view(), name='partner-staff-create'), path('users/metrics/', views.UserMetricsView.as_view(), name='user-metrics'), diff --git a/admin_api/views.py b/admin_api/views.py index 055d860..5bd4145 100644 --- a/admin_api/views.py +++ b/admin_api/views.py @@ -2600,6 +2600,42 @@ class PartnerStaffCreateView(APIView): ) +class PartnerImpersonateView(APIView): + """ + POST /api/v1/partners//impersonate/ + Admin-only: generate a short-lived JWT for the partner's primary manager user. + Returns access/refresh tokens + user info so the partner portal can create a session. + """ + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + from partner.models import Partner as PartnerModel + partner = get_object_or_404(PartnerModel, pk=pk) + partner_user = User.objects.filter(partner=partner, role='partner_manager').first() + if not partner_user: + return Response( + {'error': 'No partner_manager user found for this partner.'}, + status=status.HTTP_404_NOT_FOUND, + ) + refresh = RefreshToken.for_user(partner_user) + _audit_log(request, 'partner.impersonated', 'partner', str(pk), { + 'partner_name': partner.name, + 'impersonated_user': partner_user.username, + 'admin': request.user.username, + }) + return Response({ + 'access': str(refresh.access_token), + 'refresh': str(refresh), + 'user': { + 'id': partner_user.id, + 'email': partner_user.email, + 'username': partner_user.username, + 'role': partner_user.role, + 'partnerId': str(pk), + }, + }) + + # ─── Gamification Dashboard (stub) ─────────────────────────────────────────── class GamificationDashboardView(APIView): permission_classes = [] # public for now; restrict when auth is wired up