from django.db import transaction from django.utils import timezone from rest_framework import serializers, status from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from rest_framework_simplejwt.exceptions import InvalidToken, TokenError from rest_framework_simplejwt.tokens import RefreshToken from apps.cuser.models import CUser, CuserRole, RoleType from apps.jwt_auth.models import JwtToken, CUserRoleLoginOpt from apps.jwt_auth.services import login_by_username, login_by_mobile, verify_mobile_and_get_token from apps.jwt_auth.session import issue_tokens, validate_session_claims from apps.workspace.models import Workspace class RoleTypeSerializer(serializers.ModelSerializer): class Meta: model = RoleType fields = ('role_name', 'role_name_en') class RoleSerializer(serializers.ModelSerializer): class WorkspaceSerializer(serializers.ModelSerializer): class Meta: model = Workspace fields = ('id', 'name') role_type = RoleTypeSerializer() sys_ws = WorkspaceSerializer() class Meta: model = CuserRole fields = ('name_family', 'title', 'role_type', 'sys_ws') class LoginOptionSerializer(serializers.ModelSerializer): cuser_role = RoleSerializer() class Meta: model = CUserRoleLoginOpt fields = ('cuser_role', 'expire', 'uuid') class PublicAuthView(APIView): authentication_classes = () permission_classes = (AllowAny,) def get_authenticate_header(self, request): return 'Bearer realm="api"' class VerifyMobileAndGetTokenView(PublicAuthView): class InputSerializer(serializers.Serializer): mobile = serializers.CharField(required=True) opt = serializers.IntegerField(required=True, min_value=100000, max_value=999999) def post(self, request): serializer = self.InputSerializer(data=request.data) serializer.is_valid(raise_exception=True) options = verify_mobile_and_get_token(**serializer.validated_data) return Response(LoginOptionSerializer(options, many=True).data) class LoginByMobileView(PublicAuthView): class InputSerializer(serializers.Serializer): mobile = serializers.CharField(required=True) def post(self, request): serializer = self.InputSerializer(data=request.data) serializer.is_valid(raise_exception=True) result = login_by_mobile(**serializer.validated_data) return Response({'result': result}, status=202 if result['delivery_status'] == 'unconfirmed' else 200) class LoginByUserView(PublicAuthView): class InputSerializer(serializers.Serializer): username = serializers.CharField(required=True) password = serializers.CharField(required=True, trim_whitespace=False, write_only=True) def post(self, request): serializer = self.InputSerializer(data=request.data) serializer.is_valid(raise_exception=True) options = login_by_username(**serializer.validated_data) return Response(LoginOptionSerializer(options, many=True).data) class CustomTokenObtainPairView(PublicAuthView): class InputSerializer(serializers.Serializer): uuid_opt = serializers.UUIDField(required=True) @transaction.atomic def post(self, request): serializer = self.InputSerializer(data=request.data) serializer.is_valid(raise_exception=True) try: user_id = CUserRoleLoginOpt.objects.values_list('cuser_id', flat=True).get( uuid=serializer.validated_data['uuid_opt'], expire__gt=timezone.now()) CUser.objects.select_for_update().get(pk=user_id) option = CUserRoleLoginOpt.objects.select_related( 'cuser_role__cuser', 'cuser_role__role_type', 'cuser_role__sys_ws', ).get(uuid=serializer.validated_data['uuid_opt'], expire__gt=timezone.now()) except (CUserRoleLoginOpt.DoesNotExist, CUser.DoesNotExist): raise InvalidToken('اعتبار درخواست ورود تمام شده است. دوباره وارد شوید.') if option.cuser_id != option.cuser_role.cuser_id: raise InvalidToken('درخواست ورود معتبر نیست.') data = issue_tokens(option.cuser_role) CUserRoleLoginOpt.objects.filter(cuser_id=option.cuser_id).delete() return Response(data) class CustomTokenRefreshView(PublicAuthView): class InputSerializer(serializers.Serializer): refresh = serializers.CharField(required=True) @transaction.atomic def post(self, request): serializer = self.InputSerializer(data=request.data) serializer.is_valid(raise_exception=True) refresh_value = serializer.validated_data['refresh'] try: refresh = RefreshToken(refresh_value) session = JwtToken.objects.select_for_update().select_related( 'cuser_role__cuser', 'cuser_role__role_type', 'cuser_role__sys_ws', ).get(refresh=refresh_value, is_blocked=False) except (TokenError, JwtToken.DoesNotExist): raise InvalidToken('نشست شما منقضی یا باطل شده است. دوباره وارد شوید.') validate_session_claims(session, refresh) return Response(issue_tokens(session.cuser_role, session=session)) class LogoutView(PublicAuthView): class InputSerializer(serializers.Serializer): refresh = serializers.CharField(required=True) def post(self, request): serializer = self.InputSerializer(data=request.data) serializer.is_valid(raise_exception=True) value = serializer.validated_data['refresh'] try: token = RefreshToken(value) except TokenError: return Response(status=status.HTTP_204_NO_CONTENT) # Possession of the signed refresh token permits logout even after access expires. if type(token.get('session_id')) is int and type(token.get('user_id')) is int: JwtToken.objects.filter(pk=token['session_id'], cuser_id=token['user_id']).update(is_blocked=True) else: JwtToken.objects.filter(refresh=value).update(is_blocked=True) return Response(status=status.HTTP_204_NO_CONTENT)