Skip to content
Snippets Groups Projects
Commit 319b652a authored by Chris Li's avatar Chris Li
Browse files

Merge branch 'chris/feature/social-auth'

parents 1c568062 e74aa83e
No related branches found
No related tags found
No related merge requests found
import json
import jwt
from jwt.algorithms import RSAAlgorithm
import requests
from django.contrib.auth.models import User
from django.shortcuts import render
from rest_framework import generics, HTTP_HEADER_ENCODING
from django.urls import reverse
from rest_framework import generics, status, serializers, HTTP_HEADER_ENCODING
from rest_framework.decorators import api_view, authentication_classes
from rest_framework.response import Response
from knox.auth import TokenAuthentication
from knox.models import AuthToken
from django.contrib.auth.backends import AllowAllUsersModelBackend
from core.models.social_account import SocialAccount
from core.serializers.login import LoginSerializer
from core.serializers.register import RegisterSerializer
from core.serializers.user import UserSerializer
from core.serializers.socialAuthSerializer import AppleUserInputSerializer, FacebookUserInputSerializer, GoogleUserInputSerializer
class RegisterAPI(generics.GenericAPIView):
......@@ -41,6 +49,125 @@ class LoginAPI(generics.GenericAPIView):
})
class AppleLogin(generics.GenericAPIView):
serializer_class = AppleUserInputSerializer
APPLE_PUBLIC_KEY_URL = "https://appleid.apple.com/auth/keys"
APPLE_APP_ID = "com.hamsterwhat.ios"
def _decode_apple_user_token(self, apple_user_token):
key_payload = requests.get(self.APPLE_PUBLIC_KEY_URL).json()
for public_key in key_payload["keys"]:
public_key = RSAAlgorithm.from_jwk(json.dumps(public_key))
try:
token = jwt.decode(apple_user_token, public_key, audience=[self.APPLE_APP_ID, 'host.exp.Exponent'], algorithms=['RS256'])
except jwt.exceptions.ExpiredSignatureError as e:
serializers.ValidationError({"id_token": "That token has expired."})
except jwt.exceptions.InvalidAudienceError as e:
serializers.ValidationError({"id_token": "That token's audience did not match."})
except Exception as e:
continue
if token is None:
serializers.ValidationError({"id_token": "That token is invalid."})
return token
def post(self, request):
print(request.data)
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
id_token = serializer.validated_data.get('id_token')
data_from_id_token = self._decode_apple_user_token(id_token)
print(data_from_id_token)
identity = 'apple_' + data_from_id_token.get('sub')
if SocialAccount.objects.filter(identity=identity).exists():
user = SocialAccount.objects.filter(identity=identity).first().user
else:
user, created = User.objects.get_or_create(
username=identity,
password=User.objects.make_random_password(),
email=data_from_id_token.get('email', None),
)
social_account = SocialAccount(identity=identity, user=user)
social_account.save()
token = AuthToken.objects.create(user)
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token[1],
})
class GoogleLogin(generics.GenericAPIView):
serializer_class = GoogleUserInputSerializer
GOOGLE_API = "https://www.googleapis.com/userinfo/v2/me"
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
access_token = serializer.validated_data.get('access_token')
req = requests.get(self.GOOGLE_API, params={'access_token': access_token})
data_from_api = req.json()
identity = 'google_' + data_from_api.get('id')
if SocialAccount.objects.filter(identity=identity).exists():
user = SocialAccount.objects.filter(identity=identity).first().user
else:
user, created = User.objects.get_or_create(
username=identity,
password=User.objects.make_random_password(),
email=data_from_api.get('email', None),
)
if created:
user.first_name = data_from_api.get('given_name', user.first_name)
user.last_name = data_from_api.get('family_name', user.last_name)
user.save()
user.profile.photo = data_from_api.get('picture', user.profile.photo)
user.profile.save()
social_account = SocialAccount(identity=identity, user=user)
social_account.save()
token = AuthToken.objects.create(user)
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token[1],
})
class FacebookLogin(generics.GenericAPIView):
serializer_class = FacebookUserInputSerializer
FACEBOOK_API = "https://graph.facebook.com/me"
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
access_token = serializer.validated_data.get('access_token')
req = requests.get(self.FACEBOOK_API, params={'fields': 'id,name,email,first_name,last_name,picture', 'access_token': access_token})
data_from_api = req.json()
identity = 'fb_' + data_from_api.get('id')
if SocialAccount.objects.filter(identity=identity).exists():
user = SocialAccount.objects.filter(identity=identity).first().user
else:
user, created = User.objects.get_or_create(
username=identity,
password=User.objects.make_random_password(),
email=data_from_api.get('email', None),
)
if created:
user.first_name = data_from_api.get('first_name', user.first_name)
user.last_name = data_from_api.get('last_name', user.last_name)
user.save()
user.profile.photo = data_from_api.get('picture', {}).get('data', {}).get('url', user.profile.photo)
user.profile.save()
social_account = SocialAccount(identity=identity, user=user)
social_account.save()
token = AuthToken.objects.create(user)
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token[1],
})
@api_view(['GET'])
@authentication_classes([])
def validate_token(request):
......
from django.contrib.auth.models import User
from django.db import models
from .utils import UUIDModel
class SocialAccount(UUIDModel):
identity = models.CharField(max_length=100, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self) -> str:
return self.identity
from rest_framework import serializers
class AppleUserInputSerializer(serializers.Serializer):
id_token = serializers.CharField(required=True, allow_blank=False)
class GoogleUserInputSerializer(serializers.Serializer):
access_token = serializers.CharField(required=True, allow_blank=False)
class FacebookUserInputSerializer(serializers.Serializer):
access_token = serializers.CharField(required=True, allow_blank=False)
......@@ -2,7 +2,7 @@ from django.urls import path, include
from knox import views as knox_views
from rest_framework import routers
from core.api.auth import RegisterAPI, LoginAPI, validate_token, verify_user_and_activate
from core.api.auth import RegisterAPI, LoginAPI, AppleLogin, GoogleLogin, FacebookLogin, validate_token, verify_user_and_activate
from core.api.password import ChangePasswordView
from core.api.profile import ProfileViewSet
......@@ -16,6 +16,9 @@ urlpatterns += [
path('api/auth/activate/<token>', verify_user_and_activate, name='activate'),
path('api/auth/login', LoginAPI.as_view(), name='login'),
path('api/auth/logout', knox_views.LogoutView.as_view(), name='logout'),
path('api/auth/apple', AppleLogin.as_view(), name='apple_login'),
path('api/auth/google', GoogleLogin.as_view(), name='google_login'),
path('api/auth/facebook', FacebookLogin.as_view(), name='facebook_login'),
path('api/auth/validate-token', validate_token, name='validate-token'),
# passwd
path('api/change-password', ChangePasswordView.as_view(), name='change-password'),
......
......@@ -53,7 +53,7 @@ REST_FRAMEWORK = {
}
REST_KNOX = {
'TOKEN_TTL': timedelta(days=3),
'TOKEN_TTL': timedelta(days=7),
'AUTO_REFRESH': True,
}
......@@ -129,7 +129,7 @@ TIME_ZONE = 'America/Toronto'
USE_I18N = True
USE_TZ = True
USE_TZ = False
# Static files (CSS, JavaScript, Images)
......
......@@ -11,6 +11,8 @@ urlpatterns = router.urls
urlpatterns += [
path('', include('core.urls')),
# path('dj-rest-auth/', include('dj_rest_auth.urls')),
# path('dj-rest-auth/registration/', include('dj_rest_auth.registration.urls')),
path('admin/', admin.site.urls),
path('docs/', include_docs_urls(title='ECE651 Backend API Document', description='This API document includes all ednpoints that has been implemented.')),
]
......
env.yml 0 → 100644
name: django
channels:
- defaults
dependencies:
- python=3.9
- django
- pyjwt
- cryptography
- djangorestframework
- django-extensions
- pillow
prefix: /Users/lichenyang/opt/anaconda3/envs/django
......@@ -38,3 +38,6 @@ dependencies:
- pygments
- django-filter
- django-guardian
- dj-rest-auth
- django-allauth
- djangorestframework-simplejwt
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment