diff --git a/account/serializers.py b/account/serializers.py index 36571dc..e2aee49 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -13,21 +13,15 @@ from rest_framework.decorators import action from django.contrib.auth import authenticate, login from rest_framework_simplejwt.tokens import RefreshToken from guardian.utils import upload_image_to_alibaba +from .utils import get_token -class GoogleSignInSerializer(serializers.Serializer): - """Google login Serializer""" - email = serializers.EmailField() - def create(self, validated_data): - """Create or update user model""" - with transaction.atomic(): - if User.objects.filter(email__iexact=self.validated_data['email']).exists(): - return User.objects.get(email__iexact=self.validated_data['email']) +class GoogleLoginSerializer(serializers.Serializer): + access_token = serializers.CharField(max_length=5000, required=True) - if not User.objects.filter(email__iexact=self.validated_data['email']).exists(): - instance = User.objects.create(username=self.validated_data['email'], - email=self.validated_data['email']) - return instance + class Meta: + """meta class""" + fields = ('access_token',) class UpdateGuardianImageSerializer(serializers.ModelSerializer): """Reset Password after verification""" @@ -132,11 +126,13 @@ class GuardianSerializer(serializers.ModelSerializer): first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') auth_token = serializers.SerializerMethodField('get_auth_token') + refresh_token = serializers.SerializerMethodField('get_refresh_token') def get_auth_token(self, obj): - refresh = RefreshToken.for_user(obj.user) - access_token = str(refresh.access_token) - return access_token + return get_token()['access'] + def get_refresh_token(self, obj): + return get_token()['refresh'] + def get_user_type(self, obj): """user type""" @@ -157,8 +153,8 @@ class GuardianSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Guardian - fields = ['auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob', - 'referral_code', 'is_active', 'is_complete_profile', 'passcode', + fields = ['auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob', + 'referral_code', 'is_active', 'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type'] @@ -169,11 +165,12 @@ class JuniorSerializer(serializers.ModelSerializer): first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') auth_token = serializers.SerializerMethodField('get_auth_token') + refresh_token = serializers.SerializerMethodField('get_refresh_token') def get_auth_token(self, obj): - refresh = RefreshToken.for_user(obj.auth) - access_token = str(refresh.access_token) - return access_token + return get_token()['access'] + def get_refresh_token(self, obj): + return get_token()['refresh'] def get_user_type(self, obj): return JUNIOR @@ -190,8 +187,8 @@ class JuniorSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Junior - fields = ['auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', - 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', + fields = ['auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type'] class EmailVerificationSerializer(serializers.ModelSerializer): diff --git a/account/urls.py b/account/urls.py index b9501e6..e0e1c6c 100644 --- a/account/urls.py +++ b/account/urls.py @@ -5,14 +5,15 @@ from rest_framework.decorators import api_view """Third party import""" from rest_framework import routers from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, - ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage) + ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, + GoogleLoginViewSet, SigninWithApple) """Router""" router = routers.SimpleRouter() """API End points with router""" router.register('user', UserLogin, basename='user') router.register('admin', UserLogin, basename='admin') -# router.register('google-login', GoogleLoginAPIViewset, basename='admin') +router.register('google-login', GoogleLoginViewSet, basename='admin') router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') @@ -22,5 +23,6 @@ urlpatterns = [ path('api/v1/forgot-password/', ForgotPasswordAPIView.as_view()), path('api/v1/reset-password/', ResetPasswordAPIView.as_view()), path('api/v1/change-password/', ChangePasswordAPIView.as_view()), - path('api/v1/update-profile-image/', UpdateProfileImage.as_view()) + path('api/v1/update-profile-image/', UpdateProfileImage.as_view()), + path('api/v1/apple-login/', SigninWithApple.as_view(), name='signup_with_apple'), ] diff --git a/account/utils.py b/account/utils.py index bfc2399..0848c03 100644 --- a/account/utils.py +++ b/account/utils.py @@ -3,8 +3,13 @@ from django.conf import settings from rest_framework import viewsets, status from rest_framework.response import Response - from templated_email import send_templated_mail +import jwt +from datetime import datetime +from calendar import timegm +from uuid import uuid4 +import secrets + def send_otp_email(recipient_email, otp): from_email = settings.EMAIL_FROM_ADDRESS recipient_list = [recipient_email] @@ -36,3 +41,53 @@ def custom_error_response(detail, response_status): if not detail: detail = {} return Response({"error": detail, "status": "failed", "code": response_status}) + + +def get_user_data(attrs): + """ + used to decode token + """ + user_data = jwt.decode(jwt=attrs['token'], options={'verify_signature': False}, + algorithms=['RS256']) + return user_data + + +def generate_jwt_token(token_type: str, now_time: int, data: dict = dict): + """ + used to generate jwt token + """ + if type(data) == type: + data = {} + data.update({ + 'token_type': token_type, + 'iss': 'your_site_url', + 'iat': timegm(datetime.utcnow().utctimetuple()), + 'jti': uuid4().hex + }) + TOKEN_TYPE = ["access", "refresh"] + if token_type == TOKEN_TYPE[1]: + exp = now_time + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'] + else: + exp = now_time + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] + + data.update({ + "exp": timegm(exp.utctimetuple()) + }) + + + signing_key = secrets.token_hex(32) + + return jwt.encode(payload=data, key=signing_key, + algorithm='HS256') + + +def get_token(data: dict = dict): + """ create access and refresh token """ + now_time = datetime.utcnow() + access = generate_jwt_token('access', now_time, data) + refresh = generate_jwt_token('refresh', now_time, data) + + return { + 'access': access, + 'refresh': refresh + } \ No newline at end of file diff --git a/account/views.py b/account/views.py index 067b147..f992a05 100644 --- a/account/views.py +++ b/account/views.py @@ -2,6 +2,7 @@ from rest_framework import viewsets, status, views from rest_framework.decorators import action import random import logging +import jwt from django.contrib.auth import authenticate, login from guardian.models import Guardian from junior.models import Junior @@ -9,40 +10,125 @@ from account.models import UserProfile, UserPhoneOtp, UserEmailOtp from django.contrib.auth.models import User from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, - GoogleSignInSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer) + GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp -from django.conf import settings from account.utils import send_otp_email from account.utils import custom_response, custom_error_response -from django.core.mail import EmailMessage -from django.core.mail import send_mail -from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from templated_email import send_templated_mail +import google.oauth2.credentials +import google.auth.transport.requests +from rest_framework import status +from rest_framework.response import Response +import requests +from django.conf import settings +from .utils import get_token + + +class GoogleLoginMixin: + def google_login(self, request): + access_token = request.data.get('access_token') + user_type = request.data.get('user_type') + if not access_token: + return Response({'error': 'Access token is required.'}, status=status.HTTP_400_BAD_REQUEST) + + try: + # Validate the access token and obtain the user's email and name + credentials = google.oauth2.credentials.Credentials.from_authorized_user_info( + info={ + 'access_token': access_token, + 'token_uri': 'https://oauth2.googleapis.com/token', + 'client_id': settings.GOOGLE_CLIENT_ID, + 'client_secret': settings.GOOGLE_CLIENT_SECRET, + 'refresh_token': None, + } + ) + user_info_endpoint = f'https://www.googleapis.com/oauth2/v3/userinfo?access_token={access_token}' + headers = {'Authorization': f'Bearer {credentials.token}'} + response = requests.get(user_info_endpoint, headers=headers) + response.raise_for_status() + user_info = response.json() + email = user_info['email'] + first_name = user_info['given_name'] + last_name = user_info['family_name'] + profile_picture = user_info['picture'] + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + + # Check if the user exists in your database or create a new user + # ... + user_data = User.objects.filter(email__iexact=email) + if user_data.exists(): + if str(user_type) == '1': + junior_query = Junior.objects.filter(auth=user_data.last()).last() + serializer = JuniorSerializer(junior_query) + if str(user_type) == '2': + guardian_query = Guardian.objects.filter(user=user_data.last()).last() + serializer = GuardianSerializer(guardian_query) + return custom_response(SUCCESS_CODE['3003'], serializer.data, + response_status=status.HTTP_200_OK) + + if not User.objects.filter(email__iexact=email).exists(): + user_obj = User.objects.create(username=email, email=email, first_name=first_name, last_name=last_name) + if str(user_type) == '1': + junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True, + image=profile_picture) + serializer = JuniorSerializer(junior_query) + if str(user_type) == '2': + guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True, + image=profile_picture) + serializer = GuardianSerializer(guardian_query) + # Return a JSON response with the user's email and name + return custom_response(SUCCESS_CODE['3003'], serializer.data, + response_status=status.HTTP_200_OK) + + +class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet): + serializer_class = GoogleLoginSerializer + + def create(self, request): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + return self.google_login(request) + +class SigninWithApple(views.APIView): + """This API is for sign in with Apple for app.""" + def post(self, request): + token = request.data.get("access_token") + user_type = request.data.get("user_type") + if not token: + return custom_error_response(ERROR_CODE['2027'], response_status=status.HTTP_400_BAD_REQUEST) + try: + decoded_data = jwt.decode(token, options={"verify_signature": False}) + user_data = {"email": decoded_data.get('email'), "username": decoded_data.get('email'), "is_active": True} + if decoded_data.get("email"): + try: + user = User.objects.get(email=decoded_data.get("email")) + if str(user_type) == '1': + junior_query = Junior.objects.filter(auth=user).last() + serializer = JuniorSerializer(junior_query) + if str(user_type) == '2': + guardian_query = Guardian.objects.filter(user=user).last() + serializer = GuardianSerializer(guardian_query) + return custom_response(SUCCESS_CODE['3003'], serializer.data, + response_status=status.HTTP_200_OK) + + except User.DoesNotExist: + user = User.objects.create(**user_data) + if str(user_type) == '1': + junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True) + serializer = JuniorSerializer(junior_query) + if str(user_type) == '2': + guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True) + serializer = GuardianSerializer(guardian_query) + return custom_response(SUCCESS_CODE['3003'], serializer.data, + response_status=status.HTTP_200_OK) + except Exception as e: + logging.error(e) + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) -# class GoogleLoginAPIViewset(viewsets.ModelViewSet): -# """Google Login""" -# serializer_class = GoogleSignInSerializer -# -# def create(self, request, *args, **kwargs): -# """ -# Override default behaviour of create method -# """ -# provider_type = [] -# serializer = self.get_serializer(data=request.data) -# if serializer.is_valid(raise_exception=True): -# # provider = self.get_provider_view(request.data.get('provider')) -# # if User is not authenticated then send error message -# # if not provider.is_authenticated(request): -# # return custom_error_response({}, status.HTTP_400_BAD_REQUEST) -# -# user = serializer.save() -# if User.objects.filter(email__iexact=user.email).exists(): -# print("ppppppppppppp") -# return custom_response(SUCCESS_CODE["3003"], response_status=status.HTTP_200_OK) -# return custom_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) class UpdateProfileImage(views.APIView): permission_classes = [IsAuthenticated] diff --git a/base/messages.py b/base/messages.py index db53373..ffdc1c5 100644 --- a/base/messages.py +++ b/base/messages.py @@ -45,11 +45,12 @@ ERROR_CODE = { "2019": "Either File extension or File size doesn't meet the requirements", "2020": "Enter valid mobile number", "2021": "Already register", - "2022":"Invalid Guardian code", - "2023":"Invalid user", - "2024":"Email not verified", - "2025":"Invalid input. Expected a list of strings.", - "2026" : "New password should not same as old password" + "2022": "Invalid Guardian code", + "2023": "Invalid user", + "2024": "Email not verified", + "2025": "Invalid input. Expected a list of strings.", + "2026": "New password should not same as old password", + "2027": "data should contain `identityToken`" } SUCCESS_CODE = { # Success code for password diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 840de8e..fc88b1c 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -56,6 +56,7 @@ INSTALLED_APPS = [ 'account', 'junior', 'guardian', + # 'social_django' ] MIDDLEWARE = [ @@ -95,7 +96,7 @@ REST_FRAMEWORK = { ] } SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15), + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=50), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), } # Database @@ -171,6 +172,21 @@ CORS_ALLOW_HEADERS = ( """Static files (CSS, JavaScript, Images) https://docs.djangoproject.com/en/3.0/howto/static-files/""" +# AUTHENTICATION_BACKENDS = [ +# 'social_core.backends.google.GoogleOAuth2', +# 'django.contrib.auth.backends.ModelBackend', +# ] +# +# LOGIN_URL = 'login' +# LOGIN_REDIRECT_URL = 'home' +# LOGOUT_URL = 'logout' +# LOGOUT_REDIRECT_URL = 'login' + +# SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '' +# SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '' + +GOOGLE_CLIENT_ID = "182276566528-hlbjncs19fo502jposod6kft2p9k4grk.apps.googleusercontent.com" +GOOGLE_CLIENT_SECRET = "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" # EMAIL_BACKEND = os.getenv('EMAIL_BACKEND') # EMAIL_HOST = os.getenv('EMAIL_HOST')