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:
@@ -18,6 +18,7 @@ urlpatterns = [
|
||||
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>/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/<int:partner_id>/staff/', views.PartnerStaffCreateView.as_view(), name='partner-staff-create'),
|
||||
path('users/metrics/', views.UserMetricsView.as_view(), name='user-metrics'),
|
||||
|
||||
@@ -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) ───────────────────────────────────────────
|
||||
class GamificationDashboardView(APIView):
|
||||
permission_classes = [] # public for now; restrict when auth is wired up
|
||||
|
||||
Reference in New Issue
Block a user