<file_sep>from rest_framework import status
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from .serializers import GoogleSocialAuthSerializer, FacebookSocialAuthSerializer
from rest_framework.permissions import IsAuthenticated, AllowAny


class GoogleSocialAuthView(GenericAPIView):
    permission_classes = [AllowAny]
    serializer_class = GoogleSocialAuthSerializer

    def post(self, request):
        """
        POST with "auth_token"
        Send an "idtoken" as from google to get user information
        """
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)
        response = ((serializer.validated_data)['auth_token'])
        return Response(response)


class FacebookSocialAuthView(GenericAPIView):
    permission_classes = [AllowAny]
    serializer_class = FacebookSocialAuthSerializer

    def post(self, request):
        """
        POST with "auth_token"
        Send an access token as from facebook to get user information
        """
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)
        response = ((serializer.validated_data)['auth_token'])
        return Response(response)<file_sep>from rest_framework.exceptions import APIException
from django.utils.encoding import force_text
from rest_framework import status


class CustomException(APIException):
    status_code = status.HTTP_200_OK
    default_detail = 'Something went wrong.'

    def __init__(self, detail, **kwargs):
        if detail is not None:
            self.detail = detail
        else:
            self.detail = {'detail': force_text(self.default_detail)}
        for key, value in kwargs.items():
            if key == "status_code":
                self.status_code = value
<file_sep>from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()


class SocialApplication(models.Model):
    AUTH_PROVIDERS = [
        ('facebook', 'Facebook'),
        ('google', 'Google')
    ]
    provider = models.CharField(max_length=128, choices=AUTH_PROVIDERS)
    name = models.CharField(max_length=128)
    client_id = models.CharField(
        max_length=256, help_text="App ID, or consumer key")
    client_secret = models.CharField(
        max_length=256, help_text="API secret, client secret, or consumer secret")

    def __str__(self):
        return self.name


class SocialAccount(models.Model):
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="tfora_social_auth_account")
    provider = models.ForeignKey(SocialApplication, on_delete=models.CASCADE)

    def __str__(self):
        return self.user.username
<file_sep>from google.auth.transport import requests
from google.oauth2 import id_token
import facebook
from .utils import get_social_app_credentials


class Google:
    @staticmethod
    def validate(auth_token):
        idinfo = id_token.verify_oauth2_token(
            auth_token, requests.Request(), get_social_app_credentials("google").client_id)
        if 'accounts.google.com' in idinfo['iss']:
            return idinfo


class Facebook:
    @staticmethod
    def validate(auth_token):
        try:
            graph = facebook.GraphAPI(access_token=auth_token)
            profile = graph.request(
                '/me?fields=name,email,first_name,last_name,middle_name,picture,id')
            return profile
        except:
            return "The token is either invalid or has expired"<file_sep>from rest_framework import serializers
from rest_framework.exceptions import AuthenticationFailed
from . import providers
from .utils import register_social_user, get_social_app_credentials


class GoogleSocialAuthSerializer(serializers.Serializer):
    auth_token = serializers.CharField()

    def validate_auth_token(self, auth_token):
        user_data = providers.Google.validate(auth_token)

        try:
            user_data['sub']
        except:
            raise serializers.ValidationError(
                'The token is invalid or expired. Please login again.'
            )

        google_cred = get_social_app_credentials("google")
        if user_data['aud'] != google_cred.client_id:
            raise AuthenticationFailed('Unauthorized activity detected')

        user_id = user_data['sub']
        email = user_data['email']
        name = user_data['name']
        provider = 'google'

        return register_social_user(
            provider=provider,
            user_id=user_id,
            email=email,
            name=name,
            provider_data=user_data
        )


class FacebookSocialAuthSerializer(serializers.Serializer):
    auth_token = serializers.CharField()

    def validate_auth_token(self, auth_token):
        get_social_app_credentials("facebook")
        user_data = providers.Facebook.validate(auth_token)

        try:
            user_id = user_data.get('id')
            email = user_data.get('email')
            name = user_data.get('name')
            provider = 'facebook'

            return register_social_user(
                provider=provider,
                user_id=user_id,
                email=email,
                name=name,
                provider_data=user_data
            )

        except Exception as e:
            raise serializers.ValidationError(
                'The token  is invalid or expired. Please login again.'
            )<file_sep>import random
from . import signals
from .models import SocialApplication
from .exceptions import CustomException
from . models import User, SocialAccount


def generate_username(name):
    username = "".join(name.split(' ')).lower()
    if not User.objects.filter(username=username).exists():
        return username
    else:
        random_username = username + str(random.randint(0, 1000))
        return generate_username(random_username)


def get_social_app_credentials(provider):
    """Query social app credentials from db provided by Providers (google,facebook)"""
    try:
        return SocialApplication.objects.get(provider=provider)
    except:
        raise Exception(
            f"{provider.title()} credentials not found in Social Applications")


def get_user_social_account(user, provider):
    """User have already registered.Checking corresponding provider"""
    try:
        return SocialAccount.objects.get(user=user, provider__provider=provider)
    except:
        raise CustomException({
            'status': 403,
            "message": "Login with your username and password"
        })


