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>/', 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'),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user