diff --git a/README.md b/README.md index d9a1039..50b3f06 100644 --- a/README.md +++ b/README.md @@ -89,14 +89,16 @@ Next follow these instructions. ``` python manage.py migrate ``` -8. To run the server: +8. To setup Social Auth for backend see [this](#Setup-Social-Auth). + +9. To run the server: ``` python manage.py runserver ``` -9. Navigate to `http://localhost:8000/` in your browser. -10. To change the port you may run `python manage.py runserver ` -11. To run the migrations run: `python manage.py migrate` -12. You can terminate the process by `Ctrl+C` in your terminal. +10. Navigate to `http://localhost:8000/` in your browser. +11. To change the port you may run `python manage.py runserver ` +12. To run the migrations run: `python manage.py migrate` +13. You can terminate the process by `Ctrl+C` in your terminal. Follow the given instructions for Login into the app. @@ -133,21 +135,39 @@ Add it to your .env file as follows: ``` export SENDGRID_API_KEY= ``` +3. `GOOGLE_CALLBACK_URL` - For using Google authentication the **Callback URL** is required by Google API. Add this env variable in `.env` file or export it to use **Callback URL** which you used while setting Up Google App. The default value is: `http://localhost:3000/login`. + +4. `SECRET_KEY` - This environment variable is required for running the backend. Add `SECRET_KEY` in `.env` file or export it by using `export SECRET_KEY=`. + +5. `DB_BACKEND` - This environment variable is used here to get the the backend class of the database. Different databases have different backends in django. You can read more about it [here](https://docs.djangoproject.com/en/3.1/ref/databases/). Its default backend is postgresql. + +6. `DB_NAME` - This environment variable is required to get the name of the database. By default, its value is `osp`. + +7. `DB_USERNAME` - This environment variable is required to get the **USERNAME** of the user with all privileges to the above mentioned database. + +8. `DB_PASSWORD` - This environment variable is required to get the **password** of the above mentioned user i.e. the user with all the privileges to the database. -3. `SECRET_KEY` - This environment variable is required for running the backend. Add `SECRET_KEY` in `.env` file or export it by using `export SECRET_KEY=`. +9. `DB_HOST` - It is used to get the database host from the env variables. For `docker` it must be set to `db` otherwise its default value is `localhost`. -4. `DB_BACKEND` - This environment variable is used here to get the the backend class of the database. Different databases have different backends in django. You can read more about it [here](https://docs.djangoproject.com/en/3.1/ref/databases/). Its default backend is postgresql. +10. `DB_PORT` - It is used to get the database port from the env variables. Different database backends have different ports. Its default value is of postgresql port i.e. `5432`. -5. `DB_NAME` - This environment variable is required to get the name of the database. By default, its value is `osp`. -6. `DB_USERNAME` - This environment variable is required to get the **USERNAME** of the user with all privileges to the above mentioned database. -7. `DB_PASSWORD` - This environment variable is required to get the **password** of the above mentioned user i.e. the user with all the privileges to the database. +# Setup Social Auth -8. `DB_HOST` - It is used to get the database host from the env variables. For `docker` it must be set to `db` otherwise its default value is `localhost`. +1. Create a Super User by running this command: `python manage.py createsuperuser`. +2. Login to [Django admin site](http://localhost:8000/admin/) using credentials of the previous step. +3. Go to **Sites dashboard** in admin site. (**URL**: http://localhost:8000/admin/sites/site/). +4. Click on `Add site` button and fill in the information as given in the image. -9. `DB_PORT` - It is used to get the database port from the env variables. Different database backends have different ports. Its default value is of postgresql port i.e. `5432`. + ![site_id](https://user-images.githubusercontent.com/56037184/109974910-0fa79b00-7d20-11eb-9826-44fdf6d770f9.png) + + **Note**: After saving this if the site id is not `1` then change the `SITE_ID` in settings.py with the new site id. +5. After this go to **Social Applications Dashboard**. (**URL**: http://localhost:8000/admin/socialaccount/socialapp/). +6. Add the credentials that you get after creating the Google app. Fill in the information as given in the image. + ![social_add](https://user-images.githubusercontent.com/44670961/110095166-9f058a80-7dc2-11eb-85eb-afb109f46663.png) + **For creating Google App & Creating ClientId & Client Secret see [this docs](https://developers.google.com/adwords/api/docs/guides/authentication#create_a_client_id_and_client_secret).** ## Testing To run the tests run: `python manage.py test`. diff --git a/main/settings.py b/main/settings.py index be8b771..7c7b0bb 100644 --- a/main/settings.py +++ b/main/settings.py @@ -45,6 +45,13 @@ "corsheaders", "osp", "token_auth", + # For Social Authentication + "dj_rest_auth", + "django.contrib.sites", + "allauth", + "allauth.account", + "allauth.socialaccount", + "allauth.socialaccount.providers.google", ] MIDDLEWARE = [ @@ -80,11 +87,16 @@ "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"], "DEFAULT_PARSER_CLASSES": ["rest_framework.parsers.JSONParser"], "DEFAULT_AUTHENTICATION_CLASSES": ( + "dj_rest_auth.jwt_auth.JWTCookieAuthentication", "rest_framework.authentication.SessionAuthentication", "rest_framework_simplejwt.authentication.JWTAuthentication", ), } +SITE_ID = 1 + +REST_USE_JWT = True + SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(days=2), "REFRESH_TOKEN_LIFETIME": timedelta(days=30), @@ -157,3 +169,10 @@ # https://docs.djangoproject.com/en/3.0/howto/static-files/ STATIC_URL = "/static/" + +ACCOUNT_EMAIL_VERIFICATION = "none" + +if os.environ.get("GOOGLE_CALLBACK_URL"): + GOOGLE_CALLBACK_URL = os.getenv("GOOGLE_CALLBACK_URL") +else: + GOOGLE_CALLBACK_URL = "http://localhost:3000/login" diff --git a/requirements.txt b/requirements.txt index e31b442..409bca4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,4 +33,7 @@ toml==0.10.1 typed-ast==1.4.1 urllib3==1.25.10 wrapt==1.12.1 -zulip==0.7.0 \ No newline at end of file +zulip==0.7.0 +django-allauth==0.44.0 +dj-rest-auth==2.1.3 +responses==0.12.1 \ No newline at end of file diff --git a/tests/test_google_oauth.py b/tests/test_google_oauth.py new file mode 100644 index 0000000..976b31f --- /dev/null +++ b/tests/test_google_oauth.py @@ -0,0 +1,79 @@ +import json + +import responses +from allauth.socialaccount.models import SocialApp +from django.contrib.auth import get_user_model +from django.contrib.sites.models import Site +from django.test import TestCase +from django.urls import reverse +from rest_framework.test import APIClient + + +class TestGoogleAuth(TestCase): + """ + Tests for Google Authentication !! + """ + + def setUp(self): + self.client = APIClient() + self.google_signup_url = reverse("google_login") + self.payload = {"access_token": "1234567", "id_token": "789456123"} + social_app = SocialApp.objects.create( + provider="google", + name="Google", + client_id="123456123456123456654321", + secret="654321123456123456987456123", + ) + site = Site.objects.get_current() + social_app.sites.add(site) + + @responses.activate + def test_google_auth(self): + """ + Testing Google Auth SignUp !! + """ + self._get_google_response() + user_count = get_user_model().objects.all().count() + + response = self.client.post(self.google_signup_url, data=self.payload, format="json") + self.assertEquals(response.status_code, 200) + # assertion to check new user is created. + self.assertEquals(get_user_model().objects.all().count(), user_count + 1) + + # Below two assertions are added to check REST_USE_JWT is True or not. + self.assertIn("access_token", response.data) + self.assertIn("refresh_token", response.data) + + # Assertion to check second request will not create a new user + response = self.client.post(self.google_signup_url, data=self.payload, format="json") + + self.assertEquals(get_user_model().objects.all().count(), user_count + 1) + + def _get_google_response(self): + """ + Private methods to get response from Google API Calls. + """ + + responses.add( + responses.POST, + "https://accounts.google.com/o/oauth2/v2/auth", + body=json.dumps({"access_token": "access_token", "id_token": "id_token"}), + status=200, + content_type="application/json", + ) + + responses.add( + responses.GET, + "https://www.googleapis.com/oauth2/v2/userinfo", + body=json.dumps( + { + "id": "456123", + "email": "testuser@gmail.com", + "picture": "https://lh3.googleusercontent.com/a-/saf456fq1fc894veqw1fsc2", + "type": "User", + "verified_email": "true", + } + ), + status=200, + content_type="application/json", + ) diff --git a/token_auth/urls.py b/token_auth/urls.py index bc2a6b6..eb79c7b 100644 --- a/token_auth/urls.py +++ b/token_auth/urls.py @@ -1,7 +1,8 @@ from django.conf.urls import url -from django.urls import path +from django.urls import include, path from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView +from token_auth.views.google_oauth import GoogleLogin from token_auth.views.register import RegisterView urlpatterns = [ @@ -14,4 +15,7 @@ # login URLs path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path("refresh/", TokenRefreshView.as_view(), name="token_refresh"), + # social-authentication + path("google/", GoogleLogin.as_view(), name="google_login"), + url(r"^accounts/", include("allauth.urls"), name="socialaccount_signup"), ] diff --git a/token_auth/views/google_oauth.py b/token_auth/views/google_oauth.py new file mode 100644 index 0000000..e73ffed --- /dev/null +++ b/token_auth/views/google_oauth.py @@ -0,0 +1,11 @@ +from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter +from allauth.socialaccount.providers.oauth2.client import OAuth2Client +from dj_rest_auth.registration.views import SocialLoginView +from django.conf import settings + + +class GoogleLogin(SocialLoginView): + authentication_classes = [] # disable authentication + adapter_class = GoogleOAuth2Adapter + callback_url = settings.GOOGLE_CALLBACK_URL + client_class = OAuth2Client