diff --git a/backend/core/settings.py b/backend/core/settings.py
index f06c5ac..c795b7c 100644
--- a/backend/core/settings.py
+++ b/backend/core/settings.py
@@ -79,11 +79,14 @@ REST_FRAMEWORK = {
REST_USE_JWT = True
+GOOGLE_CLIENT_ID = config('GOOGLE_CLIENT_ID', default='fake-client-id')
+GOOGLE_CLIENT_SECRET = config('GOOGLE_CLIENT_SECRET', default='fake-client-secret')
+
SOCIALACCOUNT_PROVIDERS = {
'google': {
'APP': {
- 'client_id': config('GOOGLE_CLIENT_ID', default='fake-client-id'),
- 'secret': config('GOOGLE_CLIENT_SECRET', default='fake-client-secret'),
+ 'client_id': GOOGLE_CLIENT_ID,
+ 'secret': GOOGLE_CLIENT_SECRET,
'key': ''
},
"SCOPE": [
diff --git a/backend/users/migrations/0002_customuser_refresh_token.py b/backend/users/migrations/0002_customuser_refresh_token.py
new file mode 100644
index 0000000..25d9215
--- /dev/null
+++ b/backend/users/migrations/0002_customuser_refresh_token.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.6 on 2023-11-01 17:57
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('users', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='customuser',
+ name='refresh_token',
+ field=models.CharField(blank=True, max_length=255, null=True),
+ ),
+ ]
diff --git a/backend/users/models.py b/backend/users/models.py
index e473572..64d976d 100644
--- a/backend/users/models.py
+++ b/backend/users/models.py
@@ -19,6 +19,9 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
# Custom manager
objects = CustomAccountManager()
+ # Google API
+ refresh_token = models.CharField(max_length=255, blank=True, null=True)
+
# Fields for authentication
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name']
diff --git a/backend/users/urls.py b/backend/users/urls.py
index fd03be1..28f77da 100644
--- a/backend/users/urls.py
+++ b/backend/users/urls.py
@@ -9,5 +9,5 @@ urlpatterns = [
path('token/custom_obtain/', ObtainTokenPairWithCustomView.as_view(), name='token_create_custom'),
path('hello/', GreetingView.as_view(), name='hello_world'),
path('dj-rest-auth/google/', GoogleLogin.as_view(), name="google_login"),
- path('auth/google/', GoogleRetrieveUserInfo.as_view())
+ path('auth/google/', GoogleRetrieveUserInfo.as_view()),
]
\ No newline at end of file
diff --git a/backend/users/views.py b/backend/users/views.py
index cb63de2..591c609 100644
--- a/backend/users/views.py
+++ b/backend/users/views.py
@@ -3,6 +3,7 @@
import json
import requests
+from django.conf import settings
from django.contrib.auth.hashers import make_password
from rest_framework import status
@@ -16,6 +17,8 @@ from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from dj_rest_auth.registration.views import SocialLoginView
+from google_auth_oauthlib.flow import InstalledAppFlow
+
from .serializers import MyTokenObtainPairSerializer, CustomUserSerializer
from .managers import CustomAccountManager
from .models import CustomUser
@@ -96,17 +99,28 @@ class GoogleRetrieveUserInfo(APIView):
Retrieve user information from Google and create a user if not exists.
"""
permission_classes = (AllowAny,)
+ client_config = {"web":{"client_id": settings.GOOGLE_CLIENT_ID,
+ "project_id":"turtask","auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_secret": settings.GOOGLE_CLIENT_SECRET,
+ }
+ }
+ scopes = [
+ 'https://www.googleapis.com/auth/userinfo.email',
+ 'https://www.googleapis.com/auth/userinfo.profile',
+ 'https://www.googleapis.com/auth/calendar.readonly',
+ ]
def post(self, request):
- access_token = request.data.get("token")
-
- user_info = self.get_google_user_info(access_token)
-
- if 'error' in user_info:
- error_message = 'Wrong Google token or the token has expired.'
- return Response({'message': error_message, 'error': user_info['error']})
-
- user = self.get_or_create_user(user_info)
+ code = request.data.get("code")
+ payload = self.exchange_authorization_code(code=code)
+ if 'error' in payload:
+ return Response({'error': payload['error']})
+ user_info = self.call_google_api(api_url='https://www.googleapis.com/oauth2/v2/userinfo?alt=json',
+ access_token=payload['access_token'])
+ payload['email'] = user_info['email']
+ user = self.get_or_create_user(payload)
token = RefreshToken.for_user(user)
response = {
@@ -117,19 +131,52 @@ class GoogleRetrieveUserInfo(APIView):
return Response(response)
- def get_google_user_info(self, access_token):
- url = 'https://www.googleapis.com/oauth2/v2/userinfo'
- payload = {'access_token': access_token}
- response = requests.get(url, params=payload)
+ def get(self, request):
+ """Get authorization url."""
+ flow = InstalledAppFlow.from_client_config(client_config=self.client_config,
+ scopes=self.scopes)
+ flow.redirect_uri = 'http://localhost:5173/'
+ authorization_url, state = flow.authorization_url(
+ access_type='offline',
+ # include_granted_scopes='true',
+ )
+ return Response({'url': authorization_url})
+
+ def exchange_authorization_code(self, code):
+ """Exchange authorization code for access, id, refresh token."""
+ url = 'https://oauth2.googleapis.com/token'
+ payload = {
+ 'code': code,
+ 'client_id': settings.GOOGLE_CLIENT_ID,
+ 'client_secret': settings.GOOGLE_CLIENT_SECRET,
+ 'redirect_uri': 'postmessage',
+ 'grant_type': 'authorization_code',
+ }
+ response = requests.post(url, data=payload)
return json.loads(response.text)
def get_or_create_user(self, user_info):
+ """Get or create a user based on email."""
try:
user = CustomUser.objects.get(email=user_info['email'])
+ user.refresh_token = user_info['refresh_token']
+ user.save()
except CustomUser.DoesNotExist:
user = CustomUser()
user.username = user_info['email']
user.password = make_password(CustomAccountManager().make_random_password())
user.email = user_info['email']
+ user.refresh_token = user_info['refresh_token']
user.save()
- return user
\ No newline at end of file
+ return user
+
+ def call_google_api(self, api_url, access_token):
+ """Call Google API with access token."""
+ headers = {
+ 'Authorization': f'Bearer {access_token}'
+ }
+
+ response = requests.get(api_url, headers=headers)
+ if response.status_code == 200:
+ return response.json()
+ raise Exception('Google API Error', response)
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 8c7d50d..7333c4a 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -6,6 +6,7 @@ import IconSideNav from './components/IconSideNav';
import AuthenticantionPage from './components/authentication/AuthenticationPage';
import SignUpPage from './components/authentication/SignUpPage';
import NavBar from './components/Nav/Navbar';
+import Home from './components/Home';
const App = () => {
@@ -14,7 +15,7 @@ const App = () => {
- This is Home page!
} />
+ }/>
}/>
}/>
}/>
diff --git a/frontend/src/api/axiosapi.jsx b/frontend/src/api/axiosapi.jsx
index 211cf14..313d6f1 100644
--- a/frontend/src/api/axiosapi.jsx
+++ b/frontend/src/api/axiosapi.jsx
@@ -67,7 +67,7 @@ const googleLogin = async (token) => {
let res = await axios.post(
"http://localhost:8000/api/auth/google/",
{
- token: token,
+ code: token,
}
);
// console.log('service google login res: ', res);
diff --git a/frontend/src/components/Home.jsx b/frontend/src/components/Home.jsx
new file mode 100644
index 0000000..3089df5
--- /dev/null
+++ b/frontend/src/components/Home.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+function HomePage() {
+ return (
+
+
Welcome to My Website
+
+ );
+}
+
+export default HomePage;
diff --git a/frontend/src/components/Nav/Navbar.jsx b/frontend/src/components/Nav/Navbar.jsx
index 331ab9e..f88d821 100644
--- a/frontend/src/components/Nav/Navbar.jsx
+++ b/frontend/src/components/Nav/Navbar.jsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { Link } from 'react-router-dom';
+import { Link, useNavigate } from 'react-router-dom';
import IsAuthenticated from '../authentication/IsAuthenticated';
import axiosapi from '../../api/axiosapi';
import AppBar from '@mui/material/AppBar';
@@ -28,6 +28,7 @@ const settings = {
};
function NavBar() {
+ const Navigate = useNavigate();
const [anchorElNav, setAnchorElNav] = React.useState(null);
const [anchorElUser, setAnchorElUser] = React.useState(null);
@@ -51,7 +52,7 @@ function NavBar() {
const logout = () => {
// Log out the user, clear tokens, and navigate to the "/testAuth" route
axiosapi.apiUserLogout();
- Navigate('/testAuth');
+ Navigate('/');
}
return (
diff --git a/frontend/src/components/authentication/AuthenticationPage.jsx b/frontend/src/components/authentication/AuthenticationPage.jsx
index 791cdd9..0955342 100644
--- a/frontend/src/components/authentication/AuthenticationPage.jsx
+++ b/frontend/src/components/authentication/AuthenticationPage.jsx
@@ -1,7 +1,6 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useGoogleLogin } from '@react-oauth/google';
-
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import CssBaseline from '@mui/material/CssBaseline';
@@ -17,6 +16,7 @@ import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import Typography from '@mui/material/Typography';
import { createTheme, ThemeProvider } from '@mui/material/styles';
+import refreshAccessToken from './refreshAcesstoken';
import axiosapi from '../../api/axiosapi';
@@ -24,8 +24,8 @@ function Copyright(props) {
return (
{'Copyright © '}
-
- Your Website
+
+ TurTask
{' '}
{new Date().getFullYear()}
{'.'}
@@ -40,6 +40,12 @@ export default function SignInSide() {
const Navigate = useNavigate();
+ useEffect(() => {
+ if (!refreshAccessToken()) {
+ Navigate("/");
+ }
+ }, []);
+
const [email, setEmail] = useState("");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
@@ -76,27 +82,12 @@ export default function SignInSide() {
});
}
- const responseGoogle = async (response) => {
- // Handle Google login response
- let googleResponse = await axiosapi.googleLogin(response.access_token);
- console.log('Google Response:\n', googleResponse);
-
- if (googleResponse.status === 200) {
- // Store Google login tokens and set the authorization header on success
- localStorage.setItem('access_token', googleResponse.data.access_token);
- localStorage.setItem('refresh_token', googleResponse.data.refresh_token);
- axiosapi.axiosInstance.defaults.headers['Authorization'] = "Bearer " + googleResponse.data.access_token;
- Navigate('/');
- }
- }
-
const googleLoginImplicit = useGoogleLogin({
- // flow: 'auth-code',
- onSuccess: async (response) => {
- console.log(response);
-
+ flow: 'auth-code',
+ redirect_uri: 'postmessage',
+ onSuccess: async (response) => {
try {
- const loginResponse = await axiosapi.googleLogin(response.access_token);
+ const loginResponse = await axiosapi.googleLogin(response.code);
if (loginResponse && loginResponse.data) {
const { access_token, refresh_token } = loginResponse.data;
diff --git a/frontend/src/components/authentication/IsAuthenticated.jsx b/frontend/src/components/authentication/IsAuthenticated.jsx
index e3691c8..48322de 100644
--- a/frontend/src/components/authentication/IsAuthenticated.jsx
+++ b/frontend/src/components/authentication/IsAuthenticated.jsx
@@ -1,48 +1,19 @@
import { useState, useEffect } from 'react';
-import axiosapi from '../../api/axiosapi';
function IsAuthenticated() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
- const checkAuthentication = async () => {
- const access_token = localStorage.getItem('access_token');
- const refresh_token = localStorage.getItem('refresh_token');
+ const access_token = localStorage.getItem('access_token');
- if (access_token && refresh_token) {
- const isAccessTokenExpired = checkIfAccessTokenExpired(access_token);
-
- if (!isAccessTokenExpired) {
- setIsAuthenticated(true);
- } else {
- try {
- // Attempt to refresh the access token using the refresh token
- const response = await axiosapi.refreshAccessToken(refresh_token);
- if (response.status === 200) {
- const newAccessToken = response.data.access_token;
- localStorage.setItem('access_token', newAccessToken);
- setIsAuthenticated(true);
- } else {
- setIsAuthenticated(false);
- }
- } catch (error) {
- setIsAuthenticated(false);
- }
- }
- } else {
- setIsAuthenticated(false);
- }
- };
-
- checkAuthentication();
+ if (access_token) {
+ setIsAuthenticated(true);
+ } else {
+ setIsAuthenticated(false);
+ }
}, []);
- const checkIfAccessTokenExpired = (accessToken) => {
- // Need to change logic again!
- return !accessToken;
- };
-
return isAuthenticated;
}
-export default IsAuthenticated;
+export default IsAuthenticated;
\ No newline at end of file
diff --git a/frontend/src/components/authentication/SignUpPage.jsx b/frontend/src/components/authentication/SignUpPage.jsx
index 3e33a86..7739016 100644
--- a/frontend/src/components/authentication/SignUpPage.jsx
+++ b/frontend/src/components/authentication/SignUpPage.jsx
@@ -21,8 +21,8 @@ function Copyright(props) {
return (
{'Copyright © '}
-
- Your Website
+
+ TurTask
{' '}
{new Date().getFullYear()}
{'.'}
diff --git a/frontend/src/components/authentication/refreshAcessToken.jsx b/frontend/src/components/authentication/refreshAcessToken.jsx
new file mode 100644
index 0000000..89204d5
--- /dev/null
+++ b/frontend/src/components/authentication/refreshAcessToken.jsx
@@ -0,0 +1,37 @@
+import axios from 'axios';
+
+async function refreshAccessToken() {
+ const refresh_token = localStorage.getItem('refresh_token');
+ const access_token = localStorage.getItem('access_token');
+
+ if (access_token) {
+ return true;
+ }
+
+ if (!refresh_token) {
+ return false;
+ }
+
+ const refreshUrl = 'http://127.0.0.1:8000/api/token/refresh/';
+
+ try {
+ const response = await axios.post(refreshUrl, { refresh: refresh_token });
+
+ if (response.status === 200) {
+ // Successful refresh - save the new access token and refresh token
+ const newAccessToken = response.data.access;
+ const newRefreshToken = response.data.refresh;
+
+ localStorage.setItem('access_token', newAccessToken);
+ localStorage.setItem('refresh_token', newRefreshToken);
+
+ return true;
+ } else {
+ return false;
+ }
+ } catch (error) {
+ return false;
+ }
+}
+
+export default refreshAccessToken;