Skip to content

Commit b6f70df

Browse files
committed
add Google oauth
1 parent 27c5f0d commit b6f70df

21 files changed

+455
-79
lines changed

backend/backend/settings.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
ALLOWED_HOSTS = []
3434

35+
SITE_ID = 1
3536

3637
# Application definition
3738

@@ -42,12 +43,20 @@
4243
"django.contrib.sessions",
4344
"django.contrib.messages",
4445
"django.contrib.staticfiles",
46+
'django.contrib.sites',
4547
"rest_framework",
4648
"myapp",
4749
"corsheaders",
4850
'rest_framework_simplejwt.token_blacklist',
4951
'drf_yasg',
50-
'backtest'
52+
'backtest',
53+
"rest_framework.authtoken",
54+
'allauth',
55+
'allauth.account',
56+
'allauth.socialaccount',
57+
'allauth.socialaccount.providers.google',
58+
'dj_rest_auth',
59+
'dj_rest_auth.registration',
5160
]
5261

5362
MIDDLEWARE = [
@@ -142,6 +151,12 @@
142151

143152
AUTH_USER_MODEL = 'myapp.CustomUser'
144153

154+
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
155+
ACCOUNT_USERNAME_REQUIRED = False
156+
ACCOUNT_AUTHENTICATION_METHOD = 'email'
157+
ACCOUNT_EMAIL_REQUIRED = True
158+
ACCOUNT_EMAIL_VERIFICATION = 'optional'
159+
145160
REST_FRAMEWORK = {
146161
'DEFAULT_AUTHENTICATION_CLASSES': [
147162
'rest_framework_simplejwt.authentication.JWTAuthentication',
@@ -203,4 +218,17 @@
203218
'MAX_ENTRIES': 1000, # Maximum number of entries in the cache
204219
},
205220
}
221+
}
222+
223+
SOCIALACCOUNT_PROVIDERS = {
224+
'google': {
225+
'SCOPE': [
226+
'profile',
227+
'email',
228+
],
229+
'AUTH_PARAMS': {
230+
'access_type': 'online',
231+
},
232+
'OAUTH_PKCE_ENABLED': True,
233+
}
206234
}

backend/myapp/admin.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
11
from django.contrib import admin
2+
from django.contrib.auth.admin import UserAdmin
3+
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
4+
from .models import CustomUser
25

36
# Register your models here.
7+
class CustomUserAdmin(UserAdmin):
8+
add_form = UserCreationForm
9+
form = UserChangeForm
10+
model = CustomUser
11+
list_display = ['email', 'first_name', 'last_name', 'is_staff']
12+
search_fields = ['email', 'first_name', 'last_name']
13+
ordering = ['email'] # Change 'username' to 'email'
14+
15+
# Fields to be used in displaying the User model.
16+
fieldsets = (
17+
(None, {'fields': ('email', 'password')}),
18+
('Personal info', {'fields': ('first_name', 'last_name')}),
19+
('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser')}),
20+
('Important dates', {'fields': ('last_login', 'date_joined')}),
21+
)
22+
23+
# Fields to be used when creating a user.
24+
add_fieldsets = (
25+
(None, {
26+
'classes': ('wide',),
27+
'fields': ('email', 'password1', 'password2', 'first_name', 'last_name', 'is_active', 'is_staff', 'is_superuser'),
28+
}),
29+
)
30+
31+
# Register CustomUserAdmin with the admin site
32+
admin.site.register(CustomUser, CustomUserAdmin)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 5.0.6 on 2024-05-14 14:37
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('myapp', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='customuser',
15+
name='first_name',
16+
field=models.CharField(max_length=128),
17+
),
18+
migrations.AlterField(
19+
model_name='customuser',
20+
name='last_name',
21+
field=models.CharField(max_length=128),
22+
),
23+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.0.6 on 2024-05-16 12:16
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('myapp', '0002_alter_customuser_first_name_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='customuser',
15+
name='last_login',
16+
field=models.DateTimeField(auto_now=True),
17+
),
18+
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 5.0.6 on 2024-05-16 12:30
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('myapp', '0003_alter_customuser_last_login'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='customuser',
15+
name='auth_provider',
16+
field=models.CharField(default='email', max_length=50),
17+
),
18+
migrations.AlterField(
19+
model_name='customuser',
20+
name='last_login',
21+
field=models.DateTimeField(auto_now=True, null=True),
22+
),
23+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.0.6 on 2024-05-16 12:40
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('myapp', '0004_customuser_auth_provider_alter_customuser_last_login'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='customuser',
15+
name='last_login',
16+
field=models.DateTimeField(blank=True, null=True, verbose_name='last login'),
17+
),
18+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 5.0.6 on 2024-05-17 13:19
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('myapp', '0005_alter_customuser_last_login'),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name='customuser',
15+
name='auth_provider',
16+
),
17+
]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generated by Django 5.0.6 on 2024-05-17 14:37
2+
3+
import django.contrib.auth.validators
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('myapp', '0006_remove_customuser_auth_provider'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='customuser',
16+
name='username',
17+
field=models.CharField(default='placeholder', error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
18+
preserve_default=False,
19+
),
20+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 5.0.6 on 2024-05-17 15:16
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('myapp', '0007_customuser_username'),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name='customuser',
15+
name='username',
16+
),
17+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 5.0.6 on 2024-05-22 14:48
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('myapp', '0008_remove_customuser_username'),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name='customuser',
15+
options={},
16+
),
17+
]

