Fix Google Login / Restyle Login-Logout

This commit is contained in:
sosokker 2023-10-31 04:09:18 +07:00
parent f032ae50d6
commit 67829fc292
12 changed files with 445 additions and 188 deletions

View File

@ -46,19 +46,19 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'tasks',
'django.contrib.sites',
'tasks',
'users',
'rest_framework',
'corsheaders',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'rest_framework',
'dj_rest_auth',
'dj_rest_auth.registration',
'rest_framework.authtoken',
@ -70,10 +70,10 @@ REST_FRAMEWORK = {
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
@ -105,7 +105,7 @@ CORS_ALLOWED_ORIGINS = [
"http://localhost:5173",
]
CSRF_TRUSTED_ORIGINS = ["http://*"]
CSRF_TRUSTED_ORIGINS = ["http://localhost:5173"]
CORS_ORIGIN_WHITELIST = ["*"]

View File

@ -24,7 +24,7 @@ class CustomUserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ('email', 'username', 'password')
fields = ('email', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):

View File

@ -1,6 +1,6 @@
from django.urls import path
from rest_framework_simplejwt import views as jwt_views
from .views import ObtainTokenPairWithCustomView, CustomUserCreate, GreetingView, GoogleLogin
from .views import ObtainTokenPairWithCustomView, CustomUserCreate, GreetingView, GoogleLogin, GoogleRetrieveUserInfo
urlpatterns = [
path('user/create/', CustomUserCreate.as_view(), name="create_user"),
@ -9,4 +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())
]

View File

@ -1,16 +1,25 @@
"""This module defines API views for authentication, user creation, and a simple hello message."""
from django.shortcuts import render
import json
import requests
from django.contrib.auth.hashers import make_password
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView
from dj_rest_auth.registration.views import SocialLoginView
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from .adapter import CustomGoogleOAuth2Adapter
from .serializers import MyTokenObtainPairSerializer, CustomUserSerializer
from rest_framework_simplejwt.tokens import RefreshToken
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 .serializers import MyTokenObtainPairSerializer, CustomUserSerializer
from .managers import CustomAccountManager
from .models import CustomUser
class ObtainTokenPairWithCustomView(APIView):
"""
@ -78,5 +87,47 @@ class GoogleLogin(SocialLoginView):
"""
# permission_classes = (AllowAny,)
adapter_class = GoogleOAuth2Adapter
client_class = OAuth2Client
# callback_url = 'http://localhost:8000/accounts/google/login/callback/'
# client_class = OAuth2Client
# callback_url = 'http://localhost:8000/accounts/google/login/callback/'
class GoogleRetrieveUserInfo(APIView):
"""
Retrieve user information from Google and create a user if not exists.
"""
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)
token = RefreshToken.for_user(user)
response = {
'username': user.username,
'access_token': str(token.access_token),
'refresh_token': str(token),
}
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)
return json.loads(response.text)
def get_or_create_user(self, user_info):
try:
user = CustomUser.objects.get(email=user_info['email'])
except CustomUser.DoesNotExist:
user = CustomUser()
user.username = user_info['email']
user.password = make_password(CustomAccountManager().make_random_password())
user.email = user_info['email']
user.save()
return user

View File

@ -16,6 +16,7 @@
"@material-ui/icons": "^4.11.3",
"@mui/icons-material": "^5.14.15",
"@mui/material": "^5.14.15",
"@mui/system": "^5.14.15",
"@react-oauth/google": "^0.11.1",
"axios": "^1.5.1",
"bootstrap": "^5.3.2",

View File

@ -23,6 +23,9 @@ dependencies:
'@mui/material':
specifier: ^5.14.15
version: 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
'@mui/system':
specifier: ^5.14.15
version: 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react@18.2.0)
'@react-oauth/google':
specifier: ^0.11.1
version: 0.11.1(react-dom@18.2.0)(react@18.2.0)

View File

