Files
igny8/backend/igny8_core/auth/urls.py
IGNY8 VPS (Salman) d3ec7cf2e3 Refactor authentication and integration handling
- Exclude the 'MeView' endpoint from public API documentation, marking it as an internal authenticated endpoint.
- Enhance error handling in the 'IntegrationSettingsViewSet' to gracefully manage empty request data and improve logging for account and settings lookups.
- Update API key retrieval logic to ensure fallback mechanisms are more robust and informative.
- Refactor user data fetching in the auth store to utilize a unified API system, improving error handling and data consistency.
2025-11-16 09:49:24 +00:00

208 lines
7.8 KiB
Python

"""
Authentication URL Configuration
"""
from django.urls import path, include
from django.views.decorators.csrf import csrf_exempt
from rest_framework.routers import DefaultRouter
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status, permissions
from drf_spectacular.utils import extend_schema
from igny8_core.api.response import success_response, error_response
from .views import (
GroupsViewSet, UsersViewSet, AccountsViewSet, SubscriptionsViewSet,
SiteUserAccessViewSet, PlanViewSet, SiteViewSet, SectorViewSet,
IndustryViewSet, SeedKeywordViewSet
)
from .serializers import RegisterSerializer, LoginSerializer, ChangePasswordSerializer, UserSerializer
from .models import User
router = DefaultRouter()
# Main structure: Groups, Users, Accounts, Subscriptions, Site User Access
router.register(r'groups', GroupsViewSet, basename='group')
router.register(r'users', UsersViewSet, basename='user')
router.register(r'accounts', AccountsViewSet, basename='account')
router.register(r'subscriptions', SubscriptionsViewSet, basename='subscription')
router.register(r'site-access', SiteUserAccessViewSet, basename='site-access')
# Supporting viewsets
router.register(r'plans', PlanViewSet, basename='plan')
router.register(r'sites', SiteViewSet, basename='site')
router.register(r'sectors', SectorViewSet, basename='sector')
router.register(r'industries', IndustryViewSet, basename='industry')
router.register(r'seed-keywords', SeedKeywordViewSet, basename='seed-keyword')
# Note: AuthViewSet removed - using direct APIView endpoints instead (login, register, etc.)
@extend_schema(
tags=['Authentication'],
summary='User Registration',
description='Register a new user account'
)
class RegisterView(APIView):
"""Registration endpoint."""
permission_classes = [permissions.AllowAny]
def post(self, request):
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
user_serializer = UserSerializer(user)
return success_response(
data={'user': user_serializer.data},
message='Registration successful',
status_code=status.HTTP_201_CREATED,
request=request
)
return error_response(
error='Validation failed',
errors=serializer.errors,
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
@extend_schema(
tags=['Authentication'],
summary='User Login',
description='Authenticate user and receive JWT tokens'
)
class LoginView(APIView):
"""Login endpoint."""
permission_classes = [permissions.AllowAny]
def post(self, request):
serializer = LoginSerializer(data=request.data)
if serializer.is_valid():
email = serializer.validated_data['email']
password = serializer.validated_data['password']
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return error_response(
error='Invalid credentials',
status_code=status.HTTP_401_UNAUTHORIZED,
request=request
)
if user.check_password(password):
# Log the user in (create session for session authentication)
from django.contrib.auth import login
login(request, user)
# Get account from user
account = getattr(user, 'account', None)
# Generate JWT tokens
from .utils import generate_access_token, generate_refresh_token, get_token_expiry
access_token = generate_access_token(user, account)
refresh_token = generate_refresh_token(user, account)
access_expires_at = get_token_expiry('access')
refresh_expires_at = get_token_expiry('refresh')
# Serialize user data safely, handling missing account relationship
try:
user_serializer = UserSerializer(user)
user_data = user_serializer.data
except Exception as e:
# Fallback if serializer fails (e.g., missing account_id column)
user_data = {
'id': user.id,
'username': user.username,
'email': user.email,
'role': user.role,
'account': None,
'accessible_sites': [],
}
return success_response(
data={
'user': user_data,
'tokens': {
'access': access_token,
'refresh': refresh_token,
'access_expires_at': access_expires_at.isoformat(),
'refresh_expires_at': refresh_expires_at.isoformat(),
}
},
message='Login successful',
request=request
)
return error_response(
error='Invalid credentials',
status_code=status.HTTP_401_UNAUTHORIZED,
request=request
)
return error_response(
error='Validation failed',
errors=serializer.errors,
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
@extend_schema(
tags=['Authentication'],
summary='Change Password',
description='Change user password'
)
class ChangePasswordView(APIView):
"""Change password endpoint."""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
serializer = ChangePasswordSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
user = request.user
if not user.check_password(serializer.validated_data['old_password']):
return error_response(
error='Current password is incorrect',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
user.set_password(serializer.validated_data['new_password'])
user.save()
return success_response(
message='Password changed successfully',
request=request
)
return error_response(
error='Validation failed',
errors=serializer.errors,
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
@extend_schema(exclude=True) # Exclude from public API documentation - internal authenticated endpoint
class MeView(APIView):
"""Get current user information."""
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
# Refresh user from DB to get latest account/plan data
# This ensures account/plan changes are reflected immediately
from .models import User as UserModel
user = UserModel.objects.select_related('account', 'account__plan').get(id=request.user.id)
serializer = UserSerializer(user)
return success_response(
data={'user': serializer.data},
request=request
)
urlpatterns = [
path('', include(router.urls)),
path('register/', csrf_exempt(RegisterView.as_view()), name='auth-register'),
path('login/', csrf_exempt(LoginView.as_view()), name='auth-login'),
path('change-password/', ChangePasswordView.as_view(), name='auth-change-password'),
path('me/', MeView.as_view(), name='auth-me'),
]