def register_social_user(provider, user_id, email, name, provider_data):
    if isinstance(email, type(None)):
        raise CustomException({
            'status': 403,
            "message": "E-mail cannot be blank"
        })

    filtered_user_by_email = User.objects.filter(email=email)

    if filtered_user_by_email.exists():

        social_account_user = get_user_social_account(
            filtered_user_by_email[0], provider)

        if provider == social_account_user.provider.provider:

            signals.user_logged_in.send(
                sender="social_user_login",
                provider=provider,
                user=filtered_user_by_email[0],
                provider_data=provider_data
            )

            token = filtered_user_by_email[0].get_token()

            auth_data = {
                'refresh': str(token),
                'access': str(token.access_token),
            }

            data = {
                "status": 200,
                "auth": auth_data,
                "message": "Logged in successfully"
            }

            return data
        else:

            raise CustomException({
                'status': 403,
                "message": 'Please continue your login using ' + social_account_user.provider.provider
            })
    else:

        if provider == 'google':
            first_name = provider_data['given_name']
            last_name = provider_data['family_name']

        elif provider == 'facebook':
            first_name = provider_data['first_name']
            last_name = provider_data['last_name']

        kwargs = {
            'username': generate_username(name),
            'email': email,
            "is_active": True,
            "first_name": first_name,
            "last_name": last_name
        }

        new_user = User.objects.create(**kwargs)
        new_user.set_unusable_password()
        new_user.save()

        signals.user_registered.send(
            sender="social_user_register",
            provider=provider,
            user=new_user,
            provider_data=provider_data
        )

        token = new_user.get_token()

        auth_data = {
            'refresh': str(token),
            'access': str(token.access_token)
        }

        data = {
            "status": 200,
            "auth": auth_data,
            "message": "Registered successfully"
        }
        return data<file_sep>from django.dispatch import Signal, receiver
from .models import User, SocialAccount, SocialApplication
user_registered = Signal()
user_logged_in = Signal()


@receiver(user_registered)
def create_social_account(user, provider, provider_data, **kwargs):
    social_account = SocialAccount.objects.filter(
        user=user, provider__provider=provider)

    if not social_account.exists():
        app = SocialApplication.objects.get(provider=provider)
        SocialAccount.objects.create(user=user, provider=app)<file_sep>from django.apps import AppConfig


class TforaSocialAuthConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'tfora_social_auth'
<file_sep># tfora_social_auth

Easy django rest auth integration for social applications. (currently supports Google and Facebook)


## Quick Setup

Install package

	pip install tfora-social-auth


Add  `tfora_social_auth` app to INSTALLED_APPS in your django settings.py:

```python
INSTALLED_APPS = [
    ...
    'rest_framework',
    'tfora_social_auth',
    ...
]
```


	python manage.py migrate


#### Add URL patterns

```python
from tfora_social_auth.views import ( GoogleSocialAuthView, FacebookSocialAuthView )

urlpatterns = [
    path('social/google/', GoogleSocialAuthView.as_view()),
    path('social/facebook/', FacebookSocialAuthView.as_view()),
]

```


##### For Google login
- POST with "auth_token".
- Send an "idtoken" as from google to get user information

##### For Facebook login
- POST with "auth_token".
- Send an access token as from facebook to get user information

##### Extra token payload
You can add extra payload data to access token by the following method

```python
from rest_framework_simplejwt.tokens import RefreshToken

class CustomUserModel(AbstractUser):
    ...
    @staticmethod
    def get_token(user):
        token = RefreshToken.for_user(user)
        token["extra_key"] = "extra_value"
        return token
    ...

		
```
<file_sep>from django.contrib import admin
from .models import *
admin.site.register(SocialApplication)
admin.site.register(SocialAccount)
<file_sep>from django.urls import path
from .views import GoogleSocialAuthView, FacebookSocialAuthView

app_name = "tfora_social_auth"

urlpatterns = [
    path('google/', GoogleSocialAuthView.as_view()),
    path('facebook/', FacebookSocialAuthView.as_view()),
]
<file_sep>from distutils.core import setup
import os


setup(
    name='tfora_social_auth',
    packages=['tfora_social_auth'], 
    version='0.3',     
    license='MIT',
    description='Easy django rest auth integration for social applications (currently supports google and facebook)',
    author='<NAME>',                   
    author_email='<EMAIL>',      
    url='https://github.com/pvfarooq/tfora_social_auth',
    download_url='https://github.com/pvfarooq/tfora_social_auth/archive/refs/tags/0.3.tar.gz',
    keywords=['django', 'social login', 'allauth', 'rest_auth','google login','facebook login'],
    install_requires=[            
        'facebook-sdk',
        'google-auth',
        'requests',
        'djangorestframework'
    ],
    classifiers=[
        'Development Status :: 3 - Alpha',
        'Intended Audience :: Developers',
        'Topic :: Software Development :: Build Tools',
        'License :: OSI Approved :: MIT License', 
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.9',
    ],
)