@ -24,10 +24,10 @@
import './App.css';
import {BrowserRouter, Route, Routes, Link} from "react-router-dom";
import Login from "./components/login";
import TestAuth from './components/testAuth';
import Signup from './components/signup';
import IconSideNav from "./components/IconSideNav";
import AuthenticantionPage from "./components/authentication/AuthenticationPage"
import SignUpPage from "./components/authentication/SignUpPage"
const App = () => {
return (
@ -41,14 +41,14 @@ const App = () => {
</nav>
<Routes>
<Route path={"/"} render={() => <h1>This is Home page!</h1>} />
<Route path="/login" element={<Login/>}/>
<Route path="/signup" element={<Signup/>}/>
<Route path="/login" element={<AuthenticantionPage/>}/>
<Route path="/signup" element={<SignUpPage/>}/>
<Route path="/testAuth" element={<TestAuth/>}/>
</Routes>
</div>
<div>
{/* <div>
<IconSideNav />
</div>
</div> */}
</BrowserRouter>
);
}

View File

@ -62,13 +62,12 @@ const apiUserLogout = () => {
}
// Function for Google login
const googleLogin = async (accesstoken) => {
const googleLogin = async (token) => {
axios.defaults.withCredentials = true
let res = await axios.post(
"http://localhost:8000/api/dj-rest-auth/google/",
"http://localhost:8000/api/auth/google/",
{
access_token: accesstoken,
id_token: accesstoken,
token: token,
}
);
// console.log('service google login res: ', res);

View File

@ -0,0 +1,214 @@
import React, { useState } 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';
import TextField from '@mui/material/TextField';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import Link from '@mui/material/Link';
import Divider from '@mui/material/Divider';
import Paper from '@mui/material/Paper';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import Typography from '@mui/material/Typography';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import axiosapi from '../../api/axiosapi';
function Copyright(props) {
return (
<Typography variant="body2" color="text.secondary" align="center" {...props}>
{'Copyright © '}
<Link color="inherit" href="https://mui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
const defaultTheme = createTheme();
export default function SignInSide() {
const Navigate = useNavigate();
const [email, setEmail] = useState("");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleUsernameChange = (event) => {
setUsername(event.target.value);
}
const handleEmailChange = (event) => {
setEmail(event.target.value);
}
const handlePasswordChange = (event) => {
setPassword(event.target.value);
}
const handleSubmit = (event) => {
event.preventDefault();
// Send a POST request to the authentication API
axiosapi.apiUserLogin({
email: email,
username: username,
password: password
}).then(res => {
// On successful login, store tokens and set the authorization header
localStorage.setItem('access_token', res.data.access);
localStorage.setItem('refresh_token', res.data.refresh);
axiosapi.axiosInstance.defaults.headers['Authorization'] = "Bearer " + res.data.access;
Navigate('/testAuth');
}).catch(err => {
console.log('Login failed'); // Handle login failure
console.log(err)
});
}
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('/testAuth');
}
}
const googleLoginImplicit = useGoogleLogin({
// flow: 'auth-code',
onSuccess: async (response) => {
console.log(response);
try {
const loginResponse = await axiosapi.googleLogin(response.access_token);
if (loginResponse && loginResponse.data) {
const { access_token, refresh_token } = loginResponse.data;
// Save the tokens in localStorage
localStorage.setItem('access_token', access_token);
localStorage.setItem('refresh_token', refresh_token);
}
} catch (error) {
console.error('Error with the POST request:', error);
}
},
onError: errorResponse => console.log(errorResponse),
});
return (
<ThemeProvider theme={defaultTheme}>
<Grid container component="main" sx={{ height: '100vh' }}>
<CssBaseline />
<Grid
item
xs={false}
sm={4}
md={7}
sx={{
backgroundImage: 'url(https://source.unsplash.com/random?wallpapers)',
backgroundRepeat: 'no-repeat',
backgroundColor: (t) =>
t.palette.mode === 'light' ? t.palette.grey[50] : t.palette.grey[900],
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
<Box
sx={{
my: 8,
mx: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
onChange={handleEmailChange}
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
onChange={handlePasswordChange}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Sign In
</Button>
<Divider>OR</Divider>
<Box py={2}>
<Button
fullWidth
variant="outlined"
color="secondary"
onClick={() => googleLoginImplicit()}
>
Sign in with Google 🚀
</Button>
</Box>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
<Copyright sx={{ mt: 5 }} />
</Box>
</Box>
</Grid>
</Grid>
</ThemeProvider>
);
}

View File

@ -0,0 +1,146 @@
import React, { useState } from 'react';
import axiosapi from '../../api/axiosapi';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import CssBaseline from '@mui/material/CssBaseline';
import TextField from '@mui/material/TextField';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import Link from '@mui/material/Link';
import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';
import { createTheme, ThemeProvider } from '@mui/material/styles';
function Copyright(props) {
return (
<Typography variant="body2" color="text.secondary" align="center" {...props}>
{'Copyright © '}
<Link color="inherit" href="https://mui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
const defaultTheme = createTheme();
export default function SignUp() {
const [formData, setFormData] = useState({
email: '',
username: '',
password: '',
});
const [error, setError] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
setError(null);
try {
axiosapi.createUser(formData);
} catch (error) {
console.error('Error creating user:', error);
setError('Registration failed. Please try again.');
} finally {
setIsSubmitting(false);
}
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
return (
<ThemeProvider theme={defaultTheme}>
<Container component="main" maxWidth="xs">
<CssBaseline />
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign up
</Typography>
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
onChange={handleChange}
/>
</Grid>
<Grid item xs={12}>
<TextField
autoComplete="username"
name="Username"
required
fullWidth
id="Username"
label="Username"
autoFocus
onChange={handleChange}
/>
</Grid>
<Grid item xs={12}>
<TextField
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="new-password"
onChange={handleChange}
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel
control={<Checkbox value="allowExtraEmails" color="primary" />}
label="I want to receive inspiration, marketing promotions and updates via email."
/>
</Grid>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Sign Up
</Button>
<Grid container justifyContent="flex-end">
<Grid item>
<Link href="#" variant="body2">
Already have an account? Sign in
</Link>
</Grid>
</Grid>
</Box>
</Box>
<Copyright sx={{ mt: 5 }} />
</Container>
</ThemeProvider>
);
}

View File

@ -1,161 +0,0 @@
import React, { useState } from 'react';
import Avatar from '@material-ui/core/Avatar';
import Button from '@material-ui/core/Button';
import CssBaseline from '@material-ui/core/CssBaseline';
import TextField from '@material-ui/core/TextField';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import Container from '@material-ui/core/Container';
import axiosapi from '../api/axiosapi';
import { useNavigate } from 'react-router-dom';
import { useGoogleLogin } from '@react-oauth/google';
const useStyles = makeStyles((theme) => ({
// Styles for various elements
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: '100%',
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
export default function Login() {
const history = useNavigate();
const classes = useStyles();
const [email, setEmail] = useState("");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleUsernameChange = (event) => {
// Update the 'username' state when the input field changes
setUsername(event.target.value);
}
const handleEmailChange = (event) => {
// Update the 'email' state when the email input field changes
setEmail(event.target.value);
}
const handlePasswordChange = (event) => {
// Update the 'password' state when the password input field changes
setPassword(event.target.value);
}
const handleSubmit = (event) => {
event.preventDefault();
// Send a POST request to the authentication API
axiosapi.apiUserLogin({
email: email,
username: username,
password: password
}).then(res => {
// On successful login, store tokens and set the authorization header
localStorage.setItem('access_token', res.data.access);
localStorage.setItem('refresh_token', res.data.refresh);
axiosapi.axiosInstance.defaults.headers['Authorization'] = "Bearer " + res.data.access;
history.push('/testAuth');
}).catch(err => {
console.log('Login failed'); // Handle login failure
console.log(err)
});
}
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;
history.push('/testAuth');
}
}
const googleLoginflow = useGoogleLogin({
onSuccess: async tokenResponse => {
console.log(tokenResponse);
responseGoogle(tokenResponse);
},
})
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar} />
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} noValidate onSubmit={handleSubmit}>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email"
name="email"
autoComplete="email"
autoFocus
onChange={handleEmailChange}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="username"
label="Username"
name="username"
autoComplete="username"
autoFocus
onChange={handleUsernameChange}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
onChange={handlePasswordChange}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign In
</Button>
</form>
<button onClick={() => googleLoginflow()}>
Sign in with Google 🚀{' '}
</button>
</div>
</Container>
);
}

View File

@ -8,4 +8,7 @@ djangorestframework>=3.14
markdown>=3.5
django-filter>=23.3
djangorestframework-simplejwt>=5.3
django-cors-headers>=4.3
django-cors-headers>=4.3
google_api_python_client>=2.1
google_auth_oauthlib>=1.1
google-auth-httplib2>=0.1