Add admin login API and remove hardcoded secrets

- Add token-based admin login/logout/me endpoints
- Bootstrap admin from ADMIN_USERNAME/ADMIN_PASSWORD in Docker entrypoint
- Read ALLOWED_HOSTS and CORS from env only (no hardcoded server IPs)
- Keep docs/Postman/tests free of real credentials
- Cover login and auth flows with 31 local API tests

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Shayan Azadi
2026-07-16 17:29:47 +03:30
parent 7e71d922d3
commit 50c9cf613f
13 changed files with 791 additions and 141 deletions

View File

@@ -1,8 +1,9 @@
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from rest_framework.authtoken.models import Token
from django.shortcuts import get_object_or_404
from django.db.models import Q
from django.utils import timezone
@@ -13,9 +14,56 @@ from .serializers import (
CompositionSerializer,
CampaignSerializer,
CompositionImageSerializer,
AdminLoginSerializer,
)
@api_view(['POST'])
@permission_classes([AllowAny])
def admin_login(request):
"""
Admin login with username and password.
Returns an auth token to use as:
Authorization: Token <token>
"""
serializer = AdminLoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, _ = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user': {
'id': user.id,
'username': user.username,
'is_staff': user.is_staff,
'is_superuser': user.is_superuser,
},
})
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def admin_logout(request):
"""Delete the current admin auth token (logout)."""
Token.objects.filter(user=request.user).delete()
return Response({'detail': 'Logged out successfully.'})
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def admin_me(request):
"""Return the currently authenticated admin user."""
user = request.user
return Response({
'id': user.id,
'username': user.username,
'is_staff': user.is_staff,
'is_superuser': user.is_superuser,
'is_active': user.is_active,
})
class ContactUsViewSet(viewsets.ModelViewSet):
"""
ViewSet for ContactUs model.