feat(partners): add PartnerImpersonateView for admin Login-as-Partner

POST /api/v1/partners/<pk>/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)
This commit is contained in:
2026-04-21 22:55:08 +05:30
parent f85188ca6b
commit 05de552820
2 changed files with 37 additions and 0 deletions

View File

@@ -18,6 +18,7 @@ urlpatterns = [
path('partners/<int:pk>/', views.PartnerDetailView.as_view(), name='partner-detail'), path('partners/<int:pk>/', views.PartnerDetailView.as_view(), name='partner-detail'),
path('partners/<int:pk>/status/', views.PartnerStatusView.as_view(), name='partner-status'), path('partners/<int:pk>/status/', views.PartnerStatusView.as_view(), name='partner-status'),
path('partners/<int:pk>/kyc/review/', views.PartnerKYCReviewView.as_view(), name='partner-kyc-review'), path('partners/<int:pk>/kyc/review/', views.PartnerKYCReviewView.as_view(), name='partner-kyc-review'),
path('partners/<int:pk>/impersonate/', views.PartnerImpersonateView.as_view(), name='partner-impersonate'),
path('partners/onboard/', views.PartnerOnboardView.as_view(), name='partner-onboard'), path('partners/onboard/', views.PartnerOnboardView.as_view(), name='partner-onboard'),
path('partners/<int:partner_id>/staff/', views.PartnerStaffCreateView.as_view(), name='partner-staff-create'), path('partners/<int:partner_id>/staff/', views.PartnerStaffCreateView.as_view(), name='partner-staff-create'),
path('users/metrics/', views.UserMetricsView.as_view(), name='user-metrics'), path('users/metrics/', views.UserMetricsView.as_view(), name='user-metrics'),

View File

@@ -2600,6 +2600,42 @@ class PartnerStaffCreateView(APIView):
) )
class PartnerImpersonateView(APIView):
"""
POST /api/v1/partners/<pk>/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) ─────────────────────────────────────────── # ─── Gamification Dashboard (stub) ───────────────────────────────────────────
class GamificationDashboardView(APIView): class GamificationDashboardView(APIView):
permission_classes = [] # public for now; restrict when auth is wired up permission_classes = [] # public for now; restrict when auth is wired up