backend/myapp/models.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from django.db import models
2-
from django.contrib.auth.models import AbstractUser, BaseUserManager
2+
from django.contrib.auth.models import AbstractUser, BaseUserManager, AbstractBaseUser, PermissionsMixin
33

44
class CustomUserManager(BaseUserManager):
55
def create_user(self, email, password=None, first_name=None, last_name=None, **extra_fields):
@@ -23,7 +23,7 @@ def create_superuser(self, email, password=None, **extra_fields):
2323
return self.create_user(email, password, **extra_fields)
2424

2525

26-
class CustomUser(AbstractUser):
26+
class CustomUser(AbstractBaseUser, PermissionsMixin):
2727
email = models.EmailField(unique=True)
2828
password = models.CharField(max_length=128)
2929
first_name = models.CharField(max_length=128, null=False)
@@ -32,7 +32,6 @@ class CustomUser(AbstractUser):
3232
is_staff = models.BooleanField(default=False)
3333
is_superuser = models.BooleanField(default=False)
3434
date_joined = models.DateTimeField(auto_now_add=True)
35-
username = None
3635

3736
objects = CustomUserManager()
3837

backend/myapp/urls.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.contrib import admin
22
from rest_framework import routers
33
from django.urls import path, include
4-
from .views import Register, api_home, UserView,Logout
4+
from .views import Register, api_home, UserView,Logout, GoogleLogin
55
from rest_framework_simplejwt.views import (
66
TokenObtainPairView,
77
TokenRefreshView,
@@ -13,5 +13,6 @@
1313
path("register", Register.as_view(), name="register"),
1414
path("user", UserView.as_view(), name="userview"),
1515
path("logout", Logout.as_view(), name='logout'),
16-
path("", api_home)
16+
path("", api_home),
17+
path('google-login/', GoogleLogin.as_view(), name='google_login')
1718
]

backend/myapp/views.py

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
from rest_framework_simplejwt.authentication import JWTAuthentication
99
from rest_framework.permissions import IsAuthenticated
1010
from rest_framework_simplejwt.tokens import AccessToken
11-
from drf_yasg import openapi
1211
from drf_yasg.utils import swagger_auto_schema
12+
from dj_rest_auth.registration.views import SocialLoginView
13+
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
14+
from allauth.socialaccount.models import SocialApp
15+
import requests
1316

1417
# Create your views here.
1518

@@ -74,4 +77,81 @@ def post(self, request):
7477
return Response({"success": "User logged out successfully."}, status=status.HTTP_200_OK)
7578
except Exception as e:
7679
print(request.data)
77-
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
80+
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
81+
82+
83+
84+
class GoogleLogin(SocialLoginView):
85+
adapter_class = GoogleOAuth2Adapter
86+
87+
def post(self, request, *args, **kwargs):
88+
89+
code = request.data.get('code')
90+
if not code:
91+
return Response({'detail': 'Code is missing'}, status=status.HTTP_400_BAD_REQUEST)
92+
93+
try:
94+
# Retrieve the Google social app
95+
app = SocialApp.objects.get(provider="google")
96+
97+
# Exchange the authorization code for an access token
98+
token_url = "https://oauth2.googleapis.com/token"
99+
token_data = {
100+
"code": code,
101+
"client_id": app.client_id,
102+
"client_secret": app.secret,
103+
"redirect_uri": "http://localhost:3000", # Make sure this matches the redirect_uri in your frontend config
104+
"grant_type": "authorization_code",
105+
}
106+
token_response = requests.post(token_url, data=token_data)
107+
token_response_data = token_response.json()
108+
access_token = token_response_data.get('access_token')
109+
110+
if not access_token:
111+
return Response({'detail': 'Failed to obtain access token from Google'}, status=status.HTTP_400_BAD_REQUEST)
112+
113+
# Retrieve user info from Google
114+
user_info_url = "https://www.googleapis.com/oauth2/v2/userinfo"
115+
user_info_response = requests.get(user_info_url, headers={"Authorization": f"Bearer {access_token}"})
116+
user_info = user_info_response.json()
117+
118+
# Extract user details
119+
email = user_info.get('email')
120+
first_name = user_info.get('given_name')
121+
last_name = user_info.get('family_name')
122+
password=user_info.get('id')+user_info.get('email')+user_info.get('locale')
123+
124+
125+
if not email:
126+
return Response({'detail': 'Failed to obtain user info from Google'}, status=status.HTTP_400_BAD_REQUEST)
127+
128+
# Create or get the user from your database
129+
user_data = {
130+
'email': email,
131+
'password': password,
132+
'first_name': first_name,
133+
'last_name': last_name,
134+
135+
}
136+
print("User data to be serialized:", user_data)
137+
138+
if not CustomUser.objects.filter(email=email).exists():
139+
serializer = UserSerializer(data=user_data)
140+
if serializer.is_valid():
141+
user = CustomUser.objects.create_user(email=email, password=password, last_name=last_name, first_name=first_name)
142+
else:
143+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
144+
else:
145+
user = CustomUser.objects.get(email=email)
146+
147+
# Generate JWT token using MyTokenObtainPairSerializer
148+
refresh = RefreshToken.for_user(user)
149+
return Response({
150+
'refresh': str(refresh),
151+
'access': str(refresh.access_token),
152+
}, status=status.HTTP_200_OK)
153+
154+
except SocialApp.DoesNotExist:
155+
return Response({'detail': 'Google SocialApp configuration is missing.'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
156+
except Exception as e:
157+
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)

0 commit comments

Comments
 (0)