Merge pull request #12 from TurTaskProject/feature/user-authentication

Fix Google Authentication and Add placeholder navbar and login-signup
This commit is contained in:
Sirin Puenggun 2023-10-31 05:20:46 +07:00 committed by GitHub
commit af7cd4a6f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 688 additions and 221 deletions

View File

@ -46,19 +46,19 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'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
# 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

@ -1,38 +1,18 @@
// import './App.css';
// import { Routes, Route, Link } from "react-router-dom";
// import Login from "./components/login";
// import TestAuth from './components/testAuth';
// function App() {
// return (
// <div className="App">
// <nav>
// <Link className={"nav-link"} to={"/"}>Home</Link>
// <Link className={"nav-link"} to={"/login"}>Login</Link>
// <Link className={"nav-link"} to={"/testAuth"}>testAuth</Link>
// </nav>
// <Routes>
// <Route exact path={"/login"} element={Login} />
// <Route exact path={"/testAuth"} element={TestAuth} />
// <Route path={"/"} render={() => <h1>This is Home page!</h1>} />
// </Routes>
// </div>
// );
// }
// export default App;
import './App.css';
import {BrowserRouter, Route, Routes, Link} from "react-router-dom";
import Login from "./components/login";
import { BrowserRouter, Route, Routes, Link } from 'react-router-dom';
import TestAuth from './components/testAuth';
import Signup from './components/signup';
import IconSideNav from "./components/IconSideNav";
import IconSideNav from './components/IconSideNav';
import AuthenticantionPage from './components/authentication/AuthenticationPage';
import SignUpPage from './components/authentication/SignUpPage';
import NavBar from './components/Nav/Navbar';
const App = () => {
return (
<BrowserRouter>
<div className="App">
<NavBar/>
<nav>
<Link className={"nav-link"} to={"/"}>Home</Link>
<Link className={"nav-link"} to={"/login"}>Login</Link>
@ -41,14 +21,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,177 @@
import * as React from 'react';
import { Link } from 'react-router-dom';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import Menu from '@mui/material/Menu';
import MenuIcon from '@mui/icons-material/Menu';
import Container from '@mui/material/Container';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import MenuItem from '@mui/material/MenuItem';
import AdbIcon from '@mui/icons-material/Adb';
const pages = {
TestAuth: '/testAuth',
};
const settings = {
Profile: '/profile',
Account: '/account',
Dashboard: '/dashboard',
Logout: '/logout',
};
function NavBar() {
const [anchorElNav, setAnchorElNav] = React.useState(null);
const [anchorElUser, setAnchorElUser] = React.useState(null);
const handleOpenNavMenu = (event) => {
setAnchorElNav(event.currentTarget);
};
const handleOpenUserMenu = (event) => {
setAnchorElUser(event.currentTarget);
};
const handleCloseNavMenu = () => {
setAnchorElNav(null);
};
const handleCloseUserMenu = () => {
setAnchorElUser(null);
};
return (
<AppBar position="static">
<Container maxWidth="xl">
<Toolbar disableGutters>
<AdbIcon sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} />
<Typography
variant="h6"
noWrap
component="a"
href="/"
sx={{
mr: 2,
display: { xs: 'none', md: 'flex' },
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
LOGO
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleOpenNavMenu}
color="inherit"
>
<MenuIcon />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
sx={{
display: { xs: 'block', md: 'none' },
}}
>
{Object.entries(pages).map(([page, path]) => (
<MenuItem key={page} onClick={handleCloseNavMenu}>
<Link to={path} className="nav-link">
<Typography textAlign="center">{page}</Typography>
</Link>
</MenuItem>
))}
</Menu>
</Box>
<AdbIcon sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} />
<Typography
variant="h5"
noWrap
component="a"
href="#app-bar-with-responsive-menu"
sx={{
mr: 2,
display: { xs: 'flex', md: 'none' },
flexGrow: 1,
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
LOGO
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
{Object.entries(pages).map(([page, path]) => (
<Button
key={page}
component={Link} // Use the Link component
to={path} // Specify the target path
onClick={handleCloseNavMenu}
sx={{ my: 2, color: 'white', display: 'block' }}
>
{page}
</Button>
))}
</Box>
<Box sx={{ flexGrow: 0 }}>
<Tooltip title="Open settings">
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<Avatar alt="Bullet" src="https://us-tuna-sounds-images.voicemod.net/f322631f-689a-43ac-81ab-17a70f27c443-1692187175560.png" />
</IconButton>
</Tooltip>
<Menu
sx={{ mt: '45px' }}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
{Object.entries(settings).map(([setting, path]) => (
<MenuItem key={setting} onClick={handleCloseUserMenu}>
<Link to={path} className="nav-link">
<Typography textAlign="center">{setting}</Typography>
</Link>
</MenuItem>
))}
</Menu>
</Box>
</Toolbar>
</Container>
</AppBar>
);
}
export default NavBar;

View File

@ -0,0 +1,215 @@
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('/');
}).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('/');
}
}
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);
Navigate('/');
}
} 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,48 @@
import { useState, useEffect } from 'react';
import axiosapi from './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');
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();
}, []);
const checkIfAccessTokenExpired = (accessToken) => {
// Need to change logic again!
return !accessToken;
};
return isAuthenticated;
}
export default IsAuthenticated;

View File

@ -0,0 +1,151 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
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 Navigate = useNavigate();
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);
}
Navigate('/login');
};
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

@ -4,7 +4,7 @@ import Button from '@material-ui/core/Button';
import { useNavigate } from 'react-router-dom';
function TestAuth() {
let history = useNavigate();
let Navigate = useNavigate();
const [message, setMessage] = useState("");
@ -22,19 +22,19 @@ function TestAuth() {
const logout = () => {
// Log out the user, clear tokens, and navigate to the "/testAuth" route
axiosapi.apiUserLogout();
history('/testAuth');
Navigate('/testAuth');
}
return (
<div>
{message !== "" && (
<div>
<h1>Hello!</h1>
<h1 class="text-xl font-bold">Login! Hello!</h1>
<h2>{message}</h2>
<Button variant="contained" onClick={logout}>Logout</Button>
</div>
)}
{message === "" && <h1>Need to sign in</h1>}
{message === "" && <h1 class="text-xl font-bold">Need to sign in, No authentication found</h1>}
</div>
);
}

View File

@ -1,13 +1,12 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { GoogleLogin, GoogleOAuthProvider} from '@react-oauth/google';
import { GoogleOAuthProvider} from '@react-oauth/google';
const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID
ReactDOM.createRoot(document.getElementById("root")).render(
<GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
<App />
<App />
</GoogleOAuthProvider>
);

View File

@ -9,3 +9,6 @@ markdown>=3.5
django-filter>=23.3
djangorestframework-simplejwt>=5.3
django-cors-headers>=4.3
google_api_python_client>=2.1
google_auth_oauthlib>=1.1
google-auth-httplib2>=0.1