Commit 15af72ce authored by Egor Kremnev's avatar Egor Kremnev

add google auth

parent 0f9e029b
This shop api and front clent This shop api and front client
\ No newline at end of file
NODE_FACEBOOK_APP_ID=
NODE_FACEBOOK_SECRET=
NODE_GOOGLE_APP_ID=116339632653-c637pvdqe9csqchit034e3tv77ki94cp.apps.googleusercontent.com
NODE_GOOLGE_SECRET=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
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
# misc # misc
.DS_Store .DS_Store
.env
.env.local .env.local
.env.development.local .env.development.local
.env.test.local .env.test.local
......
...@@ -10,5 +10,13 @@ module.exports = { ...@@ -10,5 +10,13 @@ module.exports = {
db: { db: {
host: 'mongodb://127.0.0.1', host: 'mongodb://127.0.0.1',
database: isTest ? 'shop_test' : 'shop', database: isTest ? 'shop_test' : 'shop',
},
facebook: {
appId: process.env.NODE_FACEBOOK_APP_ID,
secretApp: process.env.NODE_FACEBOOK_SECRET,
},
google: {
appId: process.env.NODE_GOOGLEK_APP_ID,
secretApp: process.env.NODE_GOOGLE_SECRET,
} }
}; };
require('dotenv').config();
const cors = require('cors'); const cors = require('cors');
const express = require('express'); const express = require('express');
const app = express(); const app = express();
......
...@@ -9,6 +9,14 @@ const UserSchema = new Schema({ ...@@ -9,6 +9,14 @@ const UserSchema = new Schema({
type: String, type: String,
required: true required: true
}, },
facebookId: {
type: String,
unique: true
},
googleId: {
type: String,
unique: true
},
username: { username: {
type: String, type: String,
required: true, required: true,
......
This diff is collapsed.
...@@ -15,10 +15,13 @@ ...@@ -15,10 +15,13 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^1.4.0",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"fix-esm": "^1.0.1", "fix-esm": "^1.0.1",
"google-auth-library": "^9.0.0",
"mongodb": "^5.3.0", "mongodb": "^5.3.0",
"mongoose": "^6.5.0", "mongoose": "^6.5.0",
"mongoose-id-validator": "^0.6.0", "mongoose-id-validator": "^0.6.0",
......
const router = require('express').Router(); const router = require('express').Router();
const User = require('../models/User'); const User = require('../models/User');
const auth = require("../middleware/auth"); const auth = require("../middleware/auth");
const config = require('../config');
const axios = require("axios");
const {OAuth2Client} = require("google-auth-library");
const {nanoid} = require('fix-esm').require("nanoid");
router.post('/', async (req, res) => { router.post('/', async (req, res) => {
try { try {
...@@ -78,4 +82,81 @@ router.delete('/logout', auth, async (req, res) => { ...@@ -78,4 +82,81 @@ router.delete('/logout', auth, async (req, res) => {
res.send({message: 'Success'}); res.send({message: 'Success'});
}); });
router.post('/facebookLogin', async (req, res) => {
const inputToken = req.body.accessToken;
const accessToken = config.facebook.appId + '|' + config.facebook.secretApp;
const debugTokenUrl = `https://graph.facebook.com/debug_token?input_token=${inputToken}&access_token=${accessToken}`;
try {
const response = await axios.get(debugTokenUrl).then(res => res.data.data);
if (response.error) {
return res.status(401).send({message: 'Facebook token incorrect'});
}
if (req.body.id !== response.user_id) {
return res.status(401).send({message: 'Wrong user ID'});
}
let user = await User.findOne({facebookId: req.body.id});
if (!user) {
user = new User({
username: req.body.email,
facebookId: req.body.id,
displayName: req.body.name,
password: nanoid(),
});
}
user.generateToken();
await user.save();
return res.send(user);
} catch (e) {
return res.status(400).send({message: 'Facebook token incorrect'});
}
});
router.post('/googleLogin', async (req, res) => {
const idToken = req.body.tokenObj.id_token;
const profileData = req.body.profileObj;
try {
const client = new OAuth2Client();
const ticket = await client.verifyIdToken({
idToken: idToken,
audience: config.google.appId
});
const payload = ticket.getPayload();
if (payload.error) {
return res.status(401).send({message: 'Google token incorrect'});
}
if (profileData.googleId !== payload.sub) {
return res.status(401).send({message: 'Wrong user ID'});
}
let user = await User.findOne({googleId: profileData.googleId});
if (!user) {
user = new User({
username: profileData.email,
googleId: profileData.googleId,
displayName: profileData.name,
password: nanoid(),
});
}
user.generateToken();
await user.save();
return res.send(user);
} catch (e) {
return res.status(400).send({message: 'Google token incorrect'});
}
});
module.exports = router; module.exports = router;
...@@ -8,8 +8,10 @@ ...@@ -8,8 +8,10 @@
"name": "front", "name": "front",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@dump-work/react-google-login": "^6.0.14",
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@greatsumini/react-facebook-login": "^3.3.3",
"@mui/icons-material": "^5.11.16", "@mui/icons-material": "^5.11.16",
"@mui/material": "^5.11.16", "@mui/material": "^5.11.16",
"@mui/styled-engine-sc": "^5.11.11", "@mui/styled-engine-sc": "^5.11.11",
...@@ -2194,6 +2196,20 @@ ...@@ -2194,6 +2196,20 @@
"postcss-selector-parser": "^6.0.10" "postcss-selector-parser": "^6.0.10"
} }
}, },
"node_modules/@dump-work/react-google-login": {
"version": "6.0.14",
"resolved": "https://registry.npmjs.org/@dump-work/react-google-login/-/react-google-login-6.0.14.tgz",
"integrity": "sha512-szoS1jYvhrkBqSudGJFUYS3Qd20CJ9Ye4etN7DTOkM3gxJHCiqZUoQLhzV0DJPBC6y0TkWg56n47eTutX5OaGQ==",
"dependencies": {
"@types/react": "*",
"jwt-decode": "^3.1.2",
"prop-types": "^15.6.0"
},
"peerDependencies": {
"react": "^16 || ^17",
"react-dom": "^16 || ^17"
}
},
"node_modules/@emotion/babel-plugin": { "node_modules/@emotion/babel-plugin": {
"version": "11.10.6", "version": "11.10.6",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz",
...@@ -2425,6 +2441,14 @@ ...@@ -2425,6 +2441,14 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@greatsumini/react-facebook-login": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/@greatsumini/react-facebook-login/-/react-facebook-login-3.3.3.tgz",
"integrity": "sha512-Y5D7EncR3iy/X/OfWwjjpM5OW0XV6PCE08RZUV/yhAE413PEBIlML7S6z69BcpUPWO5XzEt7cytHChUdwXO4Dw==",
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.8", "version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
...@@ -11714,6 +11738,11 @@ ...@@ -11714,6 +11738,11 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"node_modules/kind-of": { "node_modules/kind-of": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
......
...@@ -3,8 +3,10 @@ ...@@ -3,8 +3,10 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@dump-work/react-google-login": "^6.0.14",
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@greatsumini/react-facebook-login": "^3.3.3",
"@mui/icons-material": "^5.11.16", "@mui/icons-material": "^5.11.16",
"@mui/material": "^5.11.16", "@mui/material": "^5.11.16",
"@mui/styled-engine-sc": "^5.11.11", "@mui/styled-engine-sc": "^5.11.11",
...@@ -23,7 +25,7 @@ ...@@ -23,7 +25,7 @@
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "HTTPS=true react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject", "eject": "react-scripts eject",
......
import FacebookLogin from "@greatsumini/react-facebook-login";
import {facebookAppId} from '../../../constants/config';
import {Button} from "@mui/material";
import {facebookLogin} from "../../../store/actions/usersActions";
import {useDispatch} from "react-redux";
import {useNavigate} from "react-router-dom";
const FacebookLoginButton = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
return <FacebookLogin
appId={facebookAppId}
initParams={{
version: 'v17.0',
xfbml: true,
}}
onSuccess={(response) => {
console.log('Login Success!', response);
dispatch(facebookLogin({data: response, callback: () => navigate('/')}));
}}
onFail={(error) => {
console.log('Login Failed!', error);
}}
onProfileSuccess={(response) => {
console.log('Get Profile Success!', response);
}}
render={({onClick, logout}) => (
<Button onClick={onClick}>
Enter with facebook
</Button>
)}
/>
};
export default FacebookLoginButton;
import GoogleLogin from "@dump-work/react-google-login";
import {googleAppId} from '../../../constants/config';
import {Button} from "@mui/material";
import {useDispatch} from "react-redux";
import {useNavigate} from "react-router-dom";
import {googleLogin} from "../../../store/actions/usersActions";
const GoogleLoginButton = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
return <GoogleLogin
clientId={googleAppId}
onFailure={(error) => {
console.log('Login Failed!', error);
}}
onSuccess={(response) => {
console.log('Login Success!', response);
dispatch(googleLogin({data: response, callback: () => navigate('/')}));
}}
render={({ onClick, logout }) => (
<Button onClick={onClick}>
Enter with Google
</Button>
)}
/>;
};
export default GoogleLoginButton;
...@@ -7,3 +7,5 @@ if (process.env.REACT_APP_API_HOST_ENV) { ...@@ -7,3 +7,5 @@ if (process.env.REACT_APP_API_HOST_ENV) {
export const apiUrl = host; export const apiUrl = host;
export const uploadUrl = apiUrl + '/uploads'; export const uploadUrl = apiUrl + '/uploads';
export const facebookAppId = 606018451709501;
export const googleAppId = '116339632653-c637pvdqe9csqchit034e3tv77ki94cp.apps.googleusercontent.com';
...@@ -8,6 +8,8 @@ import {useDispatch, useSelector} from "react-redux"; ...@@ -8,6 +8,8 @@ import {useDispatch, useSelector} from "react-redux";
import {loginUser} from "../../../store/actions/usersActions"; import {loginUser} from "../../../store/actions/usersActions";
import FormElement from "../../../components/UI/Form/FormElement/FormElement"; import FormElement from "../../../components/UI/Form/FormElement/FormElement";
import {setLoginError} from "../../../store/services/usersSlice"; import {setLoginError} from "../../../store/services/usersSlice";
import FacebookLoginButton from "../../../components/SocialAuth/FacebookLoginButton/FacebookLoginButton";
import GoogleLoginButton from "../../../components/SocialAuth/GoogleLoginButton/GoogleLoginButton";
const theme = createTheme(); const theme = createTheme();
...@@ -94,6 +96,14 @@ const Login = () => { ...@@ -94,6 +96,14 @@ const Login = () => {
</Link> </Link>
</Grid> </Grid>
</Grid> </Grid>
<Grid container justifyContent="flex-end">
<Grid item>
<FacebookLoginButton/>
</Grid>
<Grid item>
<GoogleLoginButton/>
</Grid>
</Grid>
</Box> </Box>
</Box> </Box>
</Container> </Container>
......
...@@ -8,6 +8,8 @@ import {useDispatch, useSelector} from "react-redux"; ...@@ -8,6 +8,8 @@ import {useDispatch, useSelector} from "react-redux";
import {registerUser} from "../../../store/actions/usersActions"; import {registerUser} from "../../../store/actions/usersActions";
import FormElement from "../../../components/UI/Form/FormElement/FormElement"; import FormElement from "../../../components/UI/Form/FormElement/FormElement";
import {setRegisterError} from "../../../store/services/usersSlice"; import {setRegisterError} from "../../../store/services/usersSlice";
import FacebookLoginButton from "../../../components/SocialAuth/FacebookLoginButton/FacebookLoginButton";
import GoogleLoginButton from "../../../components/SocialAuth/GoogleLoginButton/GoogleLoginButton";
const theme = createTheme(); const theme = createTheme();
...@@ -99,6 +101,14 @@ const Register = () => { ...@@ -99,6 +101,14 @@ const Register = () => {
</Link> </Link>
</Grid> </Grid>
</Grid> </Grid>
<Grid container justifyContent="flex-end">
<Grid item>
<FacebookLoginButton/>
</Grid>
<Grid item>
<GoogleLoginButton/>
</Grid>
</Grid>
</Box> </Box>
</Box> </Box>
</Container> </Container>
......
...@@ -59,3 +59,33 @@ export const updateUser = createAsyncThunk( ...@@ -59,3 +59,33 @@ export const updateUser = createAsyncThunk(
throw e; throw e;
}) })
); );
export const googleLogin = createAsyncThunk(
'users/googleLogin',
async ({data, callback}, {dispatch}) => await axiosApi
.post('/users/googleLogin', data)
.then(res => {
dispatch(setUser(res.data));
callback();
})
.catch(e => {
if (e?.response?.data) dispatch(setLoginError(e.response.data));
else dispatch(setLoginError(e));
throw e;
})
);
export const facebookLogin = createAsyncThunk(
'users/facebookLogin',
async ({data, callback}, {dispatch}) => await axiosApi
.post('/users/facebookLogin', data)
.then(res => {
dispatch(setUser(res.data));
callback();
})
.catch(e => {
if (e?.response?.data) dispatch(setLoginError(e.response.data));
else dispatch(setLoginError(e));
throw e;
})
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment