added the session auth
8 files changed, +129 -5
+0-0.vscode/settings.json → .vscode.template/settings.json
No content changes (mode or rename only).
+0-0rango/backend/api/auth/__init__.py
No content changes (mode or rename only).
+41-0rango/backend/api/auth/serializers.py
| @@ -0,0 +1,41 @@ | ||
| 1 | +from rest_framework import serializers | |
| 2 | +from django.contrib.auth.models import User | |
| 3 | +from rest_framework.exceptions import ValidationError, NotFound, AuthenticationFailed | |
| 4 | +from django.contrib.auth import login, logout, authenticate | |
| 5 | + | |
| 6 | + | |
| 7 | +class LoginSerializer(serializers.ModelSerializer): | |
| 8 | + username = serializers.CharField(required=False) | |
| 9 | + email = serializers.EmailField(required=False) | |
| 10 | + password = serializers.CharField(write_only=True) | |
| 11 | + class Meta: | |
| 12 | + model = User | |
| 13 | + fields = ['username', 'email', 'password'] | |
| 14 | + | |
| 15 | + def create(self, validated_data): | |
| 16 | + request = self.context['request'] | |
| 17 | + logout(request) | |
| 18 | + username = validated_data.get('username') | |
| 19 | + email = validated_data.get('email') | |
| 20 | + password = validated_data.get('password') | |
| 21 | + if not username and not email: | |
| 22 | + raise ValidationError('Requires username or email.') | |
| 23 | + if username: | |
| 24 | + user = authenticate(username=username, password=password) | |
| 25 | + else: | |
| 26 | + query = User.objects.filter(email=email) | |
| 27 | + if not query: | |
| 28 | + raise NotFound(f'{email} has no account.') | |
| 29 | + username = query.first().username | |
| 30 | + user = authenticate(username, password) | |
| 31 | + if not user: | |
| 32 | + raise AuthenticationFailed('Incorrect username or password.') | |
| 33 | + login(self.context['request'], user) | |
| 34 | + return user | |
| 35 | + | |
| 36 | + | |
| 37 | +class LogoutSerializer(serializers.Serializer): | |
| 38 | + detail = serializers.CharField(read_only=True, default="Success") | |
| 39 | + | |
| 40 | +class LoggedInSerializer(serializers.Serializer): | |
| 41 | + detail = serializers.BooleanField(read_only=True, default=False) |
+11-0rango/backend/api/auth/urls.py
| @@ -0,0 +1,11 @@ | ||
| 1 | +from django.urls import path | |
| 2 | +from .views import LoginView, AuthRoot, LogoutView, LoggedInView | |
| 3 | + | |
| 4 | + | |
| 5 | +urlpatterns = [ | |
| 6 | + path('login', LoginView.as_view(), name='api-login'), | |
| 7 | + path('', AuthRoot.as_view(), name='api-auth-root'), | |
| 8 | + path('logout', LogoutView.as_view(), name='api-logout'), | |
| 9 | + path('logged-in', LoggedInView.as_view(), name='api-logged-in') | |
| 10 | +] | |
| 11 | + |
+66-0rango/backend/api/auth/views.py
| @@ -0,0 +1,66 @@ | ||
| 1 | +from rest_framework.generics import CreateAPIView, GenericAPIView, RetrieveAPIView | |
| 2 | +from rest_framework.views import APIView | |
| 3 | +from rest_framework.routers import APIRootView | |
| 4 | +from rest_framework.reverse import reverse | |
| 5 | +from rest_framework.response import Response | |
| 6 | +from rest_framework.permissions import IsAuthenticated | |
| 7 | +from rest_framework.schemas.openapi import AutoSchema | |
| 8 | +from rest_framework.permissions import AllowAny | |
| 9 | +from .serializers import LoginSerializer, LogoutSerializer, LoggedInSerializer | |
| 10 | +from django.contrib.auth.models import User | |
| 11 | +from django.contrib.auth import logout | |
| 12 | + | |
| 13 | + | |
| 14 | +class AuthRoot(APIRootView): | |
| 15 | + | |
| 16 | + def get(self, request, *args, **kwargs): | |
| 17 | + return Response({ | |
| 18 | + 'login': reverse('api-login', request=request), | |
| 19 | + 'logout': reverse('api-logout', request=request), | |
| 20 | + 'logged-in': reverse('api-logged-in', request=request) | |
| 21 | + }) | |
| 22 | + | |
| 23 | + | |
| 24 | +class LoginView(CreateAPIView): | |
| 25 | + schema = AutoSchema( | |
| 26 | + tags=['Auth'], | |
| 27 | + component_name='login', | |
| 28 | + operation_id_base='Login' | |
| 29 | + ) | |
| 30 | + serializer_class = LoginSerializer | |
| 31 | + queryset = User.objects.all() | |
| 32 | + permission_classes = [AllowAny] | |
| 33 | + | |
| 34 | + | |
| 35 | +class LoggedInView(RetrieveAPIView): | |
| 36 | + schema = AutoSchema( | |
| 37 | + tags=['Auth'], | |
| 38 | + component_name='logged-in', | |
| 39 | + operation_id_base='LoggedIn' | |
| 40 | + ) | |
| 41 | + serializer_class = LoggedInSerializer | |
| 42 | + | |
| 43 | + def retrieve(self, request, *args, **kwargs): | |
| 44 | + if request.user.is_authenticated: | |
| 45 | + return Response({ | |
| 46 | + 'detail': True | |
| 47 | + }) | |
| 48 | + return Response({'detail': False}) | |
| 49 | + | |
| 50 | + | |
| 51 | +class LogoutView(RetrieveAPIView): | |
| 52 | + schema = AutoSchema( | |
| 53 | + tags=['Auth'], | |
| 54 | + component_name='logout', | |
| 55 | + operation_id_base='Logout' | |
| 56 | + ) | |
| 57 | + serializer_class = LogoutSerializer | |
| 58 | + permission_classes = [IsAuthenticated] | |
| 59 | + | |
| 60 | + def retrieve(self, request): | |
| 61 | + user = request.user | |
| 62 | + logout(request) | |
| 63 | + return Response({ | |
| 64 | + 'details': f'Successfully logged out {user}.' | |
| 65 | + }) | |
| 66 | + |
+3-3rango/backend/api/management/commands/createapi.py
| @@ -9,13 +9,13 @@ class Command(BaseCommand): | ||
| 9 | 9 | help = "Creates a new API. Usage: python manage.py createapi api_name [root_dir]" |
| 10 | 10 | |
| 11 | 11 | def add_arguments(self, parser: CommandParser) -> None: |
| 12 | - parser.add_argument('api_name', type=str, help='The name of the API model') | |
| 12 | + parser.add_argument('model_name', type=str, help='The name of the API model') | |
| 13 | 13 | parser.add_argument('root_dir', type=str, nargs='?', default='api', help='The root directory for the API model. Defaults to "api"') |
| 14 | 14 | |
| 15 | 15 | def handle(self, *args, **options): |
| 16 | 16 | # Get the api name from the first argument |
| 17 | - api_name = options['api_name'].lower() | |
| 18 | - model_name = api_name.capitalize() | |
| 17 | + model_name = options['model_name'] | |
| 18 | + api_name = model_name.lower() | |
| 19 | 19 | # Get the root directory |
| 20 | 20 | root_dir = options['root_dir'].lower() |
| 21 | 21 | # Create context for the templates |
+4-1rango/backend/api/urls.py
| @@ -3,9 +3,12 @@ from .views import ApiRoot, schema_view, swagger_view | ||
| 3 | 3 | |
| 4 | 4 | |
| 5 | 5 | urlpatterns = [ |
| 6 | + # Auto-generated: a keyword used by the createapi command to know where to add new urls | |
| 6 | 7 | re_path('$', ApiRoot.as_view(), name='api-root'), |
| 7 | 8 | re_path('', include('api.user.urls')), |
| 8 | 9 | re_path('openapi', schema_view, name='openapi-schema'), |
| 9 | 10 | re_path('swagger-ui', swagger_view, name='swagger-ui'), |
| 10 | - # Auto-generated: a keyword used by the createapi command to know where to add new urls | |
| 11 | + re_path('openapi', schema_view, name='openapi-schema'), | |
| 12 | + re_path('swagger-ui', swagger_view, name='swagger-ui'), | |
| 13 | + re_path('auth/', include('api.auth.urls')), | |
| 11 | 14 | ] |
+4-1rango/backend/api/views.py
| @@ -13,6 +13,9 @@ class ApiRoot(APIRootView): | ||
| 13 | 13 | |
| 14 | 14 | def get(self, request, *args, **kwargs): |
| 15 | 15 | return Response({ |
| 16 | - 'user': reverse('user-list', request=request), | |
| 17 | 16 | # Auto-generated: a keyword used by the createapi command to know where to add new urls |
| 17 | + 'user': reverse('user-list', request=request), | |
| 18 | + 'openapi': reverse('openapi-schema', request=request), | |
| 19 | + 'swagger-ui': reverse('swagger-ui', request=request), | |
| 20 | + 'auth': reverse('api-auth-root', request=request), | |
| 18 | 21 | }) |