From 700b727784f70eb92d9521740df61054fe958d66 Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Mon, 25 Nov 2019 15:17:42 +0100 Subject: [PATCH 01/76] Fix useEffect dependency warnings --- client/src/components/post/Post.js | 11 ++--- .../components/profile-forms/EditProfile.js | 45 +++++++------------ .../src/components/profile/ProfileGithub.js | 25 +++-------- 3 files changed, 28 insertions(+), 53 deletions(-) diff --git a/client/src/components/post/Post.js b/client/src/components/post/Post.js index b71a38e..aa6fee8 100755 --- a/client/src/components/post/Post.js +++ b/client/src/components/post/Post.js @@ -11,7 +11,7 @@ import { getPost } from '../../actions/post'; const Post = ({ getPost, post: { post, loading }, match }) => { useEffect(() => { getPost(match.params.id); - }, [getPost]); + }, [getPost, match.params.id]); return loading || post === null ? ( @@ -33,14 +33,11 @@ const Post = ({ getPost, post: { post, loading }, match }) => { Post.propTypes = { getPost: PropTypes.func.isRequired, - post: PropTypes.object.isRequired + post: PropTypes.object.isRequired, }; const mapStateToProps = state => ({ - post: state.post + post: state.post, }); -export default connect( - mapStateToProps, - { getPost } -)(Post); +export default connect(mapStateToProps, { getPost })(Post); diff --git a/client/src/components/profile-forms/EditProfile.js b/client/src/components/profile-forms/EditProfile.js index 10dded9..dbad9dd 100755 --- a/client/src/components/profile-forms/EditProfile.js +++ b/client/src/components/profile-forms/EditProfile.js @@ -8,7 +8,7 @@ const EditProfile = ({ profile: { profile, loading }, createProfile, getCurrentProfile, - history + history, }) => { const [formData, setFormData] = useState({ company: '', @@ -22,7 +22,7 @@ const EditProfile = ({ facebook: '', linkedin: '', youtube: '', - instagram: '' + instagram: '', }); const [displaySocialInputs, toggleSocialInputs] = useState(false); @@ -36,15 +36,15 @@ const EditProfile = ({ location: loading || !profile.location ? '' : profile.location, status: loading || !profile.status ? '' : profile.status, skills: loading || !profile.skills ? '' : profile.skills.join(','), - githubusername: - loading || !profile.githubusername ? '' : profile.githubusername, + githubusername: loading || !profile.githubusername ? '' : profile.githubusername, bio: loading || !profile.bio ? '' : profile.bio, twitter: loading || !profile.social ? '' : profile.social.twitter, facebook: loading || !profile.social ? '' : profile.social.facebook, linkedin: loading || !profile.social ? '' : profile.social.linkedin, youtube: loading || !profile.social ? '' : profile.social.youtube, - instagram: loading || !profile.social ? '' : profile.social.instagram + instagram: loading || !profile.social ? '' : profile.social.instagram, }); + // eslint-disable-next-line }, [loading, getCurrentProfile]); const { @@ -59,11 +59,10 @@ const EditProfile = ({ facebook, linkedin, youtube, - instagram + instagram, } = formData; - const onChange = e => - setFormData({ ...formData, [e.target.name]: e.target.value }); + const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value }); const onSubmit = e => { e.preventDefault(); @@ -90,9 +89,7 @@ const EditProfile = ({ - - Give us an idea of where you are at in your career - + Give us an idea of where you are at in your career
onChange(e)} /> - - Could be your own company or one you work for - + Could be your own company or one you work for
onChange(e)} /> - - Could be your own or a company website - + Could be your own or a company website
onChange(e)} /> - - City & state suggested (eg. Boston, MA) - + City & state suggested (eg. Boston, MA)
onChange(e)} /> - If you want your latest repos and a Github link, include your - username + If you want your latest repos and a Github link, include your username
@@ -247,14 +237,13 @@ const EditProfile = ({ EditProfile.propTypes = { createProfile: PropTypes.func.isRequired, getCurrentProfile: PropTypes.func.isRequired, - profile: PropTypes.object.isRequired + profile: PropTypes.object.isRequired, }; const mapStateToProps = state => ({ - profile: state.profile + profile: state.profile, }); -export default connect( - mapStateToProps, - { createProfile, getCurrentProfile } -)(withRouter(EditProfile)); +export default connect(mapStateToProps, { createProfile, getCurrentProfile })( + withRouter(EditProfile), +); diff --git a/client/src/components/profile/ProfileGithub.js b/client/src/components/profile/ProfileGithub.js index b8f20ca..000050c 100755 --- a/client/src/components/profile/ProfileGithub.js +++ b/client/src/components/profile/ProfileGithub.js @@ -7,7 +7,7 @@ import { getGithubRepos } from '../../actions/profile'; const ProfileGithub = ({ username, getGithubRepos, repos }) => { useEffect(() => { getGithubRepos(username); - }, [getGithubRepos]); + }, [getGithubRepos, username]); return (
@@ -19,11 +19,7 @@ const ProfileGithub = ({ username, getGithubRepos, repos }) => {

- + {repo.name}

@@ -31,12 +27,8 @@ const ProfileGithub = ({ username, getGithubRepos, repos }) => {
    -
  • - Stars: {repo.stargazers_count} -
  • -
  • - Watchers: {repo.watchers_count} -
  • +
  • Stars: {repo.stargazers_count}
  • +
  • Watchers: {repo.watchers_count}
  • Forks: {repo.forks_count}
@@ -50,14 +42,11 @@ const ProfileGithub = ({ username, getGithubRepos, repos }) => { ProfileGithub.propTypes = { getGithubRepos: PropTypes.func.isRequired, repos: PropTypes.array.isRequired, - username: PropTypes.string.isRequired + username: PropTypes.string.isRequired, }; const mapStateToProps = state => ({ - repos: state.profile.repos + repos: state.profile.repos, }); -export default connect( - mapStateToProps, - { getGithubRepos } -)(ProfileGithub); +export default connect(mapStateToProps, { getGithubRepos })(ProfileGithub); From f1cebbaad01862c85943c2f92b03829bcd9497b4 Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Mon, 25 Nov 2019 15:36:09 +0100 Subject: [PATCH 02/76] Add nodemailer and mail sending after registration --- package.json | 1 + routes/api/users.js | 60 +++++++++++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 964b915..ef0430a 100755 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "gravatar": "^1.8.0", "jsonwebtoken": "^8.5.1", "mongoose": "^5.7.5", + "nodemailer": "^6.3.1", "request": "^2.88.0" }, "devDependencies": { diff --git a/routes/api/users.js b/routes/api/users.js index a6f62fe..542d810 100755 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -5,6 +5,9 @@ const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const config = require('config'); const { check, validationResult } = require('express-validator/check'); +const nodemailer = require('nodemailer'); + +const HOST_ADDR = process.env.HOST_ADDR || ''; const User = require('../../models/User'); @@ -18,10 +21,7 @@ router.post( .not() .isEmpty(), check('email', 'Please include a valid email').isEmail(), - check( - 'password', - 'Please enter a password with 6 or more characters' - ).isLength({ min: 6 }) + check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 }), ], async (req, res) => { const errors = validationResult(req); @@ -35,22 +35,20 @@ router.post( let user = await User.findOne({ email }); if (user) { - return res - .status(400) - .json({ errors: [{ msg: 'User already exists' }] }); + return res.status(400).json({ errors: [{ msg: 'User already exists' }] }); } const avatar = gravatar.url(email, { s: '200', r: 'pg', - d: 'mm' + d: 'mm', }); user = new User({ name, email, avatar, - password + password, }); const salt = await bcrypt.genSalt(10); @@ -61,24 +59,44 @@ router.post( const payload = { user: { - id: user.id - } + id: user.id, + }, }; - jwt.sign( - payload, - config.get('jwtSecret'), - { expiresIn: 360000 }, - (err, token) => { - if (err) throw err; - res.json({ token }); - } - ); + const transporter = nodemailer.createTransport({ + host: 'smtp-pulse.com', + port: 465, + secure: true, + auth: { + user: process.env.MAIL_USER, + pass: process.env.MAIL_PASS, + }, + }); + + const emailToken = jwt.sign(payload, config.get('emailSecret'), { expiresIn: '1d' }); + const confirmURL = `${HOST_ADDR}/confirm/${emailToken}`; + + const msg = { + to: user.email, + from: process.env.MAIL_USER, + subject: 'Confirm Email', + html: `Hurrah! You've created a Developer Hub account with ${user.email}. Please take a moment to confirm that we can use this address to send you mails.
+ ${confirmURL}`, + }; + + const result = await transporter.sendMail(msg); + // console.log(result); + res.json({ msg: 'Confirmation mail sent' }); + + // jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { + // if (err) throw err; + // res.json({ token }); + // }); } catch (err) { console.error(err.message); res.status(500).send('Server error'); } - } + }, ); module.exports = router; From 6326a6bfd8030789c489f689fc84fac9a488dbec Mon Sep 17 00:00:00 2001 From: Gul Date: Mon, 25 Nov 2019 15:45:16 +0100 Subject: [PATCH 03/76] some test --- test.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 test.md diff --git a/test.md b/test.md new file mode 100644 index 0000000..200c433 --- /dev/null +++ b/test.md @@ -0,0 +1 @@ +hello people From 18a4941a1cfde8c7256b1e6006bb3fdd389fb2a7 Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Mon, 25 Nov 2019 15:59:08 +0100 Subject: [PATCH 04/76] Add email confirmation endpoint --- routes/api/users.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/routes/api/users.js b/routes/api/users.js index 542d810..ddd5e3c 100755 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -99,4 +99,30 @@ router.post( }, ); +// @route PUT /api/users/confirm/:emailToken +// @desc Confirm user email +// @access Public +router.put('/confirm/:emailToken', async (req, res) => { + const { emailToken } = req.params; + try { + const { + user: { id }, + } = jwt.verify(emailToken, config.get('emailSecret')); + await User.findOneAndUpdate({ _id: id }, { $set: { confirmed: true } }, { new: true }); + // TODO handle error cases + const payload = { + user: { + id, + }, + }; + + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 999999 }, (err, token) => { + if (err) throw err; + res.json({ token }); + }); + } catch (error) { + return res.status(401).send({ msg: 'Invalid Token' }); + } +}); + module.exports = router; From 136ef52a6b816b79870cfcdfa2beaa43c3f92a86 Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Mon, 25 Nov 2019 16:03:01 +0100 Subject: [PATCH 05/76] Change auth api and User model --- models/User.js | 16 ++++++++++------ routes/api/auth.js | 32 +++++++++++++------------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/models/User.js b/models/User.js index d7386df..fdc249a 100755 --- a/models/User.js +++ b/models/User.js @@ -3,24 +3,28 @@ const mongoose = require('mongoose'); const UserSchema = new mongoose.Schema({ name: { type: String, - required: true + required: true, }, email: { type: String, required: true, - unique: true + unique: true, + }, + confirmed: { + type: Boolean, + default: false, }, password: { type: String, - required: true + required: true, }, avatar: { - type: String + type: String, }, date: { type: Date, - default: Date.now - } + default: Date.now, + }, }); module.exports = User = mongoose.model('user', UserSchema); diff --git a/routes/api/auth.js b/routes/api/auth.js index 34c182b..aced0c5 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -28,7 +28,7 @@ router.post( '/', [ check('email', 'Please include a valid email').isEmail(), - check('password', 'Password is required').exists() + check('password', 'Password is required').exists(), ], async (req, res) => { const errors = validationResult(req); @@ -42,39 +42,33 @@ router.post( let user = await User.findOne({ email }); if (!user) { - return res - .status(400) - .json({ errors: [{ msg: 'Invalid Credentials' }] }); + return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] }); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { - return res - .status(400) - .json({ errors: [{ msg: 'Invalid Credentials' }] }); + return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] }); } + if (!user.confirmed) + return res.status(400).json({ errors: [{ msg: 'Please confirm your email to login' }] }); + const payload = { user: { - id: user.id - } + id: user.id, + }, }; - jwt.sign( - payload, - config.get('jwtSecret'), - { expiresIn: 360000 }, - (err, token) => { - if (err) throw err; - res.json({ token }); - } - ); + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { + if (err) throw err; + res.json({ token }); + }); } catch (err) { console.error(err.message); res.status(500).send('Server error'); } - } + }, ); module.exports = router; From df5633f1610b5d37825b9f0a05a727cc94e2c81e Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Mon, 25 Nov 2019 16:09:36 +0100 Subject: [PATCH 06/76] Change auth reducer and action creator --- client/src/actions/auth.js | 28 ++++++++++++---------------- client/src/reducers/auth.js | 15 ++++++++++----- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index 60adae0..d40e006 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -8,7 +8,7 @@ import { LOGIN_SUCCESS, LOGIN_FAIL, LOGOUT, - CLEAR_PROFILE + CLEAR_PROFILE, } from './types'; import setAuthToken from '../utils/setAuthToken'; @@ -23,11 +23,11 @@ export const loadUser = () => async dispatch => { dispatch({ type: USER_LOADED, - payload: res.data + payload: res.data, }); } catch (err) { dispatch({ - type: AUTH_ERROR + type: AUTH_ERROR, }); } }; @@ -36,8 +36,8 @@ export const loadUser = () => async dispatch => { export const register = ({ name, email, password }) => async dispatch => { const config = { headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }; const body = JSON.stringify({ name, email, password }); @@ -45,12 +45,8 @@ export const register = ({ name, email, password }) => async dispatch => { try { const res = await axios.post('/api/users', body, config); - dispatch({ - type: REGISTER_SUCCESS, - payload: res.data - }); - - dispatch(loadUser()); + dispatch({ type: REGISTER_SUCCESS }); + dispatch(setAlert(res.data.msg, 'success')); } catch (err) { const errors = err.response.data.errors; @@ -59,7 +55,7 @@ export const register = ({ name, email, password }) => async dispatch => { } dispatch({ - type: REGISTER_FAIL + type: REGISTER_FAIL, }); } }; @@ -68,8 +64,8 @@ export const register = ({ name, email, password }) => async dispatch => { export const login = (email, password) => async dispatch => { const config = { headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }; const body = JSON.stringify({ email, password }); @@ -79,7 +75,7 @@ export const login = (email, password) => async dispatch => { dispatch({ type: LOGIN_SUCCESS, - payload: res.data + payload: res.data, }); dispatch(loadUser()); @@ -91,7 +87,7 @@ export const login = (email, password) => async dispatch => { } dispatch({ - type: LOGIN_FAIL + type: LOGIN_FAIL, }); } }; diff --git a/client/src/reducers/auth.js b/client/src/reducers/auth.js index eb8a4c3..00a5946 100755 --- a/client/src/reducers/auth.js +++ b/client/src/reducers/auth.js @@ -6,14 +6,14 @@ import { LOGIN_SUCCESS, LOGIN_FAIL, LOGOUT, - ACCOUNT_DELETED + ACCOUNT_DELETED, } from '../actions/types'; const initialState = { token: localStorage.getItem('token'), isAuthenticated: null, loading: true, - user: null + user: null, }; export default function(state = initialState, action) { @@ -25,16 +25,21 @@ export default function(state = initialState, action) { ...state, isAuthenticated: true, loading: false, - user: payload + user: payload, }; case REGISTER_SUCCESS: + return { + ...state, + isAuthenticated: false, + loading: false, + }; case LOGIN_SUCCESS: localStorage.setItem('token', payload.token); return { ...state, ...payload, isAuthenticated: true, - loading: false + loading: false, }; case REGISTER_FAIL: case AUTH_ERROR: @@ -46,7 +51,7 @@ export default function(state = initialState, action) { ...state, token: null, isAuthenticated: false, - loading: false + loading: false, }; default: return state; From ab06972a2019ba9f170fe6c2068ab9a8ee7ae8d7 Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Mon, 25 Nov 2019 16:14:03 +0100 Subject: [PATCH 07/76] Add CONFIRM_EMAIL type --- client/src/actions/types.js | 1 + client/src/reducers/auth.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/client/src/actions/types.js b/client/src/actions/types.js index 883c20e..e03c0f6 100755 --- a/client/src/actions/types.js +++ b/client/src/actions/types.js @@ -22,3 +22,4 @@ export const DELETE_POST = 'DELETE_POST'; export const ADD_POST = 'ADD_POST'; export const ADD_COMMENT = 'ADD_COMMENT'; export const REMOVE_COMMENT = 'REMOVE_COMMENT'; +export const CONFIRM_EMAIL = 'CONFIRM_EMAIL'; diff --git a/client/src/reducers/auth.js b/client/src/reducers/auth.js index 00a5946..fc43d52 100755 --- a/client/src/reducers/auth.js +++ b/client/src/reducers/auth.js @@ -7,6 +7,7 @@ import { LOGIN_FAIL, LOGOUT, ACCOUNT_DELETED, + CONFIRM_EMAIL, } from '../actions/types'; const initialState = { @@ -34,6 +35,7 @@ export default function(state = initialState, action) { loading: false, }; case LOGIN_SUCCESS: + case CONFIRM_EMAIL: localStorage.setItem('token', payload.token); return { ...state, From 164179f872c267f2856c32fb2ea8fc102ef29373 Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Mon, 25 Nov 2019 16:19:45 +0100 Subject: [PATCH 08/76] Add Confirm and confirmEmail action creator --- client/src/actions/auth.js | 18 +++++++++++++++ client/src/components/auth/Confirm.js | 29 +++++++++++++++++++++++++ client/src/components/routing/Routes.js | 2 ++ 3 files changed, 49 insertions(+) create mode 100644 client/src/components/auth/Confirm.js diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index d40e006..5bbdabc 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -9,6 +9,7 @@ import { LOGIN_FAIL, LOGOUT, CLEAR_PROFILE, + CONFIRM_EMAIL, } from './types'; import setAuthToken from '../utils/setAuthToken'; @@ -47,6 +48,7 @@ export const register = ({ name, email, password }) => async dispatch => { dispatch({ type: REGISTER_SUCCESS }); dispatch(setAlert(res.data.msg, 'success')); + return true; } catch (err) { const errors = err.response.data.errors; @@ -57,6 +59,7 @@ export const register = ({ name, email, password }) => async dispatch => { dispatch({ type: REGISTER_FAIL, }); + return false; } }; @@ -92,6 +95,21 @@ export const login = (email, password) => async dispatch => { } }; +// Confirm email +export const confirmEmail = token => async dispatch => { + try { + const response = await axios.put(`/api/users/confirm/${token}`); + dispatch({ type: CONFIRM_EMAIL, payload: response.data }); + dispatch(loadUser()); + } catch (error) { + const { errors } = error.response.data; + if (errors) { + errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); + } + dispatch({ type: LOGIN_FAIL }); + } +}; + // Logout / Clear Profile export const logout = () => dispatch => { dispatch({ type: CLEAR_PROFILE }); diff --git a/client/src/components/auth/Confirm.js b/client/src/components/auth/Confirm.js new file mode 100644 index 0000000..a1af8a2 --- /dev/null +++ b/client/src/components/auth/Confirm.js @@ -0,0 +1,29 @@ +import React, { useEffect } from 'react'; +import { Redirect } from 'react-router-dom'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; + +import Spinner from '../layout/Spinner'; + +import { confirmEmail } from '../../actions/auth'; + +const Confirm = ({ confirmEmail, isAuthenticated, match }) => { + const { emailToken } = match.params; + useEffect(() => { + confirmEmail(emailToken); + }, [confirmEmail, emailToken]); + + if (isAuthenticated) return ; + return ; +}; + +Confirm.propTypes = { + confirmEmail: PropTypes.func.isRequired, + isAuthenticated: PropTypes.bool, +}; + +const mapStateToProps = state => ({ + isAuthenticated: state.auth.isAuthenticated, +}); + +export default connect(mapStateToProps, { confirmEmail })(Confirm); diff --git a/client/src/components/routing/Routes.js b/client/src/components/routing/Routes.js index 3b4479c..dc44386 100755 --- a/client/src/components/routing/Routes.js +++ b/client/src/components/routing/Routes.js @@ -13,6 +13,7 @@ import Profile from '../profile/Profile'; import Posts from '../posts/Posts'; import Post from '../post/Post'; import NotFound from '../layout/NotFound'; +import Confirm from '../auth/Confirm'; import PrivateRoute from '../routing/PrivateRoute'; const Routes = () => { @@ -22,6 +23,7 @@ const Routes = () => { + From be1fa8102dd09304554b426627b80c9156e8686d Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Mon, 25 Nov 2019 16:24:10 +0100 Subject: [PATCH 09/76] Change Register component --- client/src/components/auth/Register.js | 30 +++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/client/src/components/auth/Register.js b/client/src/components/auth/Register.js index 679a4f4..dfb8764 100755 --- a/client/src/components/auth/Register.js +++ b/client/src/components/auth/Register.js @@ -10,20 +10,21 @@ const Register = ({ setAlert, register, isAuthenticated }) => { name: '', email: '', password: '', - password2: '' + password2: '', }); + const [isMailSent, setIsMailSent] = useState(false); const { name, email, password, password2 } = formData; - const onChange = e => - setFormData({ ...formData, [e.target.name]: e.target.value }); + const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value }); const onSubmit = async e => { + setIsMailSent(false); e.preventDefault(); if (password !== password2) { setAlert('Passwords do not match', 'danger'); } else { - register({ name, email, password }); + (await register({ name, email, password })) && setIsMailSent(true); } }; @@ -31,7 +32,14 @@ const Register = ({ setAlert, register, isAuthenticated }) => { return ; } - return ( + return isMailSent ? ( + +

Confirm Email

+

+ Confirmation mail is sent to {email} +

+
+ ) : (

Sign Up

@@ -56,8 +64,7 @@ const Register = ({ setAlert, register, isAuthenticated }) => { onChange={e => onChange(e)} /> - This site uses Gravatar so if you want a profile image, use a - Gravatar email + This site uses Gravatar so if you want a profile image, use a Gravatar email

@@ -90,14 +97,11 @@ const Register = ({ setAlert, register, isAuthenticated }) => { Register.propTypes = { setAlert: PropTypes.func.isRequired, register: PropTypes.func.isRequired, - isAuthenticated: PropTypes.bool + isAuthenticated: PropTypes.bool, }; const mapStateToProps = state => ({ - isAuthenticated: state.auth.isAuthenticated + isAuthenticated: state.auth.isAuthenticated, }); -export default connect( - mapStateToProps, - { setAlert, register } -)(Register); +export default connect(mapStateToProps, { setAlert, register })(Register); From 038a1aecaf5a7423f7d243221d91738dc8ba87e9 Mon Sep 17 00:00:00 2001 From: Gul Date: Tue, 26 Nov 2019 16:38:48 +0100 Subject: [PATCH 10/76] Add loginWithSocial action --- client/src/actions/auth.js | 64 ++++++++++++++++++---- client/src/components/auth/Login.js | 60 ++++++++++---------- client/src/social-config/firebaseConfig.js | 18 ++++++ package.json | 1 + 4 files changed, 103 insertions(+), 40 deletions(-) create mode 100644 client/src/social-config/firebaseConfig.js diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index 60adae0..8819b1b 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -8,9 +8,10 @@ import { LOGIN_SUCCESS, LOGIN_FAIL, LOGOUT, - CLEAR_PROFILE + CLEAR_PROFILE, } from './types'; import setAuthToken from '../utils/setAuthToken'; +import firebase from 'firebase/app'; // Load User export const loadUser = () => async dispatch => { @@ -23,11 +24,11 @@ export const loadUser = () => async dispatch => { dispatch({ type: USER_LOADED, - payload: res.data + payload: res.data, }); } catch (err) { dispatch({ - type: AUTH_ERROR + type: AUTH_ERROR, }); } }; @@ -36,8 +37,8 @@ export const loadUser = () => async dispatch => { export const register = ({ name, email, password }) => async dispatch => { const config = { headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }; const body = JSON.stringify({ name, email, password }); @@ -47,7 +48,7 @@ export const register = ({ name, email, password }) => async dispatch => { dispatch({ type: REGISTER_SUCCESS, - payload: res.data + payload: res.data, }); dispatch(loadUser()); @@ -59,7 +60,7 @@ export const register = ({ name, email, password }) => async dispatch => { } dispatch({ - type: REGISTER_FAIL + type: REGISTER_FAIL, }); } }; @@ -68,8 +69,8 @@ export const register = ({ name, email, password }) => async dispatch => { export const login = (email, password) => async dispatch => { const config = { headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }; const body = JSON.stringify({ email, password }); @@ -79,7 +80,7 @@ export const login = (email, password) => async dispatch => { dispatch({ type: LOGIN_SUCCESS, - payload: res.data + payload: res.data, }); dispatch(loadUser()); @@ -91,7 +92,48 @@ export const login = (email, password) => async dispatch => { } dispatch({ - type: LOGIN_FAIL + type: LOGIN_FAIL, + }); + } +}; + +// Login User with social +export const loginWithSocial = () => async dispatch => { + console.log('facebook'); + + const provider = new firebase.auth.FacebookAuthProvider(); + + const auth = firebase.auth(); + + try { + const userToken = await firebase + .auth() + .signInWithPopup(provider) + .then(function(result) { + const token = result.credential.accessToken; + const user = result.user; + console.log(user.displayName); + return token; + }); + console.log(userToken); + + console.log('different try'); + + dispatch({ + type: LOGIN_SUCCESS, + payload: auth.currentUser, + }); + + // dispatch(loadUser()); + } catch (err) { + // const errors = err.response.data.errors; + + // if (errors) { + // errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); + // } + + dispatch({ + type: LOGIN_FAIL, }); } }; diff --git a/client/src/components/auth/Login.js b/client/src/components/auth/Login.js index a1fabfd..60295ab 100755 --- a/client/src/components/auth/Login.js +++ b/client/src/components/auth/Login.js @@ -1,75 +1,77 @@ import React, { Fragment, useState } from 'react'; import { Link, Redirect } from 'react-router-dom'; import { connect } from 'react-redux'; +import { login, loginWithSocial } from '../../actions/auth'; import PropTypes from 'prop-types'; -import { login } from '../../actions/auth'; +import firebase from '../../social-config/firebaseConfig'; -const Login = ({ login, isAuthenticated }) => { +const Login = ({ login, isAuthenticated, loginWithSocial }) => { const [formData, setFormData] = useState({ email: '', - password: '' + password: '', }); const { email, password } = formData; - const onChange = e => - setFormData({ ...formData, [e.target.name]: e.target.value }); + const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value }); const onSubmit = async e => { e.preventDefault(); login(email, password); }; + // Redirect if logged in if (isAuthenticated) { - return ; + return ; } return ( -

Sign In

-

- Sign Into Your Account +

Sign In

+

+ Sign Into Your Account

-
onSubmit(e)}> -
+ onSubmit(e)}> +
onChange(e)} required />
-
+ +
onChange(e)} - minLength='6' + minLength="6" />
- + -

- Don't have an account? Sign Up + {isAuthenticated ?

{firebase.auth().currentUser.displayName}
:
} + +

+ Don't have an account? Sign Up

); }; -Login.propTypes = { +login.propTypes = { login: PropTypes.func.isRequired, - isAuthenticated: PropTypes.bool + loginWithSocial: PropTypes.func.isRequired, + isAuthenticated: PropTypes.bool, }; const mapStateToProps = state => ({ - isAuthenticated: state.auth.isAuthenticated + isAuthenticated: state.auth.isAuthenticated, }); -export default connect( - mapStateToProps, - { login } -)(Login); +export default connect(mapStateToProps, { login, loginWithSocial })(Login); diff --git a/client/src/social-config/firebaseConfig.js b/client/src/social-config/firebaseConfig.js new file mode 100644 index 0000000..5318a17 --- /dev/null +++ b/client/src/social-config/firebaseConfig.js @@ -0,0 +1,18 @@ +import * as firebase from 'firebase'; + +// const settings = { timestampsInSnapshots: true }; + +const firebaseConfig = { + apiKey: 'AIzaSyDE8pABtUCH2kzGsN-htLQY8yDednivRiQ', + authDomain: 'ocean-d27d7.firebaseapp.com', + databaseURL: 'https://ocean-d27d7.firebaseio.com', + projectId: 'ocean-d27d7', + storageBucket: 'ocean-d27d7.appspot.com', + messagingSenderId: '732932278090', + appId: '1:732932278090:web:2f6389b5472052698663b3', + measurementId: 'G-3X88ZLXK9L', +}; + +firebase.initializeApp(firebaseConfig); + +export default { firebase, firebaseConfig }; diff --git a/package.json b/package.json index 964b915..89d5737 100755 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "config": "^3.1.0", "express": "^4.16.4", "express-validator": "^5.3.1", + "firebase": "^7.5.0", "gravatar": "^1.8.0", "jsonwebtoken": "^8.5.1", "mongoose": "^5.7.5", From 4489edffed65bc7873b7600363bb59d28873e405 Mon Sep 17 00:00:00 2001 From: Gul Date: Tue, 26 Nov 2019 21:13:34 +0100 Subject: [PATCH 11/76] Refactor firebase calls --- client/src/actions/auth.js | 60 +++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index 8819b1b..884de7e 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -1,5 +1,6 @@ import axios from 'axios'; import { setAlert } from './alert'; + import { REGISTER_SUCCESS, REGISTER_FAIL, @@ -99,38 +100,43 @@ export const login = (email, password) => async dispatch => { // Login User with social export const loginWithSocial = () => async dispatch => { - console.log('facebook'); - const provider = new firebase.auth.FacebookAuthProvider(); - const auth = firebase.auth(); - try { - const userToken = await firebase - .auth() - .signInWithPopup(provider) - .then(function(result) { - const token = result.credential.accessToken; - const user = result.user; - console.log(user.displayName); - return token; - }); - console.log(userToken); - - console.log('different try'); - - dispatch({ - type: LOGIN_SUCCESS, - payload: auth.currentUser, + const result = await firebase.auth().signInWithPopup(provider); + + const { email, displayName, uid } = result.user; + const { accessToken } = result.credential; + console.log(result); + + // const body = { displayName, email }; + // const res = await axios.post('/api/users', body, config); + // console.log(res.data); + + firebase.auth().onAuthStateChanged(user => { + if (user) { + dispatch({ + type: LOGIN_SUCCESS, + payload: { + token: accessToken, + user: { + _id: uid, + name: displayName, + email: email, + date: Date.now, + }, + }, + }); + + // dispatch(loadUser()); + } }); - - // dispatch(loadUser()); } catch (err) { - // const errors = err.response.data.errors; + const errors = err.response.data.errors; - // if (errors) { - // errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); - // } + if (errors) { + errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); + } dispatch({ type: LOGIN_FAIL, @@ -138,7 +144,7 @@ export const loginWithSocial = () => async dispatch => { } }; -// Logout / Clear Profile +// Logout / Clear profile export const logout = () => dispatch => { dispatch({ type: CLEAR_PROFILE }); dispatch({ type: LOGOUT }); From f8a5c16070e3509c774ed4fc270c623de5330784 Mon Sep 17 00:00:00 2001 From: Gul Date: Tue, 26 Nov 2019 22:22:39 +0100 Subject: [PATCH 12/76] Remove config --- client/src/social-config/firebaseConfig.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/src/social-config/firebaseConfig.js b/client/src/social-config/firebaseConfig.js index 5318a17..8c54a8b 100644 --- a/client/src/social-config/firebaseConfig.js +++ b/client/src/social-config/firebaseConfig.js @@ -3,14 +3,14 @@ import * as firebase from 'firebase'; // const settings = { timestampsInSnapshots: true }; const firebaseConfig = { - apiKey: 'AIzaSyDE8pABtUCH2kzGsN-htLQY8yDednivRiQ', - authDomain: 'ocean-d27d7.firebaseapp.com', - databaseURL: 'https://ocean-d27d7.firebaseio.com', - projectId: 'ocean-d27d7', - storageBucket: 'ocean-d27d7.appspot.com', - messagingSenderId: '732932278090', - appId: '1:732932278090:web:2f6389b5472052698663b3', - measurementId: 'G-3X88ZLXK9L', + apiKey: '', + authDomain: '', + databaseURL: '', + projectId: '', + storageBucket: '', + messagingSenderId: '', + appId: '', + measurementId: '', }; firebase.initializeApp(firebaseConfig); From 25c19adaa791e49fbe06d19cdb4f94b83a1d87a1 Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Wed, 27 Nov 2019 17:17:41 +0100 Subject: [PATCH 13/76] Add resending expired link --- client/src/actions/auth.js | 26 ++++++++- client/src/components/auth/Confirm.js | 49 ++++++++++++++-- routes/api/users.js | 80 +++++++++++++++++++++++---- 3 files changed, 136 insertions(+), 19 deletions(-) diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index 5bbdabc..388382d 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -98,8 +98,9 @@ export const login = (email, password) => async dispatch => { // Confirm email export const confirmEmail = token => async dispatch => { try { - const response = await axios.put(`/api/users/confirm/${token}`); - dispatch({ type: CONFIRM_EMAIL, payload: response.data }); + const res = await axios.put(`/api/users/confirm/${token}`); + if (res.data.msg) dispatch(setAlert(res.data.msg, 'success')); + dispatch({ type: CONFIRM_EMAIL, payload: res.data }); dispatch(loadUser()); } catch (error) { const { errors } = error.response.data; @@ -107,6 +108,27 @@ export const confirmEmail = token => async dispatch => { errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); } dispatch({ type: LOGIN_FAIL }); + return errors && errors[0].msg; + } +}; + +// Resend email +export const resendEmail = email => async dispatch => { + const config = { + headers: { + 'Content-Type': 'application/json', + }, + }; + + const body = JSON.stringify({ email }); + try { + const res = await axios.post('/api/users/resend', body, config); + dispatch(setAlert(res.data.msg, 'success')); + } catch (error) { + const { errors } = error.response.data; + if (errors) { + errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); + } } }; diff --git a/client/src/components/auth/Confirm.js b/client/src/components/auth/Confirm.js index a1af8a2..acc76d5 100644 --- a/client/src/components/auth/Confirm.js +++ b/client/src/components/auth/Confirm.js @@ -1,29 +1,66 @@ -import React, { useEffect } from 'react'; +import React, { Fragment, useEffect, useState } from 'react'; import { Redirect } from 'react-router-dom'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import Spinner from '../layout/Spinner'; -import { confirmEmail } from '../../actions/auth'; +import { confirmEmail, resendEmail } from '../../actions/auth'; -const Confirm = ({ confirmEmail, isAuthenticated, match }) => { +const Confirm = ({ confirmEmail, resendEmail, isAuthenticated, loading, match }) => { const { emailToken } = match.params; + const [error, setError] = useState(''); + const [email, setEmail] = useState(''); + const onChange = e => setEmail(e.target.value); + + const onSubmit = async e => { + e.preventDefault(); + resendEmail(email); + }; + useEffect(() => { - confirmEmail(emailToken); + (async () => { + setError(await confirmEmail(emailToken)); + })(); }, [confirmEmail, emailToken]); + if (error === 'Invalid Token') return ; + if (isAuthenticated) return ; - return ; + + if (error === 'Email validation link expired') + return ( + +

+ Resend Confirmation Email +

+
onSubmit(e)}> +
+ onChange(e)} + required + /> +
+ +
+
+ ); + return loading && ; }; Confirm.propTypes = { confirmEmail: PropTypes.func.isRequired, + resendEmail: PropTypes.func.isRequired, isAuthenticated: PropTypes.bool, }; const mapStateToProps = state => ({ isAuthenticated: state.auth.isAuthenticated, + loading: state.auth.loading, }); -export default connect(mapStateToProps, { confirmEmail })(Confirm); +export default connect(mapStateToProps, { confirmEmail, resendEmail })(Confirm); diff --git a/routes/api/users.js b/routes/api/users.js index ddd5e3c..297449b 100755 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -84,14 +84,8 @@ router.post( ${confirmURL}`, }; - const result = await transporter.sendMail(msg); - // console.log(result); + await transporter.sendMail(msg); res.json({ msg: 'Confirmation mail sent' }); - - // jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { - // if (err) throw err; - // res.json({ token }); - // }); } catch (err) { console.error(err.message); res.status(500).send('Server error'); @@ -108,21 +102,85 @@ router.put('/confirm/:emailToken', async (req, res) => { const { user: { id }, } = jwt.verify(emailToken, config.get('emailSecret')); + const user = await User.findOne({ _id: id }); + let msg = ''; + if (user.confirmed) { + msg = 'Email is already confirmed'; + } await User.findOneAndUpdate({ _id: id }, { $set: { confirmed: true } }, { new: true }); - // TODO handle error cases const payload = { user: { id, }, }; - jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 999999 }, (err, token) => { + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { if (err) throw err; - res.json({ token }); + res.json({ token, msg }); }); } catch (error) { - return res.status(401).send({ msg: 'Invalid Token' }); + if (error.name === 'TokenExpiredError') { + return res.status(401).send({ errors: [{ msg: 'Email validation link expired' }] }); + } else { + return res.status(401).send({ errors: [{ msg: 'Invalid Token' }] }); + } } }); +// @route POST /api/users/resend +// @desc Resend confirmation email +// @access Public +router.post( + '/resend', + [check('email', 'Please include a valid email').isEmail()], + async (req, res) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + const { email } = req.body; + try { + const user = await User.findOne({ email }); + if (!user) { + return res + .status(400) + .json({ errors: [{ msg: `No user found registered with ${email}` }] }); + } + + const payload = { + user: { + id: user.id, + }, + }; + + const transporter = nodemailer.createTransport({ + host: 'smtp-pulse.com', + port: 465, + secure: true, + auth: { + user: process.env.MAIL_USER, + pass: process.env.MAIL_PASS, + }, + }); + + const emailToken = jwt.sign(payload, config.get('emailSecret'), { expiresIn: '1d' }); + const confirmURL = `${HOST_ADDR}/confirm/${emailToken}`; + + const msg = { + to: user.email, + from: process.env.MAIL_USER, + subject: 'Confirm Email', + html: `Hurrah! You've created a Developer Hub account with ${user.email}. Please take a moment to confirm that we can use this address to send you mails.
+ ${confirmURL}`, + }; + + await transporter.sendMail(msg); + res.json({ msg: 'Confirmation mail sent' }); + } catch (err) { + console.error(err.message); + res.status(500).send('Server error'); + } + }, +); + module.exports = router; From adae69847ea9e65a76738aafee52e2441377e0fa Mon Sep 17 00:00:00 2001 From: Gul Date: Thu, 28 Nov 2019 08:34:33 +0100 Subject: [PATCH 14/76] Changes after Q&A --- client/src/actions/auth.js | 23 ++++++++++++++++------ client/src/components/auth/Login.js | 9 +++++++-- client/src/social-config/firebaseConfig.js | 5 +++-- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index 884de7e..b73a9f3 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -115,16 +115,27 @@ export const loginWithSocial = () => async dispatch => { firebase.auth().onAuthStateChanged(user => { if (user) { + dispatch({ + type: REGISTER_SUCCESS, + payload: { + token: accessToken, + }, + }); + dispatch({ type: LOGIN_SUCCESS, payload: { token: accessToken, - user: { - _id: uid, - name: displayName, - email: email, - date: Date.now, - }, + }, + }); + + dispatch({ + type: USER_LOADED, + payload: { + _id: uid, + name: displayName, + email: email, + date: Date.now, }, }); diff --git a/client/src/components/auth/Login.js b/client/src/components/auth/Login.js index 60295ab..389632b 100755 --- a/client/src/components/auth/Login.js +++ b/client/src/components/auth/Login.js @@ -3,7 +3,10 @@ import { Link, Redirect } from 'react-router-dom'; import { connect } from 'react-redux'; import { login, loginWithSocial } from '../../actions/auth'; import PropTypes from 'prop-types'; -import firebase from '../../social-config/firebaseConfig'; + +// if I remove import firebaseApp somehow login button doesn't work. Is it because I initialize firebase in firebaseConfig and you have to import it somewhere? +import firebaseApp from '../../social-config/firebaseConfig'; +import firebase from 'firebase'; const Login = ({ login, isAuthenticated, loginWithSocial }) => { const [formData, setFormData] = useState({ @@ -56,7 +59,9 @@ const Login = ({ login, isAuthenticated, loginWithSocial }) => { {isAuthenticated ?
{firebase.auth().currentUser.displayName}
:
} - +

Don't have an account? Sign Up

diff --git a/client/src/social-config/firebaseConfig.js b/client/src/social-config/firebaseConfig.js index 8c54a8b..64108e9 100644 --- a/client/src/social-config/firebaseConfig.js +++ b/client/src/social-config/firebaseConfig.js @@ -13,6 +13,7 @@ const firebaseConfig = { measurementId: '', }; -firebase.initializeApp(firebaseConfig); +// Could not remove this into another file it breaks the code +const firebaseApp = firebase.initializeApp(firebaseConfig); -export default { firebase, firebaseConfig }; +export default { firebaseApp, firebaseConfig }; From cf36e5bf66134c51632cee4599ecdd447f93e8ee Mon Sep 17 00:00:00 2001 From: Gul Date: Thu, 28 Nov 2019 11:02:24 +0100 Subject: [PATCH 15/76] Register user in db with facebook --- client/src/actions/auth.js | 61 ++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index b73a9f3..e3f28ff 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -13,6 +13,7 @@ import { } from './types'; import setAuthToken from '../utils/setAuthToken'; import firebase from 'firebase/app'; +import { Mongoose } from 'mongoose'; // Load User export const loadUser = () => async dispatch => { @@ -44,6 +45,7 @@ export const register = ({ name, email, password }) => async dispatch => { const body = JSON.stringify({ name, email, password }); + console.log(body); try { const res = await axios.post('/api/users', body, config); @@ -98,6 +100,8 @@ export const login = (email, password) => async dispatch => { } }; +// Register User with Social + // Login User with social export const loginWithSocial = () => async dispatch => { const provider = new firebase.auth.FacebookAuthProvider(); @@ -109,37 +113,36 @@ export const loginWithSocial = () => async dispatch => { const { accessToken } = result.credential; console.log(result); - // const body = { displayName, email }; - // const res = await axios.post('/api/users', body, config); - // console.log(res.data); + // Register user with facebook data + const config = { + headers: { + 'Content-Type': 'application/json', + }, + }; + + const body = JSON.stringify({ name: displayName, email: email, password: uid }); + + const res = await axios.post('/api/users', body, config); + + dispatch({ + type: REGISTER_SUCCESS, + payload: res.data, + }); + + // dispatch(loadUser()); + console.log('user sent to database'); - firebase.auth().onAuthStateChanged(user => { + firebase.auth().onAuthStateChanged(async user => { if (user) { - dispatch({ - type: REGISTER_SUCCESS, - payload: { - token: accessToken, - }, - }); - - dispatch({ - type: LOGIN_SUCCESS, - payload: { - token: accessToken, - }, - }); - - dispatch({ - type: USER_LOADED, - payload: { - _id: uid, - name: displayName, - email: email, - date: Date.now, - }, - }); - - // dispatch(loadUser()); + // dispatch({ + // type: LOGIN_SUCCESS, + // payload: { + // token: accessToken, + // }, + // }); + console.log('this is a facebook user'); + + dispatch(loadUser()); } }); } catch (err) { From 3dcdf83589a5278f629c98bcdd52baa0e4eadc66 Mon Sep 17 00:00:00 2001 From: Adham Elias Botrous <50738429+Adham-Elias-Botours@users.noreply.github.com> Date: Thu, 28 Nov 2019 15:06:53 +0100 Subject: [PATCH 16/76] finish ading adham code to oguzhan --- config/default.json | 10 +++-- models/User.js | 1 + routes/api/auth.js | 98 +++++++++++++++++++++++++++++++++++++++++ routes/api/users.js | 103 ++++++++++++++++++++++++++------------------ send_mail.js | 20 +++++++++ 5 files changed, 186 insertions(+), 46 deletions(-) create mode 100644 send_mail.js diff --git a/config/default.json b/config/default.json index 1c210b2..3ac1fa6 100755 --- a/config/default.json +++ b/config/default.json @@ -1,6 +1,10 @@ { - "mongoURI": "", + "mongoURI": "mongodb+srv://adham123:adham123@devconnector-5ex4n.mongodb.net/test?retryWrites=true&w=majority", "jwtSecret": "secret", - "githubClientId": "", - "githubSecret": "" + "emailSecret": "secret2", + "githubClientId": "4807401adfe078bad374", + "githubSecret": "6e069ffe38e4dd6fad7eb69c7d768e8b21dfad8d", + "email": "oguzhan@turgut.biz", + "emailPw": "4fsgsRekDYea", + "local_url": "http://localhost:3000" } diff --git a/models/User.js b/models/User.js index fdc249a..841a131 100755 --- a/models/User.js +++ b/models/User.js @@ -18,6 +18,7 @@ const UserSchema = new mongoose.Schema({ type: String, required: true, }, + resetPasswordLink: { data: String, default: '' }, avatar: { type: String, }, diff --git a/routes/api/auth.js b/routes/api/auth.js index aced0c5..a0f26a8 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -4,8 +4,11 @@ const bcrypt = require('bcryptjs'); const auth = require('../../middleware/auth'); const jwt = require('jsonwebtoken'); const config = require('config'); +const { sendEmail } = require('../../send_mail'); const { check, validationResult } = require('express-validator/check'); +const HOST_ADDR = process.env.HOST_ADDR || config.get('local_url'); + const User = require('../../models/User'); // @route GET api/auth @@ -71,4 +74,99 @@ router.post( }, ); +// password forgot and reset routes************** +// password forgot and reset routes************* +router.put( + '/forgot-password', + [check('email', 'Please include a valid email').isEmail()], + (req, res) => { + console.log('forget password'); + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + + console.log('forgot password finding user with that email'); + const { email } = req.body; + console.log('signin req.body', email); + + // find the user in our database based on email + User.findOne({ email }, (err, user) => { + // if err or no user + if (err || !user) + return res.status('401').json({ + error: 'User with that email does not exist!' + }); + + // generate a token with user id and secret + const token = jwt.sign({ _id: user._id }, config.get('jwtSecret')); + // res.json({ user, token }); + + // email data + const resetPwUrl=`${HOST_ADDR}/api/auth/reset-password/${token}` + const emailData = { + from: 'a2neverever@gmail.com', + to: email, + subject: 'Password Reset Instructions', + text: `Please use the following link to reset your password: ${resetPwUrl}`, + html: `

Please use the following link to reset your password:

${resetPwUrl}

` + }; + console.log(emailData.text); + + return user.updateOne({ resetPasswordLink: token }, (err, success) => { + if (err) { + return res.json({ message: err }); + } else { + sendEmail(emailData); + return res.status(200).json({ + message: `Email has been sent to ${email}. Follow the instructions to reset your password.` + }); + } + }); + }); + } +); + +// *** +router.put( + '/reset-password', + [ + check( + 'newPassword', + 'Please enter a password with 6 or more characters' + ).isLength({ min: 6 }) + ], + async (req, res) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + // in the frontend we have to use ReactRouterDom to get the resetPasswordLink from the url and push it to the body + const { resetPasswordLink, newPassword } = req.body; + try { + let user = await User.findOne({ resetPasswordLink }); + // if Not the user + if (!user) + return res.status('401').json({ + error: 'Invalid Link!' + }); + // there is a user + const salt = await bcrypt.genSalt(10); + const newPasswordCrypt = await bcrypt.hash(newPassword, salt); + user = await User.findOneAndUpdate( + { resetPasswordLink }, + { $set: { resetPasswordLink: '', password: newPasswordCrypt } } + ); + + res.json({ + message: `Great! Now you can login with your new password.` + }); + } catch (error) { + res.status(400).json({ + error: error.message + }); + } + } +); + module.exports = router; diff --git a/routes/api/users.js b/routes/api/users.js index 297449b..ad68341 100755 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -4,10 +4,12 @@ const gravatar = require('gravatar'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const config = require('config'); +const { sendEmail } = require('../../send_mail'); const { check, validationResult } = require('express-validator/check'); const nodemailer = require('nodemailer'); -const HOST_ADDR = process.env.HOST_ADDR || ''; +const HOST_ADDR = process.env.HOST_ADDR || config.get('local_url'); +console.log(HOST_ADDR); const User = require('../../models/User'); @@ -21,7 +23,10 @@ router.post( .not() .isEmpty(), check('email', 'Please include a valid email').isEmail(), - check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 }), + check( + 'password', + 'Please enter a password with 6 or more characters' + ).isLength({ min: 6 }) ], async (req, res) => { const errors = validationResult(req); @@ -35,20 +40,22 @@ router.post( let user = await User.findOne({ email }); if (user) { - return res.status(400).json({ errors: [{ msg: 'User already exists' }] }); + return res + .status(400) + .json({ errors: [{ msg: 'User already exists' }] }); } const avatar = gravatar.url(email, { s: '200', r: 'pg', - d: 'mm', + d: 'mm' }); user = new User({ name, email, avatar, - password, + password }); const salt = await bcrypt.genSalt(10); @@ -59,38 +66,35 @@ router.post( const payload = { user: { - id: user.id, - }, + id: user.id + } }; - const transporter = nodemailer.createTransport({ - host: 'smtp-pulse.com', - port: 465, - secure: true, - auth: { - user: process.env.MAIL_USER, - pass: process.env.MAIL_PASS, - }, - }); + - const emailToken = jwt.sign(payload, config.get('emailSecret'), { expiresIn: '1d' }); + const emailToken = jwt.sign(payload, config.get('emailSecret'), { + expiresIn: '1d' + }); const confirmURL = `${HOST_ADDR}/confirm/${emailToken}`; - const msg = { + const emailData = { to: user.email, - from: process.env.MAIL_USER, - subject: 'Confirm Email', - html: `Hurrah! You've created a Developer Hub account with ${user.email}. Please take a moment to confirm that we can use this address to send you mails.
- ${confirmURL}`, + from: process.env.MAIL_USER || config.get('email'), + + subject: 'Confirm Email Instructions', + text: `Hurrah! You've created a Developer Hub account with ${user.email}. + Please take a moment to confirm that we can use this address to send you mails.: ${confirmURL}`, + html: `

Hurrah! You've created a Developer Hub account with ${user.email}.

Please take a moment to confirm that we can use this address to send you mails.

+

${confirmURL}

` }; - await transporter.sendMail(msg); + await sendEmail(emailData); res.json({ msg: 'Confirmation mail sent' }); } catch (err) { console.error(err.message); res.status(500).send('Server error'); } - }, + } ); // @route PUT /api/users/confirm/:emailToken @@ -100,27 +104,38 @@ router.put('/confirm/:emailToken', async (req, res) => { const { emailToken } = req.params; try { const { - user: { id }, + user: { id } } = jwt.verify(emailToken, config.get('emailSecret')); const user = await User.findOne({ _id: id }); let msg = ''; if (user.confirmed) { msg = 'Email is already confirmed'; } - await User.findOneAndUpdate({ _id: id }, { $set: { confirmed: true } }, { new: true }); + await User.findOneAndUpdate( + { _id: id }, + { $set: { confirmed: true } }, + { new: true } + ); const payload = { user: { - id, - }, + id + } }; - jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { - if (err) throw err; - res.json({ token, msg }); - }); + jwt.sign( + payload, + config.get('jwtSecret'), + { expiresIn: 360000 }, + (err, token) => { + if (err) throw err; + res.json({ token, msg }); + } + ); } catch (error) { if (error.name === 'TokenExpiredError') { - return res.status(401).send({ errors: [{ msg: 'Email validation link expired' }] }); + return res + .status(401) + .send({ errors: [{ msg: 'Email validation link expired' }] }); } else { return res.status(401).send({ errors: [{ msg: 'Invalid Token' }] }); } @@ -142,15 +157,15 @@ router.post( try { const user = await User.findOne({ email }); if (!user) { - return res - .status(400) - .json({ errors: [{ msg: `No user found registered with ${email}` }] }); + return res.status(400).json({ + errors: [{ msg: `No user found registered with ${email}` }] + }); } const payload = { user: { - id: user.id, - }, + id: user.id + } }; const transporter = nodemailer.createTransport({ @@ -159,11 +174,13 @@ router.post( secure: true, auth: { user: process.env.MAIL_USER, - pass: process.env.MAIL_PASS, - }, + pass: process.env.MAIL_PASS + } }); - const emailToken = jwt.sign(payload, config.get('emailSecret'), { expiresIn: '1d' }); + const emailToken = jwt.sign(payload, config.get('emailSecret'), { + expiresIn: '1d' + }); const confirmURL = `${HOST_ADDR}/confirm/${emailToken}`; const msg = { @@ -171,7 +188,7 @@ router.post( from: process.env.MAIL_USER, subject: 'Confirm Email', html: `Hurrah! You've created a Developer Hub account with ${user.email}. Please take a moment to confirm that we can use this address to send you mails.
- ${confirmURL}`, + ${confirmURL}` }; await transporter.sendMail(msg); @@ -180,7 +197,7 @@ router.post( console.error(err.message); res.status(500).send('Server error'); } - }, + } ); module.exports = router; diff --git a/send_mail.js b/send_mail.js new file mode 100644 index 0000000..bf6d264 --- /dev/null +++ b/send_mail.js @@ -0,0 +1,20 @@ +const nodeMailer = require('nodemailer'); +const config = require('config'); + +exports.sendEmail = emailData => { + const transporter = nodeMailer.createTransport({ + host: 'smtp-pulse.com', + port: 465, + secure: true, + auth: { + user: process.env.MAIL_USER || config.get('email'), + pass: process.env.MAIL_PASS || config.get('emailPw') + } + }); + return transporter + .sendMail(emailData) + .then(info => console.log(`Message sent: ${info.response}`)) + .catch(err => console.log(`Problem sending email: ${err}`)); +}; + +// ---------------- From 6c34e93fb4b807405d2e5aafdbadae699ef7f148 Mon Sep 17 00:00:00 2001 From: Adham Elias Botrous <50738429+Adham-Elias-Botours@users.noreply.github.com> Date: Thu, 28 Nov 2019 17:20:13 +0100 Subject: [PATCH 17/76] fix auth send mail --- models/User.js | 14 +++++++------- routes/api/auth.js | 37 ++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/models/User.js b/models/User.js index 841a131..b4c7227 100755 --- a/models/User.js +++ b/models/User.js @@ -3,29 +3,29 @@ const mongoose = require('mongoose'); const UserSchema = new mongoose.Schema({ name: { type: String, - required: true, + required: true }, email: { type: String, required: true, - unique: true, + unique: true }, confirmed: { type: Boolean, - default: false, + default: false }, password: { type: String, - required: true, + required: true }, resetPasswordLink: { data: String, default: '' }, avatar: { - type: String, + type: String }, date: { type: Date, - default: Date.now, - }, + default: Date.now + } }); module.exports = User = mongoose.model('user', UserSchema); diff --git a/routes/api/auth.js b/routes/api/auth.js index a0f26a8..695c58a 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -31,7 +31,7 @@ router.post( '/', [ check('email', 'Please include a valid email').isEmail(), - check('password', 'Password is required').exists(), + check('password', 'Password is required').exists() ], async (req, res) => { const errors = validationResult(req); @@ -45,33 +45,44 @@ router.post( let user = await User.findOne({ email }); if (!user) { - return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] }); + return res + .status(400) + .json({ errors: [{ msg: 'Invalid Credentials' }] }); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { - return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] }); + return res + .status(400) + .json({ errors: [{ msg: 'Invalid Credentials' }] }); } if (!user.confirmed) - return res.status(400).json({ errors: [{ msg: 'Please confirm your email to login' }] }); + return res + .status(400) + .json({ errors: [{ msg: 'Please confirm your email to login' }] }); const payload = { user: { - id: user.id, - }, + id: user.id + } }; - jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { - if (err) throw err; - res.json({ token }); - }); + jwt.sign( + payload, + config.get('jwtSecret'), + { expiresIn: 360000 }, + (err, token) => { + if (err) throw err; + res.json({ token }); + } + ); } catch (err) { console.error(err.message); res.status(500).send('Server error'); } - }, + } ); // password forgot and reset routes************** @@ -103,9 +114,9 @@ router.put( // res.json({ user, token }); // email data - const resetPwUrl=`${HOST_ADDR}/api/auth/reset-password/${token}` + const resetPwUrl = `${HOST_ADDR}/api/auth/reset-password/${token}`; const emailData = { - from: 'a2neverever@gmail.com', + from: process.env.MAIL_USER || config.get('email'), to: email, subject: 'Password Reset Instructions', text: `Please use the following link to reset your password: ${resetPwUrl}`, From 24c4223ace005508287c8f046625a02de304a4ba Mon Sep 17 00:00:00 2001 From: musafir928 Date: Thu, 28 Nov 2019 18:00:34 +0100 Subject: [PATCH 18/76] add config default --- client/.DS_Store | Bin 0 -> 8196 bytes config/default.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 client/.DS_Store diff --git a/client/.DS_Store b/client/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..275165aa6dc84f58f56293d42c379d9cd6211af1 GIT binary patch literal 8196 zcmeHMPfrs;6n_J#TSQqDH;>nY~`BQ`z4qlAJdCAQ0&71e$?EIS9>1zRiWGclWfF1y_ zunDw}Vz-BgdC?lANn28g1mgknunZiEP=T<)+ZYT3h5^HXVZbn882A?$z&l%rwa>k; zW=(GxFbw>c42buGgH52Tv8qtMIESE1<*1BQV{21M=N2257%bXWaOAR+nL`E<;;ghJE5lcU|0J zjH`f;>jhBQy#{$v=i7`=5;b^z-`mSyjmvt+AN_I|#L3{`C(_j1a_I08%WAb+kF`F` zJ?5*q#7io1#*5~~Xv#|-`&ysoJlxhje zF_w99YQnNoRw{4zY;0T_y*%IyUmDsRa5hFq2M3&y;VYY)DXaV3&{%e1d2M~;+2-?C zxD^zl-;B_%#@qdC)HDe}VMT9`ysvHVi*467m}))RcIoK!XLLRdq=<_COjmLd3pp#HMR=!R4Uc1lNBUyzIPN|m z@)*C}ag24&MiA*7Yyw@4RfWPN?D7^yH21$C#E4>%$ULfH zz%cNq43L)b+;|3~ Date: Thu, 28 Nov 2019 20:25:22 +0100 Subject: [PATCH 19/76] add adil work after adjustment , frontend --- client/src/components/auth/Login.js | 43 ++++----- .../password-forms/PasswordEmailFrom.js | 73 ++++++++++++++ .../password-forms/PasswordReset.js | 94 +++++++++++++++++++ client/src/components/routing/Routes.js | 32 ++++--- routes/api/auth.js | 6 +- 5 files changed, 212 insertions(+), 36 deletions(-) create mode 100644 client/src/components/password-forms/PasswordEmailFrom.js create mode 100644 client/src/components/password-forms/PasswordReset.js diff --git a/client/src/components/auth/Login.js b/client/src/components/auth/Login.js index a1fabfd..ddfb404 100755 --- a/client/src/components/auth/Login.js +++ b/client/src/components/auth/Login.js @@ -21,40 +21,44 @@ const Login = ({ login, isAuthenticated }) => { }; if (isAuthenticated) { - return ; + return ; } return ( -

Sign In

-

- Sign Into Your Account +

Sign In

+

+ Sign Into Your Account

-
onSubmit(e)}> -
+ onSubmit(e)}> +
onChange(e)} required />
-
+
onChange(e)} - minLength='6' + minLength="6" />
- + -

- Don't have an account? Sign Up +

+ Forgot password?{' '} + Send password recovery email +

+

+ Don't have an account? Sign Up

); @@ -69,7 +73,4 @@ const mapStateToProps = state => ({ isAuthenticated: state.auth.isAuthenticated }); -export default connect( - mapStateToProps, - { login } -)(Login); +export default connect(mapStateToProps, { login })(Login); diff --git a/client/src/components/password-forms/PasswordEmailFrom.js b/client/src/components/password-forms/PasswordEmailFrom.js new file mode 100644 index 0000000..d2134a0 --- /dev/null +++ b/client/src/components/password-forms/PasswordEmailFrom.js @@ -0,0 +1,73 @@ +import React, { Fragment, useState } from 'react'; +import axios from 'axios'; +import { setAlert } from '../../actions/alert'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; + +const PasswordEmailForm = ({ setAlert }) => { + const [formData, setFormData] = useState({}); + const [sent, setSent] = useState(false); + + const onChange = e => setFormData({ ...formData, email: e.target.value }); + const onSubmit = async e => { + try { + e.preventDefault(); + + // send email + const reqConfig = { + headers: { + 'Content-Type': 'application/json' + } + }; + + const reqBody = JSON.stringify({ email: formData.email }); + + await axios.put(`/api/auth/forgot-password/`, reqBody, reqConfig); + setSent(true); + } catch (err) { + console.error(err); + } + }; + if (sent) { + // return ; + setAlert('Email sent', 'success'); + + return ( + +

Rest password emial is sent.

+

+ Check your emial: {formData.email} +

+
+ ); + } + + return ( + +

Forgot Password

+

+ Input Your Email And Submit To Send + Recovery Email +

+
onSubmit(e)}> +
+ onChange(e)} + required + /> +
+ + +
+
+ ); +}; + +PasswordEmailForm.propTypes = { + setAlert: PropTypes.func.isRequired +}; + +export default connect(null, { setAlert })(PasswordEmailForm); diff --git a/client/src/components/password-forms/PasswordReset.js b/client/src/components/password-forms/PasswordReset.js new file mode 100644 index 0000000..0aa2e67 --- /dev/null +++ b/client/src/components/password-forms/PasswordReset.js @@ -0,0 +1,94 @@ +import React, { Fragment, useState } from 'react'; +// import { Redirect } from "react-router-dom"; +import { Link } from 'react-router-dom'; +import { connect } from 'react-redux'; +import axios from 'axios'; +import { setAlert } from '../../actions/alert'; +import PropTypes from 'prop-types'; + +const PasswordReset = ({ match, setAlert }) => { + const [formData, setFormData] = useState({ + password: '', + password2: '' + }); + + const { password, password2 } = formData; + const [redirect, setRedirect] = useState(false); + + const onChange = e => + setFormData({ ...formData, [e.target.name]: e.target.value }); + const resetPasswordLink = match.params.token; + console.log(resetPasswordLink); + const onSubmit = async e => { + e.preventDefault(); + if (password !== password2) { + setAlert('Passwords do not match', 'danger'); + e.target.password.value = ''; + e.target.password2.value = ''; + } else { + setRedirect(true); + const reqConfig = { + headers: { + 'Content-Type': 'application/json' + } + }; + + const reqBody = JSON.stringify({ + resetPasswordLink, + newPassword: formData.password + }); + + await axios.put(`/api/auth/reset-password`, reqBody, reqConfig); + } + }; + if (redirect) { + // return ; + setAlert(`Password Reset success`, 'success'); + return ( +

+ Password Reset Success{' '} + + Login + +

+ ); + } + + return ( + +

Reset Password

+

+ Input your new Password +

+
onSubmit(e)}> +
+ onChange(e)} + required + /> +
+
+ onChange(e)} + required + /> +
+ + +
+
+ ); +}; + +PasswordReset.propTypes = { + setAlert: PropTypes.func.isRequired, + match: PropTypes.object.isRequired +}; + +export default connect(null, { setAlert })(PasswordReset); diff --git a/client/src/components/routing/Routes.js b/client/src/components/routing/Routes.js index dc44386..86216b7 100755 --- a/client/src/components/routing/Routes.js +++ b/client/src/components/routing/Routes.js @@ -15,24 +15,30 @@ import Post from '../post/Post'; import NotFound from '../layout/NotFound'; import Confirm from '../auth/Confirm'; import PrivateRoute from '../routing/PrivateRoute'; +import PasswordEmailFrom from '../password-forms/PasswordEmailFrom'; +import PasswordReset from '../password-forms/PasswordReset'; const Routes = () => { return ( -
+
- - - - - - - - - - - - + + + + + + + + + + + + + + + +
diff --git a/routes/api/auth.js b/routes/api/auth.js index 695c58a..08e6818 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -114,7 +114,7 @@ router.put( // res.json({ user, token }); // email data - const resetPwUrl = `${HOST_ADDR}/api/auth/reset-password/${token}`; + const resetPwUrl = `${HOST_ADDR}/reset-password/${token}`; const emailData = { from: process.env.MAIL_USER || config.get('email'), to: email, @@ -153,8 +153,10 @@ router.put( return res.status(400).json({ errors: errors.array() }); } // in the frontend we have to use ReactRouterDom to get the resetPasswordLink from the url and push it to the body - const { resetPasswordLink, newPassword } = req.body; + try { + const { resetPasswordLink, newPassword } = req.body; + console.log(resetPasswordLink, newPassword); let user = await User.findOne({ resetPasswordLink }); // if Not the user if (!user) From 9e355c64d9a667c30cc625c373623397d4d8846b Mon Sep 17 00:00:00 2001 From: musafir928 Date: Thu, 28 Nov 2019 20:55:14 +0100 Subject: [PATCH 20/76] password reset issue (with invalid link) solved --- .../password-forms/PasswordReset.js | 104 +++++++++++------- routes/api/auth.js | 72 ++++++------ 2 files changed, 97 insertions(+), 79 deletions(-) diff --git a/client/src/components/password-forms/PasswordReset.js b/client/src/components/password-forms/PasswordReset.js index 0aa2e67..30318a8 100644 --- a/client/src/components/password-forms/PasswordReset.js +++ b/client/src/components/password-forms/PasswordReset.js @@ -1,53 +1,75 @@ -import React, { Fragment, useState } from 'react'; -// import { Redirect } from "react-router-dom"; -import { Link } from 'react-router-dom'; -import { connect } from 'react-redux'; -import axios from 'axios'; -import { setAlert } from '../../actions/alert'; -import PropTypes from 'prop-types'; +import React, { Fragment, useState } from "react"; +import { Redirect } from "react-router-dom"; +import { Link } from "react-router-dom"; +import { connect } from "react-redux"; +import axios from "axios"; +import { setAlert } from "../../actions/alert"; +import PropTypes from "prop-types"; const PasswordReset = ({ match, setAlert }) => { const [formData, setFormData] = useState({ - password: '', - password2: '' + password: "", + password2: "" }); const { password, password2 } = formData; const [redirect, setRedirect] = useState(false); + const [resetError, setResetError] = useState(false); const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value }); const resetPasswordLink = match.params.token; - console.log(resetPasswordLink); const onSubmit = async e => { e.preventDefault(); if (password !== password2) { - setAlert('Passwords do not match', 'danger'); - e.target.password.value = ''; - e.target.password2.value = ''; + setAlert("Passwords do not match", "danger"); + e.target.password.value = ""; + e.target.password2.value = ""; } else { - setRedirect(true); - const reqConfig = { - headers: { - 'Content-Type': 'application/json' - } - }; + try { + const reqConfig = { + headers: { + "Content-Type": "application/json" + } + }; - const reqBody = JSON.stringify({ - resetPasswordLink, - newPassword: formData.password - }); + const reqBody = JSON.stringify({ + resetPasswordLink, + newPassword: formData.password + }); - await axios.put(`/api/auth/reset-password`, reqBody, reqConfig); + await axios.put(`/api/auth/reset-password`, reqBody, reqConfig); + setRedirect(true); + } catch (err) { + console.error(err.message); + setAlert("Password Reset Link Invalid", "danger"); + setResetError(true); + } } }; + + if (resetError) { + return ( + <> + {" "} +

+ Resend Recovery Email?{" "} + Resend password recovery email +

+

+ Login? Login +

+ + ); + } + if (redirect) { // return ; - setAlert(`Password Reset success`, 'success'); + setAlert(`Password Reset success`, "success"); return ( -

- Password Reset Success{' '} - +

+ Password Reset Success{" "} + Login

@@ -56,31 +78,31 @@ const PasswordReset = ({ match, setAlert }) => { return ( -

Reset Password

-

- Input your new Password +

Reset Password

+

+ Input your new Password

-
onSubmit(e)}> -
+ onSubmit(e)}> +
onChange(e)} required />
-
+
onChange(e)} required />
- + ); diff --git a/routes/api/auth.js b/routes/api/auth.js index 08e6818..82df62b 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -1,26 +1,26 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const bcrypt = require('bcryptjs'); -const auth = require('../../middleware/auth'); -const jwt = require('jsonwebtoken'); -const config = require('config'); -const { sendEmail } = require('../../send_mail'); -const { check, validationResult } = require('express-validator/check'); +const bcrypt = require("bcryptjs"); +const auth = require("../../middleware/auth"); +const jwt = require("jsonwebtoken"); +const config = require("config"); +const { sendEmail } = require("../../send_mail"); +const { check, validationResult } = require("express-validator/check"); -const HOST_ADDR = process.env.HOST_ADDR || config.get('local_url'); +const HOST_ADDR = process.env.HOST_ADDR || config.get("local_url"); -const User = require('../../models/User'); +const User = require("../../models/User"); // @route GET api/auth // @desc Test route // @access Public -router.get('/', auth, async (req, res) => { +router.get("/", auth, async (req, res) => { try { - const user = await User.findById(req.user.id).select('-password'); + const user = await User.findById(req.user.id).select("-password"); res.json(user); } catch (err) { console.error(err.message); - res.status(500).send('Server Error'); + res.status(500).send("Server Error"); } }); @@ -28,10 +28,10 @@ router.get('/', auth, async (req, res) => { // @desc Authenticate user & get token // @access Public router.post( - '/', + "/", [ - check('email', 'Please include a valid email').isEmail(), - check('password', 'Password is required').exists() + check("email", "Please include a valid email").isEmail(), + check("password", "Password is required").exists() ], async (req, res) => { const errors = validationResult(req); @@ -47,7 +47,7 @@ router.post( if (!user) { return res .status(400) - .json({ errors: [{ msg: 'Invalid Credentials' }] }); + .json({ errors: [{ msg: "Invalid Credentials" }] }); } const isMatch = await bcrypt.compare(password, user.password); @@ -55,13 +55,13 @@ router.post( if (!isMatch) { return res .status(400) - .json({ errors: [{ msg: 'Invalid Credentials' }] }); + .json({ errors: [{ msg: "Invalid Credentials" }] }); } if (!user.confirmed) return res .status(400) - .json({ errors: [{ msg: 'Please confirm your email to login' }] }); + .json({ errors: [{ msg: "Please confirm your email to login" }] }); const payload = { user: { @@ -71,7 +71,7 @@ router.post( jwt.sign( payload, - config.get('jwtSecret'), + config.get("jwtSecret"), { expiresIn: 360000 }, (err, token) => { if (err) throw err; @@ -80,7 +80,7 @@ router.post( ); } catch (err) { console.error(err.message); - res.status(500).send('Server error'); + res.status(500).send("Server error"); } } ); @@ -88,41 +88,37 @@ router.post( // password forgot and reset routes************** // password forgot and reset routes************* router.put( - '/forgot-password', - [check('email', 'Please include a valid email').isEmail()], + "/forgot-password", + [check("email", "Please include a valid email").isEmail()], (req, res) => { - console.log('forget password'); const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } - console.log('forgot password finding user with that email'); const { email } = req.body; - console.log('signin req.body', email); // find the user in our database based on email User.findOne({ email }, (err, user) => { // if err or no user if (err || !user) - return res.status('401').json({ - error: 'User with that email does not exist!' + return res.status("401").json({ + error: "User with that email does not exist!" }); // generate a token with user id and secret - const token = jwt.sign({ _id: user._id }, config.get('jwtSecret')); + const token = jwt.sign({ _id: user._id }, config.get("jwtSecret")); // res.json({ user, token }); // email data const resetPwUrl = `${HOST_ADDR}/reset-password/${token}`; const emailData = { - from: process.env.MAIL_USER || config.get('email'), + from: process.env.MAIL_USER || config.get("email"), to: email, - subject: 'Password Reset Instructions', + subject: "Password Reset Instructions", text: `Please use the following link to reset your password: ${resetPwUrl}`, html: `

Please use the following link to reset your password:

${resetPwUrl}

` }; - console.log(emailData.text); return user.updateOne({ resetPasswordLink: token }, (err, success) => { if (err) { @@ -140,11 +136,11 @@ router.put( // *** router.put( - '/reset-password', + "/reset-password", [ check( - 'newPassword', - 'Please enter a password with 6 or more characters' + "newPassword", + "Please enter a password with 6 or more characters" ).isLength({ min: 6 }) ], async (req, res) => { @@ -156,19 +152,19 @@ router.put( try { const { resetPasswordLink, newPassword } = req.body; - console.log(resetPasswordLink, newPassword); + let user = await User.findOne({ resetPasswordLink }); // if Not the user if (!user) - return res.status('401').json({ - error: 'Invalid Link!' + return res.status("401").json({ + error: "Invalid Link!" }); // there is a user const salt = await bcrypt.genSalt(10); const newPasswordCrypt = await bcrypt.hash(newPassword, salt); user = await User.findOneAndUpdate( { resetPasswordLink }, - { $set: { resetPasswordLink: '', password: newPasswordCrypt } } + { $set: { resetPasswordLink: "", password: newPasswordCrypt } } ); res.json({ From df59df8b8264d7e959d9ed6f0394367843b78650 Mon Sep 17 00:00:00 2001 From: musafir928 Date: Thu, 28 Nov 2019 21:27:28 +0100 Subject: [PATCH 21/76] adil : redirect authenticated user from password forms to dashborad --- .../password-forms/PasswordEmailFrom.js | 54 +++++++++++-------- .../password-forms/PasswordReset.js | 15 ++++-- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/client/src/components/password-forms/PasswordEmailFrom.js b/client/src/components/password-forms/PasswordEmailFrom.js index d2134a0..d06b095 100644 --- a/client/src/components/password-forms/PasswordEmailFrom.js +++ b/client/src/components/password-forms/PasswordEmailFrom.js @@ -1,10 +1,11 @@ -import React, { Fragment, useState } from 'react'; -import axios from 'axios'; -import { setAlert } from '../../actions/alert'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; +import React, { Fragment, useState } from "react"; +import axios from "axios"; +import { Redirect } from "react-router-dom"; +import { setAlert } from "../../actions/alert"; +import { connect } from "react-redux"; +import PropTypes from "prop-types"; -const PasswordEmailForm = ({ setAlert }) => { +const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { const [formData, setFormData] = useState({}); const [sent, setSent] = useState(false); @@ -16,7 +17,7 @@ const PasswordEmailForm = ({ setAlert }) => { // send email const reqConfig = { headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json" } }; @@ -30,44 +31,53 @@ const PasswordEmailForm = ({ setAlert }) => { }; if (sent) { // return ; - setAlert('Email sent', 'success'); + setAlert("Email sent", "success"); return ( -

Rest password emial is sent.

-

- Check your emial: {formData.email} +

Rest password emial is sent.

+

+ Check your emial: {formData.email}

); } + if (isAuthenticated) { + return ; + } + return ( -

Forgot Password

-

- Input Your Email And Submit To Send +

Forgot Password

+

+ Input Your Email And Submit To Send Recovery Email

-
onSubmit(e)}> -
+ onSubmit(e)}> +
onChange(e)} required />
- + ); }; PasswordEmailForm.propTypes = { - setAlert: PropTypes.func.isRequired + setAlert: PropTypes.func.isRequired, + isAuthenticated: PropTypes.bool }; -export default connect(null, { setAlert })(PasswordEmailForm); +const mapStateToProps = state => ({ + isAuthenticated: state.auth.isAuthenticated +}); + +export default connect(mapStateToProps, { setAlert })(PasswordEmailForm); diff --git a/client/src/components/password-forms/PasswordReset.js b/client/src/components/password-forms/PasswordReset.js index 30318a8..47c2b39 100644 --- a/client/src/components/password-forms/PasswordReset.js +++ b/client/src/components/password-forms/PasswordReset.js @@ -6,7 +6,7 @@ import axios from "axios"; import { setAlert } from "../../actions/alert"; import PropTypes from "prop-types"; -const PasswordReset = ({ match, setAlert }) => { +const PasswordReset = ({ match, setAlert, isAuthenticated }) => { const [formData, setFormData] = useState({ password: "", password2: "" @@ -76,6 +76,10 @@ const PasswordReset = ({ match, setAlert }) => { ); } + if (isAuthenticated) { + return ; + } + return (

Reset Password

@@ -110,7 +114,12 @@ const PasswordReset = ({ match, setAlert }) => { PasswordReset.propTypes = { setAlert: PropTypes.func.isRequired, - match: PropTypes.object.isRequired + match: PropTypes.object.isRequired, + isAuthenticated: PropTypes.bool }; -export default connect(null, { setAlert })(PasswordReset); +const mapStateToProps = state => ({ + isAuthenticated: state.auth.isAuthenticated +}); + +export default connect(mapStateToProps, { setAlert })(PasswordReset); From fb3eeadb17a041283ac7669deedcdff68a860102 Mon Sep 17 00:00:00 2001 From: musafir928 Date: Thu, 28 Nov 2019 21:29:01 +0100 Subject: [PATCH 22/76] solved namming issue --- ...swordEmailFrom.js => PasswordEmailForm.js} | 0 client/src/components/routing/Routes.js | 68 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) rename client/src/components/password-forms/{PasswordEmailFrom.js => PasswordEmailForm.js} (100%) diff --git a/client/src/components/password-forms/PasswordEmailFrom.js b/client/src/components/password-forms/PasswordEmailForm.js similarity index 100% rename from client/src/components/password-forms/PasswordEmailFrom.js rename to client/src/components/password-forms/PasswordEmailForm.js diff --git a/client/src/components/routing/Routes.js b/client/src/components/routing/Routes.js index 86216b7..e592a6a 100755 --- a/client/src/components/routing/Routes.js +++ b/client/src/components/routing/Routes.js @@ -1,44 +1,44 @@ -import React from 'react'; -import { Route, Switch } from 'react-router-dom'; -import Register from '../auth/Register'; -import Login from '../auth/Login'; -import Alert from '../layout/Alert'; -import Dashboard from '../dashboard/Dashboard'; -import CreateProfile from '../profile-forms/CreateProfile'; -import EditProfile from '../profile-forms/EditProfile'; -import AddExperience from '../profile-forms/AddExperience'; -import AddEducation from '../profile-forms/AddEducation'; -import Profiles from '../profiles/Profiles'; -import Profile from '../profile/Profile'; -import Posts from '../posts/Posts'; -import Post from '../post/Post'; -import NotFound from '../layout/NotFound'; -import Confirm from '../auth/Confirm'; -import PrivateRoute from '../routing/PrivateRoute'; -import PasswordEmailFrom from '../password-forms/PasswordEmailFrom'; -import PasswordReset from '../password-forms/PasswordReset'; +import React from "react"; +import { Route, Switch } from "react-router-dom"; +import Register from "../auth/Register"; +import Login from "../auth/Login"; +import Alert from "../layout/Alert"; +import Dashboard from "../dashboard/Dashboard"; +import CreateProfile from "../profile-forms/CreateProfile"; +import EditProfile from "../profile-forms/EditProfile"; +import AddExperience from "../profile-forms/AddExperience"; +import AddEducation from "../profile-forms/AddEducation"; +import Profiles from "../profiles/Profiles"; +import Profile from "../profile/Profile"; +import Posts from "../posts/Posts"; +import Post from "../post/Post"; +import NotFound from "../layout/NotFound"; +import Confirm from "../auth/Confirm"; +import PrivateRoute from "../routing/PrivateRoute"; +import PasswordEmailForm from "../password-forms/PasswordEmailForm"; +import PasswordReset from "../password-forms/PasswordReset"; const Routes = () => { return ( -
+
- - - - - - + + + + + + - + - - - - - - - + + + + + + +
From 72d78bfd711bba377428ecb9ba6ed5b99c2d9c11 Mon Sep 17 00:00:00 2001 From: musafir928 Date: Thu, 28 Nov 2019 22:17:41 +0100 Subject: [PATCH 23/76] adil: delete un neccessary consoles and comments --- routes/api/auth.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/routes/api/auth.js b/routes/api/auth.js index 82df62b..cc5336f 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -85,8 +85,9 @@ router.post( } ); -// password forgot and reset routes************** -// password forgot and reset routes************* +// @route PUT api/auth/forgot-password +// @desc Send Password Recovery Email +// @access Public router.put( "/forgot-password", [check("email", "Please include a valid email").isEmail()], @@ -98,19 +99,14 @@ router.put( const { email } = req.body; - // find the user in our database based on email User.findOne({ email }, (err, user) => { - // if err or no user if (err || !user) return res.status("401").json({ error: "User with that email does not exist!" }); - // generate a token with user id and secret const token = jwt.sign({ _id: user._id }, config.get("jwtSecret")); - // res.json({ user, token }); - // email data const resetPwUrl = `${HOST_ADDR}/reset-password/${token}`; const emailData = { from: process.env.MAIL_USER || config.get("email"), @@ -134,7 +130,9 @@ router.put( } ); -// *** +// @route PUT api/auth/reset-password +// @desc Reset Password When The User Click The received Link +// @access Public router.put( "/reset-password", [ @@ -148,18 +146,17 @@ router.put( if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } - // in the frontend we have to use ReactRouterDom to get the resetPasswordLink from the url and push it to the body try { const { resetPasswordLink, newPassword } = req.body; let user = await User.findOne({ resetPasswordLink }); - // if Not the user + if (!user) return res.status("401").json({ error: "Invalid Link!" }); - // there is a user + const salt = await bcrypt.genSalt(10); const newPasswordCrypt = await bcrypt.hash(newPassword, salt); user = await User.findOneAndUpdate( From fbf458d9426f848d540a41f67160d570ddbe876c Mon Sep 17 00:00:00 2001 From: musafir928 Date: Fri, 29 Nov 2019 00:02:33 +0100 Subject: [PATCH 24/76] add spinner to reset and send reset eamil components --- .../password-forms/PasswordEmailForm.js | 26 +++++++++++++----- .../password-forms/PasswordReset.js | 27 +++++++++++++------ 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/client/src/components/password-forms/PasswordEmailForm.js b/client/src/components/password-forms/PasswordEmailForm.js index d06b095..74ed165 100644 --- a/client/src/components/password-forms/PasswordEmailForm.js +++ b/client/src/components/password-forms/PasswordEmailForm.js @@ -4,10 +4,12 @@ import { Redirect } from "react-router-dom"; import { setAlert } from "../../actions/alert"; import { connect } from "react-redux"; import PropTypes from "prop-types"; +import Spinner from "../layout/Spinner"; const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { const [formData, setFormData] = useState({}); const [sent, setSent] = useState(false); + const [showSpinner, setShowSpinner] = useState(false); const onChange = e => setFormData({ ...formData, email: e.target.value }); const onSubmit = async e => { @@ -22,14 +24,30 @@ const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { }; const reqBody = JSON.stringify({ email: formData.email }); - + setShowSpinner(true); await axios.put(`/api/auth/forgot-password/`, reqBody, reqConfig); + setShowSpinner(false); setSent(true); } catch (err) { console.error(err); } }; - if (sent) { + if (isAuthenticated) { + return ; + } + + if (!sent && showSpinner) { + return ( + +

Sending mail, please wait...

+

+ +

+
+ ); + } + + if (sent && !showSpinner) { // return ; setAlert("Email sent", "success"); @@ -43,10 +61,6 @@ const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { ); } - if (isAuthenticated) { - return ; - } - return (

Forgot Password

diff --git a/client/src/components/password-forms/PasswordReset.js b/client/src/components/password-forms/PasswordReset.js index 47c2b39..61bab91 100644 --- a/client/src/components/password-forms/PasswordReset.js +++ b/client/src/components/password-forms/PasswordReset.js @@ -5,6 +5,7 @@ import { connect } from "react-redux"; import axios from "axios"; import { setAlert } from "../../actions/alert"; import PropTypes from "prop-types"; +import Spinner from "../layout/Spinner"; const PasswordReset = ({ match, setAlert, isAuthenticated }) => { const [formData, setFormData] = useState({ @@ -14,6 +15,7 @@ const PasswordReset = ({ match, setAlert, isAuthenticated }) => { const { password, password2 } = formData; const [redirect, setRedirect] = useState(false); + const [showSpinner, setShowSpinner] = useState(false); const [resetError, setResetError] = useState(false); const onChange = e => @@ -37,8 +39,9 @@ const PasswordReset = ({ match, setAlert, isAuthenticated }) => { resetPasswordLink, newPassword: formData.password }); - + setShowSpinner(true); await axios.put(`/api/auth/reset-password`, reqBody, reqConfig); + setShowSpinner(false); setRedirect(true); } catch (err) { console.error(err.message); @@ -48,6 +51,10 @@ const PasswordReset = ({ match, setAlert, isAuthenticated }) => { } }; + if (isAuthenticated) { + return ; + } + if (resetError) { return ( <> @@ -62,9 +69,17 @@ const PasswordReset = ({ match, setAlert, isAuthenticated }) => { ); } - - if (redirect) { - // return ; + if (!redirect && showSpinner) { + return ( + +

Resetting Password, please wait...

+

+ +

+
+ ); + } + if (redirect && !showSpinner) { setAlert(`Password Reset success`, "success"); return (

@@ -76,10 +91,6 @@ const PasswordReset = ({ match, setAlert, isAuthenticated }) => { ); } - if (isAuthenticated) { - return ; - } - return (

Reset Password

From 40fc66f11259b2d0c2caed7f90558ed1d653e763 Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Fri, 29 Nov 2019 01:33:18 +0100 Subject: [PATCH 25/76] Change mail transport, bug fixes,refactor --- client/src/components/auth/Login.js | 48 +++--- .../password-forms/PasswordEmailForm.js | 38 ++--- .../password-forms/PasswordReset.js | 63 +++----- models/User.js | 16 +- package.json | 2 +- routes/api/auth.js | 146 ++++++++---------- routes/api/users.js | 100 +++++------- send_mail.js | 20 --- utils/sendMail.js | 6 + 9 files changed, 180 insertions(+), 259 deletions(-) delete mode 100644 send_mail.js create mode 100644 utils/sendMail.js diff --git a/client/src/components/auth/Login.js b/client/src/components/auth/Login.js index ddfb404..40337de 100755 --- a/client/src/components/auth/Login.js +++ b/client/src/components/auth/Login.js @@ -7,13 +7,12 @@ import { login } from '../../actions/auth'; const Login = ({ login, isAuthenticated }) => { const [formData, setFormData] = useState({ email: '', - password: '' + password: '', }); const { email, password } = formData; - const onChange = e => - setFormData({ ...formData, [e.target.name]: e.target.value }); + const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value }); const onSubmit = async e => { e.preventDefault(); @@ -21,44 +20,43 @@ const Login = ({ login, isAuthenticated }) => { }; if (isAuthenticated) { - return ; + return ; } return ( -

Sign In

-

- Sign Into Your Account +

Sign In

+

+ Sign Into Your Account

-
onSubmit(e)}> -
+ onSubmit(e)}> +
onChange(e)} required />
-
+
onChange(e)} - minLength="6" + minLength='6' />
- + -

- Forgot password?{' '} - Send password recovery email +

+ Forgot password?

-

- Don't have an account? Sign Up +

+ Don't have an account? Sign Up

); @@ -66,11 +64,11 @@ const Login = ({ login, isAuthenticated }) => { Login.propTypes = { login: PropTypes.func.isRequired, - isAuthenticated: PropTypes.bool + isAuthenticated: PropTypes.bool, }; const mapStateToProps = state => ({ - isAuthenticated: state.auth.isAuthenticated + isAuthenticated: state.auth.isAuthenticated, }); export default connect(mapStateToProps, { login })(Login); diff --git a/client/src/components/password-forms/PasswordEmailForm.js b/client/src/components/password-forms/PasswordEmailForm.js index d06b095..5e1f896 100644 --- a/client/src/components/password-forms/PasswordEmailForm.js +++ b/client/src/components/password-forms/PasswordEmailForm.js @@ -1,9 +1,9 @@ -import React, { Fragment, useState } from "react"; -import axios from "axios"; -import { Redirect } from "react-router-dom"; -import { setAlert } from "../../actions/alert"; -import { connect } from "react-redux"; -import PropTypes from "prop-types"; +import React, { Fragment, useState } from 'react'; +import axios from 'axios'; +import { Redirect } from 'react-router-dom'; +import { setAlert } from '../../actions/alert'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { const [formData, setFormData] = useState({}); @@ -17,27 +17,29 @@ const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { // send email const reqConfig = { headers: { - "Content-Type": "application/json" - } + 'Content-Type': 'application/json', + }, }; const reqBody = JSON.stringify({ email: formData.email }); - await axios.put(`/api/auth/forgot-password/`, reqBody, reqConfig); + const res = await axios.put(`/api/auth/forgot-password`, reqBody, reqConfig); + setAlert(res.data.msg, 'success'); setSent(true); } catch (err) { console.error(err); + const { errors } = err.response.data; + if (errors) { + errors.forEach(error => setAlert(error.msg, 'danger')); + } } }; if (sent) { - // return ; - setAlert("Email sent", "success"); - return ( -

Rest password emial is sent.

+

Reset password email is sent.

- Check your emial: {formData.email} + Check your email: {formData.email}

); @@ -51,8 +53,8 @@ const PasswordEmailForm = ({ setAlert, isAuthenticated }) => {

Forgot Password

- Input Your Email And Submit To Send - Recovery Email + Enter your user account's verified email address and we will + send you a password reset link.

onSubmit(e)}>
@@ -73,11 +75,11 @@ const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { PasswordEmailForm.propTypes = { setAlert: PropTypes.func.isRequired, - isAuthenticated: PropTypes.bool + isAuthenticated: PropTypes.bool, }; const mapStateToProps = state => ({ - isAuthenticated: state.auth.isAuthenticated + isAuthenticated: state.auth.isAuthenticated, }); export default connect(mapStateToProps, { setAlert })(PasswordEmailForm); diff --git a/client/src/components/password-forms/PasswordReset.js b/client/src/components/password-forms/PasswordReset.js index 47c2b39..34e1e52 100644 --- a/client/src/components/password-forms/PasswordReset.js +++ b/client/src/components/password-forms/PasswordReset.js @@ -1,48 +1,47 @@ -import React, { Fragment, useState } from "react"; -import { Redirect } from "react-router-dom"; -import { Link } from "react-router-dom"; -import { connect } from "react-redux"; -import axios from "axios"; -import { setAlert } from "../../actions/alert"; -import PropTypes from "prop-types"; +import React, { Fragment, useState } from 'react'; +import { Redirect } from 'react-router-dom'; +import { Link } from 'react-router-dom'; +import { connect } from 'react-redux'; +import axios from 'axios'; +import { setAlert } from '../../actions/alert'; +import PropTypes from 'prop-types'; const PasswordReset = ({ match, setAlert, isAuthenticated }) => { const [formData, setFormData] = useState({ - password: "", - password2: "" + password: '', + password2: '', }); const { password, password2 } = formData; const [redirect, setRedirect] = useState(false); const [resetError, setResetError] = useState(false); - const onChange = e => - setFormData({ ...formData, [e.target.name]: e.target.value }); + const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value }); const resetPasswordLink = match.params.token; const onSubmit = async e => { e.preventDefault(); if (password !== password2) { - setAlert("Passwords do not match", "danger"); - e.target.password.value = ""; - e.target.password2.value = ""; + setAlert('Passwords do not match', 'danger'); + e.target.password.value = ''; + e.target.password2.value = ''; } else { try { const reqConfig = { headers: { - "Content-Type": "application/json" - } + 'Content-Type': 'application/json', + }, }; const reqBody = JSON.stringify({ resetPasswordLink, - newPassword: formData.password + newPassword: formData.password, }); await axios.put(`/api/auth/reset-password`, reqBody, reqConfig); setRedirect(true); } catch (err) { console.error(err.message); - setAlert("Password Reset Link Invalid", "danger"); + setAlert('Password Reset Link Invalid', 'danger'); setResetError(true); } } @@ -50,30 +49,18 @@ const PasswordReset = ({ match, setAlert, isAuthenticated }) => { if (resetError) { return ( - <> - {" "} + +

Something went wrong with your reset password link.

- Resend Recovery Email?{" "} - Resend password recovery email + Resend Recovery Email? Resend password recovery email

-

- Login? Login -

- +
); } if (redirect) { - // return ; - setAlert(`Password Reset success`, "success"); - return ( -

- Password Reset Success{" "} - - Login - -

- ); + setAlert(`Password Reset success`, 'success'); + return ; } if (isAuthenticated) { @@ -115,11 +102,11 @@ const PasswordReset = ({ match, setAlert, isAuthenticated }) => { PasswordReset.propTypes = { setAlert: PropTypes.func.isRequired, match: PropTypes.object.isRequired, - isAuthenticated: PropTypes.bool + isAuthenticated: PropTypes.bool, }; const mapStateToProps = state => ({ - isAuthenticated: state.auth.isAuthenticated + isAuthenticated: state.auth.isAuthenticated, }); export default connect(mapStateToProps, { setAlert })(PasswordReset); diff --git a/models/User.js b/models/User.js index b4c7227..9827bb2 100755 --- a/models/User.js +++ b/models/User.js @@ -3,29 +3,29 @@ const mongoose = require('mongoose'); const UserSchema = new mongoose.Schema({ name: { type: String, - required: true + required: true, }, email: { type: String, required: true, - unique: true + unique: true, }, confirmed: { type: Boolean, - default: false + default: false, }, password: { type: String, - required: true + required: true, }, - resetPasswordLink: { data: String, default: '' }, + resetPasswordLink: { type: String, default: '' }, avatar: { - type: String + type: String, }, date: { type: Date, - default: Date.now - } + default: Date.now, + }, }); module.exports = User = mongoose.model('user', UserSchema); diff --git a/package.json b/package.json index ef0430a..d70b186 100755 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "author": "Brad Traversy", "license": "MIT", "dependencies": { + "@sendgrid/mail": "^6.4.0", "bcryptjs": "^2.4.3", "config": "^3.1.0", "express": "^4.16.4", @@ -20,7 +21,6 @@ "gravatar": "^1.8.0", "jsonwebtoken": "^8.5.1", "mongoose": "^5.7.5", - "nodemailer": "^6.3.1", "request": "^2.88.0" }, "devDependencies": { diff --git a/routes/api/auth.js b/routes/api/auth.js index cc5336f..21b03ec 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -1,26 +1,26 @@ -const express = require("express"); +const express = require('express'); const router = express.Router(); -const bcrypt = require("bcryptjs"); -const auth = require("../../middleware/auth"); -const jwt = require("jsonwebtoken"); -const config = require("config"); -const { sendEmail } = require("../../send_mail"); -const { check, validationResult } = require("express-validator/check"); +const bcrypt = require('bcryptjs'); +const auth = require('../../middleware/auth'); +const jwt = require('jsonwebtoken'); +const config = require('config'); +const sendMail = require('../../utils/sendMail'); +const { check, validationResult } = require('express-validator/check'); -const HOST_ADDR = process.env.HOST_ADDR || config.get("local_url"); +const HOST_ADDR = process.env.HOST_ADDR || config.get('local_url'); -const User = require("../../models/User"); +const User = require('../../models/User'); // @route GET api/auth // @desc Test route // @access Public -router.get("/", auth, async (req, res) => { +router.get('/', auth, async (req, res) => { try { - const user = await User.findById(req.user.id).select("-password"); + const user = await User.findById(req.user.id).select('-password'); res.json(user); } catch (err) { console.error(err.message); - res.status(500).send("Server Error"); + res.status(500).send('Server Error'); } }); @@ -28,10 +28,10 @@ router.get("/", auth, async (req, res) => { // @desc Authenticate user & get token // @access Public router.post( - "/", + '/', [ - check("email", "Please include a valid email").isEmail(), - check("password", "Password is required").exists() + check('email', 'Please include a valid email').isEmail(), + check('password', 'Password is required').exists(), ], async (req, res) => { const errors = validationResult(req); @@ -45,134 +45,110 @@ router.post( let user = await User.findOne({ email }); if (!user) { - return res - .status(400) - .json({ errors: [{ msg: "Invalid Credentials" }] }); + return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] }); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { - return res - .status(400) - .json({ errors: [{ msg: "Invalid Credentials" }] }); + return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] }); } if (!user.confirmed) - return res - .status(400) - .json({ errors: [{ msg: "Please confirm your email to login" }] }); + return res.status(400).json({ errors: [{ msg: 'Please confirm your email to login' }] }); const payload = { user: { - id: user.id - } + id: user.id, + }, }; - jwt.sign( - payload, - config.get("jwtSecret"), - { expiresIn: 360000 }, - (err, token) => { - if (err) throw err; - res.json({ token }); - } - ); + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { + if (err) throw err; + res.json({ token }); + }); } catch (err) { console.error(err.message); - res.status(500).send("Server error"); + res.status(500).send('Server error'); } - } + }, ); // @route PUT api/auth/forgot-password // @desc Send Password Recovery Email // @access Public router.put( - "/forgot-password", - [check("email", "Please include a valid email").isEmail()], - (req, res) => { + '/forgot-password', + [check('email', 'Please include a valid email').isEmail()], + async (req, res) => { const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } + if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() }); const { email } = req.body; + try { + const user = await User.findOne({ email }); - User.findOne({ email }, (err, user) => { - if (err || !user) - return res.status("401").json({ - error: "User with that email does not exist!" - }); + if (!user) return res.status(401).json({ errors: [{ msg: `No user found with ${email}` }] }); - const token = jwt.sign({ _id: user._id }, config.get("jwtSecret")); + const token = jwt.sign({ _id: user._id }, config.get('jwtSecret')); const resetPwUrl = `${HOST_ADDR}/reset-password/${token}`; - const emailData = { - from: process.env.MAIL_USER || config.get("email"), + + const msg = { + from: process.env.MAIL_USER || config.get('email'), to: email, - subject: "Password Reset Instructions", + subject: 'Account Password Reset', text: `Please use the following link to reset your password: ${resetPwUrl}`, - html: `

Please use the following link to reset your password:

${resetPwUrl}

` + html: `

Hello ${user.name}

You requested a password reset link for your Hack Your Social account and here it is. Resetting your password is as easy as clicking the link and following the instructions!

${resetPwUrl}

`, }; - return user.updateOne({ resetPasswordLink: token }, (err, success) => { - if (err) { - return res.json({ message: err }); - } else { - sendEmail(emailData); - return res.status(200).json({ - message: `Email has been sent to ${email}. Follow the instructions to reset your password.` - }); - } + await user.updateOne({ resetPasswordLink: token }); + await sendMail(msg); + res.json({ + msg: `Email has been sent to ${email}. Follow the instructions to reset your password.`, }); - }); - } + } catch (err) { + console.error(err.message); + res.status(500).send('Server error'); + } + }, ); // @route PUT api/auth/reset-password // @desc Reset Password When The User Click The received Link // @access Public router.put( - "/reset-password", - [ - check( - "newPassword", - "Please enter a password with 6 or more characters" - ).isLength({ min: 6 }) - ], + '/reset-password', + [check('newPassword', 'Please enter a password with 6 or more characters').isLength({ min: 6 })], async (req, res) => { const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } + if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() }); try { const { resetPasswordLink, newPassword } = req.body; - let user = await User.findOne({ resetPasswordLink }); + const user = await User.findOne({ resetPasswordLink }); if (!user) - return res.status("401").json({ - error: "Invalid Link!" + return res.status(401).json({ + error: 'Invalid Link!', }); const salt = await bcrypt.genSalt(10); const newPasswordCrypt = await bcrypt.hash(newPassword, salt); - user = await User.findOneAndUpdate( + await User.findOneAndUpdate( { resetPasswordLink }, - { $set: { resetPasswordLink: "", password: newPasswordCrypt } } + { $set: { resetPasswordLink: '', password: newPasswordCrypt } }, ); res.json({ - message: `Great! Now you can login with your new password.` - }); - } catch (error) { - res.status(400).json({ - error: error.message + msg: `Great! Now you can login with your new password.`, }); + } catch (err) { + console.error(err.message); + res.status(500).send('Server error'); } - } + }, ); module.exports = router; diff --git a/routes/api/users.js b/routes/api/users.js index ad68341..e87faa3 100755 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -4,9 +4,8 @@ const gravatar = require('gravatar'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const config = require('config'); -const { sendEmail } = require('../../send_mail'); const { check, validationResult } = require('express-validator/check'); -const nodemailer = require('nodemailer'); +const sendMail = require('../../utils/sendMail'); const HOST_ADDR = process.env.HOST_ADDR || config.get('local_url'); console.log(HOST_ADDR); @@ -23,10 +22,7 @@ router.post( .not() .isEmpty(), check('email', 'Please include a valid email').isEmail(), - check( - 'password', - 'Please enter a password with 6 or more characters' - ).isLength({ min: 6 }) + check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 }), ], async (req, res) => { const errors = validationResult(req); @@ -40,22 +36,20 @@ router.post( let user = await User.findOne({ email }); if (user) { - return res - .status(400) - .json({ errors: [{ msg: 'User already exists' }] }); + return res.status(400).json({ errors: [{ msg: 'User already exists' }] }); } const avatar = gravatar.url(email, { s: '200', r: 'pg', - d: 'mm' + d: 'mm', }); user = new User({ name, email, avatar, - password + password, }); const salt = await bcrypt.genSalt(10); @@ -66,35 +60,32 @@ router.post( const payload = { user: { - id: user.id - } + id: user.id, + }, }; - - const emailToken = jwt.sign(payload, config.get('emailSecret'), { - expiresIn: '1d' + expiresIn: '1d', }); const confirmURL = `${HOST_ADDR}/confirm/${emailToken}`; - const emailData = { + const msg = { to: user.email, from: process.env.MAIL_USER || config.get('email'), - - subject: 'Confirm Email Instructions', - text: `Hurrah! You've created a Developer Hub account with ${user.email}. + subject: 'Confirm your email address', + text: `Hurrah! You've created a Hack Your Social account with ${user.email}. Please take a moment to confirm that we can use this address to send you mails.: ${confirmURL}`, - html: `

Hurrah! You've created a Developer Hub account with ${user.email}.

Please take a moment to confirm that we can use this address to send you mails.

-

${confirmURL}

` + html: `

Hello ${user.name}

Hurrah! You've created a Developer Hub account with ${user.email}.

There’s just one step left to use your account.Please take a moment to confirm that we can use this address to send you mails.

+

Click here to confirm your email address.

`, }; - await sendEmail(emailData); + await sendMail(msg); res.json({ msg: 'Confirmation mail sent' }); } catch (err) { console.error(err.message); res.status(500).send('Server error'); } - } + }, ); // @route PUT /api/users/confirm/:emailToken @@ -104,38 +95,27 @@ router.put('/confirm/:emailToken', async (req, res) => { const { emailToken } = req.params; try { const { - user: { id } + user: { id }, } = jwt.verify(emailToken, config.get('emailSecret')); const user = await User.findOne({ _id: id }); let msg = ''; if (user.confirmed) { msg = 'Email is already confirmed'; } - await User.findOneAndUpdate( - { _id: id }, - { $set: { confirmed: true } }, - { new: true } - ); + await User.findOneAndUpdate({ _id: id }, { $set: { confirmed: true } }, { new: true }); const payload = { user: { - id - } + id, + }, }; - jwt.sign( - payload, - config.get('jwtSecret'), - { expiresIn: 360000 }, - (err, token) => { - if (err) throw err; - res.json({ token, msg }); - } - ); + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { + if (err) throw err; + res.json({ token, msg }); + }); } catch (error) { if (error.name === 'TokenExpiredError') { - return res - .status(401) - .send({ errors: [{ msg: 'Email validation link expired' }] }); + return res.status(401).send({ errors: [{ msg: 'Email validation link expired' }] }); } else { return res.status(401).send({ errors: [{ msg: 'Invalid Token' }] }); } @@ -158,46 +138,38 @@ router.post( const user = await User.findOne({ email }); if (!user) { return res.status(400).json({ - errors: [{ msg: `No user found registered with ${email}` }] + errors: [{ msg: `No user found registered with ${email}` }], }); } const payload = { user: { - id: user.id - } + id: user.id, + }, }; - const transporter = nodemailer.createTransport({ - host: 'smtp-pulse.com', - port: 465, - secure: true, - auth: { - user: process.env.MAIL_USER, - pass: process.env.MAIL_PASS - } - }); - const emailToken = jwt.sign(payload, config.get('emailSecret'), { - expiresIn: '1d' + expiresIn: '1d', }); const confirmURL = `${HOST_ADDR}/confirm/${emailToken}`; const msg = { to: user.email, - from: process.env.MAIL_USER, - subject: 'Confirm Email', - html: `Hurrah! You've created a Developer Hub account with ${user.email}. Please take a moment to confirm that we can use this address to send you mails.
- ${confirmURL}` + from: process.env.MAIL_USER || config.get('email'), + subject: 'Confirm your email address', + text: `Hurrah! You've created a Hack Your Social account with ${user.email}. + Please take a moment to confirm that we can use this address to send you mails.: ${confirmURL}`, + html: `

Hello ${user.name}

Hurrah! You've created a Developer Hub account with ${user.email}.

There’s just one step left to use your account.Please take a moment to confirm that we can use this address to send you mails.

+

Click here to confirm your email address.

`, }; - await transporter.sendMail(msg); + await sendMail(msg); res.json({ msg: 'Confirmation mail sent' }); } catch (err) { console.error(err.message); res.status(500).send('Server error'); } - } + }, ); module.exports = router; diff --git a/send_mail.js b/send_mail.js deleted file mode 100644 index bf6d264..0000000 --- a/send_mail.js +++ /dev/null @@ -1,20 +0,0 @@ -const nodeMailer = require('nodemailer'); -const config = require('config'); - -exports.sendEmail = emailData => { - const transporter = nodeMailer.createTransport({ - host: 'smtp-pulse.com', - port: 465, - secure: true, - auth: { - user: process.env.MAIL_USER || config.get('email'), - pass: process.env.MAIL_PASS || config.get('emailPw') - } - }); - return transporter - .sendMail(emailData) - .then(info => console.log(`Message sent: ${info.response}`)) - .catch(err => console.log(`Problem sending email: ${err}`)); -}; - -// ---------------- diff --git a/utils/sendMail.js b/utils/sendMail.js new file mode 100644 index 0000000..7248804 --- /dev/null +++ b/utils/sendMail.js @@ -0,0 +1,6 @@ +const sgMail = require('@sendgrid/mail'); +sgMail.setApiKey(process.env.SENDGRID_API_KEY); + +const sendMail = msg => sgMail.send(msg); + +module.exports = sendMail; From 3daf0c00c8f13f5d3226d20f774f8cb45cdac606 Mon Sep 17 00:00:00 2001 From: 20twa07 <48910851+20twa07@users.noreply.github.com> Date: Fri, 29 Nov 2019 16:40:04 +0100 Subject: [PATCH 26/76] add refactoring & save user in db --- .gitignore | 2 +- client/package-lock.json | 1430 +++++++++++++++++--- client/package.json | 1 + client/src/actions/auth.js | 82 +- client/src/actions/types.js | 1 + client/src/components/auth/FacebookAuth.js | 37 + client/src/components/auth/Login.js | 52 +- client/src/components/routing/Routes.js | 2 + client/src/reducers/auth.js | 19 +- client/src/social-config/firebaseConfig.js | 16 +- config/default.json | 8 +- routes/api/users.js | 59 +- 12 files changed, 1383 insertions(+), 326 deletions(-) create mode 100644 client/src/components/auth/FacebookAuth.js diff --git a/.gitignore b/.gitignore index c03feab..6122ccb 100755 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ config/production.json client/debug.log config/default.json package-lock.json - +client/src/social-config/ diff --git a/client/package-lock.json b/client/package-lock.json index 6572e44..5c0c6da 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -932,6 +932,323 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-9.0.1.tgz", "integrity": "sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA==" }, + "@firebase/analytics": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.2.7.tgz", + "integrity": "sha512-ZpGvppEOk4qID5PZiPyGi5v+LFnXkbknMBm2ARisph1iesmxwgSHhn3QlqghjdVbdxONV2y4YSVUkJNCxCtX9A==", + "requires": { + "@firebase/analytics-types": "0.2.3", + "@firebase/installations": "0.3.6", + "@firebase/util": "0.2.34", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/analytics-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.2.3.tgz", + "integrity": "sha512-8R8V5ZZsg6lDe2EiqgNq3XMQpAHbfStoXqRjdxu7FVCNLb/yxBThANdFXHbh4XxDc7vJEZr90PAjRB+bfGkSyw==" + }, + "@firebase/app": { + "version": "0.4.25", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.4.25.tgz", + "integrity": "sha512-Zf7RsWJhJXqWJ8tp1NQXFTYoEeURVkA+yI6On0SmPAxUo2CG1sXGhUt0TJBnYpKQLeDbhxVx552U85iMaVkvkw==", + "requires": { + "@firebase/app-types": "0.4.8", + "@firebase/logger": "0.1.31", + "@firebase/util": "0.2.34", + "dom-storage": "2.1.0", + "tslib": "1.10.0", + "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/app-types": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.4.8.tgz", + "integrity": "sha512-VTjWRooelMExK/rKArp6WqnWJJfi8Vs6VuDYDSeMcQ3NpSux2bW1dfJFuzYmiK1+37hEJP1F43DyUDv2lCJquw==" + }, + "@firebase/auth": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.13.1.tgz", + "integrity": "sha512-JN/850MuahGea7NZMVbNTl3ASGFqSt8Hx9DuP4s0XZ1U0FcA439nSKGxjD0phn/HpwzYyU+sMxh1gmffuyWKMw==", + "requires": { + "@firebase/auth-types": "0.9.1" + } + }, + "@firebase/auth-types": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.9.1.tgz", + "integrity": "sha512-3P+qkJHkPcbyF9mubHGC4Bz2uZ6ha647rhWi3eMihXdD6E+vTEGpAi/KOp6KYvZJRbGbuCrobP61Djay1PuFlA==" + }, + "@firebase/database": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.5.13.tgz", + "integrity": "sha512-B1+6Ns3jbpryDUi6ZohByXk8EPcuD5rUla1UchzdCjsU1waq06QyUrakow5Hr5RugqmziMAOfzpXid+wV4+bvw==", + "requires": { + "@firebase/database-types": "0.4.8", + "@firebase/logger": "0.1.31", + "@firebase/util": "0.2.34", + "faye-websocket": "0.11.3", + "tslib": "1.10.0" + }, + "dependencies": { + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/database-types": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.4.8.tgz", + "integrity": "sha512-bYGzvcwjGOSWuL43nldY3kD3ldPDLTiiOF0TItsJx2JdL58PzGiGaR71dvPJhueNBn+bwJ5KPJxpqTSVqM/j8w==", + "requires": { + "@firebase/app-types": "0.4.8" + } + }, + "@firebase/firestore": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.8.0.tgz", + "integrity": "sha512-MOuM2Bpgq9b+GnMJL9ui8aIwTTCSZ0u+qG2R7O8NoQogvB/XDCDjPER7PJfLLZ03yxiSuWKiJ+pu37AqgDIxhg==", + "requires": { + "@firebase/firestore-types": "1.8.0", + "@firebase/logger": "0.1.31", + "@firebase/util": "0.2.34", + "@firebase/webchannel-wrapper": "0.2.32", + "@grpc/proto-loader": "^0.5.0", + "grpc": "1.24.2", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/firestore-types": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.8.0.tgz", + "integrity": "sha512-Zy7IDqtjZPbKB6tP4dKuNks3tCvWOa3VhqEdl4WLyMKhvqh+/d0lzr+0HEEeEWR4IogsnJmLZUjDx0eqvqfCpg==" + }, + "@firebase/functions": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.4.26.tgz", + "integrity": "sha512-ZY3sWKdi7UuKhZ/P05AtzOwPNZCU4oSkpWc1UR+GcYvGtn54e8PVwFvhYBZpo9DyvEgDx/0uXrtMVRvqdYUmDg==", + "requires": { + "@firebase/functions-types": "0.3.11", + "@firebase/messaging-types": "0.3.5", + "isomorphic-fetch": "2.2.1", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/functions-types": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.11.tgz", + "integrity": "sha512-sNLrBzTm6Rzq/QMbCgenhJW2LU+gQx0IMYZJJjz50dhYzQSfirlEdFEo8xm59aWhqCw87AI4XzNSGgu53C2OVA==" + }, + "@firebase/installations": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.3.6.tgz", + "integrity": "sha512-30RNzx8wBHm5RIPVVxdpSBGohPeOCl5YQ5qCdmx/gImokP9q4GC1i8BOKh/OldXiRY6nMVU2uzPaCpOSZsPFNw==", + "requires": { + "@firebase/installations-types": "0.2.2", + "@firebase/util": "0.2.34", + "idb": "3.0.2", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/installations-types": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.2.2.tgz", + "integrity": "sha512-ABe4pJbBPYTyUQZ/BzyMZw9VEO+XYrClsxcVZ3WJTlhiu1OuBOeFfdbbsKPlywqwS5cVSM0xO9bJSgiMWQhUfQ==" + }, + "@firebase/logger": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.1.31.tgz", + "integrity": "sha512-1OEJaCMMaaT0VleNwer3bocbd25beR6KZUaHBweLNHEFxaNvniSv+lm83g08dWLBml3ZVOb945hp6m8REFx6/Q==" + }, + "@firebase/messaging": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.5.7.tgz", + "integrity": "sha512-JQLhHCo6THIzj74/iGrAxJY3YI05+q9UlHAykr8HkDtmNjFkiVCGLcuCrYcwayvElziGGQ3EQAD+EGAN1BkjCw==", + "requires": { + "@firebase/installations": "0.3.6", + "@firebase/messaging-types": "0.3.5", + "@firebase/util": "0.2.34", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/messaging-types": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.3.5.tgz", + "integrity": "sha512-pRhKJlE/eXc6uBGCmi7BjBen427lbIVlxUH9aKpu5Nrd8nUQ507WwVVL098A0oY0Kx2AJxjwjmtDjDYCaFm9Hg==" + }, + "@firebase/performance": { + "version": "0.2.26", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.2.26.tgz", + "integrity": "sha512-xQpdBv3dygt7y/mMkixPL9ty1YzTyGyVvazub1QNBqzEyIVRI7Slg03taFQ6551x+JPWmmyUCqH6+dQ4U2+HHg==", + "requires": { + "@firebase/installations": "0.3.6", + "@firebase/logger": "0.1.31", + "@firebase/performance-types": "0.0.6", + "@firebase/util": "0.2.34", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/performance-types": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.6.tgz", + "integrity": "sha512-nfiIWBuMVXj+G+xIQwpwQjFhtY85GnywAL1S/CTEMTe6tgc9nA+x9ycFU2rEIl3XVTeaHqs616pe1gYToGpRJQ==" + }, + "@firebase/polyfill": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.29.tgz", + "integrity": "sha512-Ogc6BUYoyOb64lFAGBjMydoczSHdazMeINTBjEEfSkaDqOi7l/tgk9X+oWYe5mxfPNrdBLREkfQb6oKqFPqydQ==", + "requires": { + "core-js": "3.4.1", + "promise-polyfill": "8.1.3", + "whatwg-fetch": "2.0.4" + }, + "dependencies": { + "core-js": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.1.tgz", + "integrity": "sha512-KX/dnuY/J8FtEwbnrzmAjUYgLqtk+cxM86hfG60LGiW3MmltIc2yAmDgBgEkfm0blZhUrdr1Zd84J2Y14mLxzg==" + }, + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + } + } + }, + "@firebase/remote-config": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.7.tgz", + "integrity": "sha512-v//JIZZSMwPPuHDK+SmcBPA02UTYT2NiDa+4U42Di2iFG5Q9nlOkrxHIOx6Qqr1t7gDqgC1qBRYBeorTpxbTeA==", + "requires": { + "@firebase/installations": "0.3.6", + "@firebase/logger": "0.1.31", + "@firebase/remote-config-types": "0.1.3", + "@firebase/util": "0.2.34", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/remote-config-types": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.3.tgz", + "integrity": "sha512-5niKHAHDEaTF6Ch7Jc91WNCy3ZdKEjYlq0l6tDTyjN0lHp6qJW1IKdS3ExBBRmpqP8vjQOUxSDiIPbIZV3ncHw==" + }, + "@firebase/storage": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.20.tgz", + "integrity": "sha512-aKZHC1M20rDKX8Xxkz5gBQgT1g0whk1rVeZaqCIrYyNsTbAEomzgodstFLtbXwhVsV+DwWzynm4G7WDoFpr4HA==", + "requires": { + "@firebase/storage-types": "0.3.6", + "@firebase/util": "0.2.34", + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/storage-types": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.6.tgz", + "integrity": "sha512-yHsYhaFQMryR/lgXIdm1mMQwmyC76HLpQHtAxB5WF9FzwjXishJTn1Qe0+JuSWmlHmYXI3EyrCr/JUAv2OS1wQ==" + }, + "@firebase/util": { + "version": "0.2.34", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.34.tgz", + "integrity": "sha512-k8pNIzNLncvxDrqYVZN6/lnqZWy0OCJuZmK5urodARwdLy3sVLw5p9PWce0v9qzMO8tLdrBbCpnm1KJ8jg/kBQ==", + "requires": { + "tslib": "1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@firebase/webchannel-wrapper": { + "version": "0.2.32", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.32.tgz", + "integrity": "sha512-kXnsBuzmtcOntliDgxAGJJ8JHPOmZ6KgS2v0/5Fb5KXpN9UIFyeaE8pfdkBG9tESdPmGdKpJz781fEIMdMtd8A==" + }, + "@grpc/proto-loader": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.3.tgz", + "integrity": "sha512-8qvUtGg77G2ZT2HqdqYoM/OY97gQd/0crSG34xNmZ4ZOsv3aQT/FQV9QfZPazTGna6MIoyUd+u6AxsoZjJ/VMQ==", + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + }, "@jest/console": { "version": "24.7.1", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", @@ -1115,6 +1432,60 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", @@ -1263,11 +1634,25 @@ "@babel/types": "^7.3.0" } }, + "@types/bytebuffer": { + "version": "5.0.40", + "resolved": "https://registry.npmjs.org/@types/bytebuffer/-/bytebuffer-5.0.40.tgz", + "integrity": "sha512-h48dyzZrPMz25K6Q4+NCwWaxwXany2FhQg/ErOcdZS1ZpsaDnDMZg8JYLMTGz7uvXKrcKGJUZJlZObyfgdaN9g==", + "requires": { + "@types/long": "*", + "@types/node": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz", "integrity": "sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg==" }, + "@types/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", + "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" + }, "@types/node": { "version": "11.13.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.7.tgz", @@ -1745,6 +2130,15 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "ascli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", + "integrity": "sha1-vPpZdKYvGOgcq660lzKrSoj5Brw=", + "requires": { + "colour": "~0.7.1", + "optjs": "~3.2.2" + } + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -2448,6 +2842,21 @@ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" }, + "bytebuffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", + "integrity": "sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=", + "requires": { + "long": "~3" + }, + "dependencies": { + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + } + } + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -2771,6 +3180,11 @@ "simple-swizzle": "^0.2.2" } }, + "colour": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz", + "integrity": "sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=" + }, "combined-stream": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", @@ -3565,6 +3979,11 @@ "entities": "^1.1.1" } }, + "dom-storage": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", + "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==" + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -4507,6 +4926,27 @@ "locate-path": "^3.0.0" } }, + "firebase": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.5.0.tgz", + "integrity": "sha512-1ymVJ9Xq9xE9uoQIwFO7yZY/fzUmjOkhkfsQPiQFaG9Ue0EUjZF7HP0NWFdJuYvgf7bLCI6IHJPA6EHbjQoVGw==", + "requires": { + "@firebase/analytics": "0.2.7", + "@firebase/app": "0.4.25", + "@firebase/app-types": "0.4.8", + "@firebase/auth": "0.13.1", + "@firebase/database": "0.5.13", + "@firebase/firestore": "1.8.0", + "@firebase/functions": "0.4.26", + "@firebase/installations": "0.3.6", + "@firebase/messaging": "0.5.7", + "@firebase/performance": "0.2.26", + "@firebase/polyfill": "0.3.29", + "@firebase/remote-config": "0.1.7", + "@firebase/storage": "0.3.20", + "@firebase/util": "0.2.34" + } + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -4869,38 +5309,665 @@ "minizlib": { "version": "1.2.1", "bundled": true, - "optional": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "optional": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "get-own-enumerable-property-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", + "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "requires": { + "global-prefix": "^3.0.0" + } + }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" + }, + "globby": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", + "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "requires": { + "array-union": "^1.0.1", + "dir-glob": "2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "dependencies": { + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + } + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + }, + "grpc": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.24.2.tgz", + "integrity": "sha512-EG3WH6AWMVvAiV15d+lr+K77HJ/KV/3FvMpjKjulXHbTwgDZkhkcWbwhxFAoTdxTkQvy0WFcO3Nog50QBbHZWw==", + "requires": { + "@types/bytebuffer": "^5.0.40", + "lodash.camelcase": "^4.3.0", + "lodash.clone": "^4.5.0", + "nan": "^2.13.2", + "node-pre-gyp": "^0.14.0", + "protobufjs": "^5.0.3" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "chownr": { + "version": "1.1.3", + "bundled": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "debug": { + "version": "3.2.6", + "bundled": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true + }, + "fs-minipass": { + "version": "1.2.7", + "bundled": true, + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.4", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.3", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "bundled": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true + } } }, "ms": { - "version": "2.1.1", - "bundled": true, - "optional": true + "version": "2.1.2", + "bundled": true }, "needle": { - "version": "2.3.0", + "version": "2.4.0", "bundled": true, - "optional": true, "requires": { - "debug": "^4.1.0", + "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.12.0", + "version": "0.14.0", "bundled": true, - "optional": true, "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", @@ -4911,13 +5978,12 @@ "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^4" + "tar": "^4.4.2" } }, "nopt": { "version": "4.0.1", "bundled": true, - "optional": true, "requires": { "abbrev": "1", "osenv": "^0.1.4" @@ -4925,13 +5991,11 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true, - "optional": true + "bundled": true }, "npm-packlist": { - "version": "1.4.1", + "version": "1.4.6", "bundled": true, - "optional": true, "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" @@ -4940,7 +6004,6 @@ "npmlog": { "version": "4.1.2", "bundled": true, - "optional": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -4950,36 +6013,38 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", - "bundled": true, - "optional": true + "bundled": true }, "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "osenv": { "version": "0.1.5", "bundled": true, - "optional": true, "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -4987,36 +6052,36 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "optional": true + "version": "2.0.1", + "bundled": true + }, + "protobufjs": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", + "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", + "requires": { + "ascli": "~1", + "bytebuffer": "~5", + "glob": "^7.0.5", + "yargs": "^3.10.0" + } }, "rc": { "version": "1.2.8", "bundled": true, - "optional": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "optional": true - } } }, "readable-stream": { "version": "2.3.6", "bundled": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5028,47 +6093,39 @@ } }, "rimraf": { - "version": "2.6.3", + "version": "2.7.1", "bundled": true, - "optional": true, "requires": { "glob": "^7.1.3" } }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, - "optional": true + "bundled": true }, "sax": { "version": "1.2.4", - "bundled": true, - "optional": true + "bundled": true }, "semver": { - "version": "5.7.0", - "bundled": true, - "optional": true + "version": "5.7.1", + "bundled": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, - "optional": true + "bundled": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, - "optional": true + "bundled": true }, "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5078,7 +6135,6 @@ "string_decoder": { "version": "1.1.1", "bundled": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -5086,199 +6142,67 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, - "optional": true + "bundled": true }, "tar": { - "version": "4.4.8", + "version": "4.4.13", "bundled": true, - "optional": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "util-deprecate": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "wide-align": { "version": "1.1.3", "bundled": true, - "optional": true, "requires": { "string-width": "^1.0.2 || 2" } }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { - "version": "3.0.3", - "bundled": true, - "optional": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" - }, - "get-own-enumerable-property-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", - "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==" - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } - } - }, - "globals": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", - "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" - }, - "globby": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", - "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", - "requires": { - "array-union": "^1.0.1", - "dir-glob": "2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - }, - "dependencies": { - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + "version": "3.1.1", + "bundled": true }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + "yargs": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", + "requires": { + "camelcase": "^2.0.1", + "cliui": "^3.0.3", + "decamelize": "^1.1.1", + "os-locale": "^1.4.0", + "string-width": "^1.0.1", + "window-size": "^0.1.4", + "y18n": "^3.2.0" + } } } }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" - }, "gud": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", @@ -5663,6 +6587,11 @@ "postcss": "^7.0.14" } }, + "idb": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", + "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==" + }, "identity-obj-proxy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", @@ -7003,6 +7932,16 @@ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -7050,6 +7989,11 @@ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=" }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7404,8 +8348,7 @@ "nan": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", - "optional": true + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==" }, "nanomatch": { "version": "1.2.13", @@ -7781,6 +8724,11 @@ "wordwrap": "~1.0.0" } }, + "optjs": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz", + "integrity": "sha1-aabOicRCpEQDFBrS+bNwvVu29O4=" + }, "original": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", @@ -9003,6 +9951,11 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, + "promise-polyfill": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" + }, "prompts": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.4.tgz", @@ -9030,6 +9983,33 @@ "xtend": "^4.0.1" } }, + "protobufjs": { + "version": "6.8.8", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", + "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "10.17.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.6.tgz", + "integrity": "sha512-0a2X6cgN3RdPBL2MIlR6Lt0KlM7fOFsutuXcdglcOq6WvLnYXgPQSh0Mx6tO1KCAE8MxbHSOSTWDoUxRq+l3DA==" + } + } + }, "proxy-addr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", @@ -11763,6 +12743,11 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "window-size": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", + "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -12025,6 +13010,11 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-1.3.1.tgz", "integrity": "sha512-tGkGJkN8XqCod7OT+EvGYK5Z4SfDQGD30zAa58OcnAa0RRWgzUEK72tkXhsX1FZd+rgnhRxFtmO+ihkp8LHSkw==" }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + }, "xregexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", diff --git a/client/package.json b/client/package.json index 5209eb0..48305e2 100644 --- a/client/package.json +++ b/client/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "axios": "^0.18.1", + "firebase": "^7.5.0", "moment": "^2.24.0", "react": "^16.8.6", "react-dom": "^16.8.6", diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index e3f28ff..07d8a0e 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -1,6 +1,5 @@ import axios from 'axios'; import { setAlert } from './alert'; - import { REGISTER_SUCCESS, REGISTER_FAIL, @@ -10,10 +9,10 @@ import { LOGIN_FAIL, LOGOUT, CLEAR_PROFILE, + SOCIAL_SUCCESS, } from './types'; import setAuthToken from '../utils/setAuthToken'; import firebase from 'firebase/app'; -import { Mongoose } from 'mongoose'; // Load User export const loadUser = () => async dispatch => { @@ -100,54 +99,28 @@ export const login = (email, password) => async dispatch => { } }; -// Register User with Social - -// Login User with social -export const loginWithSocial = () => async dispatch => { - const provider = new firebase.auth.FacebookAuthProvider(); - - try { - const result = await firebase.auth().signInWithPopup(provider); - - const { email, displayName, uid } = result.user; - const { accessToken } = result.credential; - console.log(result); +// Facebook Login User(in progress) - // Register user with facebook data - const config = { - headers: { - 'Content-Type': 'application/json', - }, - }; +export const handleSocialLogin = (email, password) => async dispatch => { + const config = { + headers: { + 'Content-Type': 'application/json', + }, + }; - const body = JSON.stringify({ name: displayName, email: email, password: uid }); + const body = JSON.stringify({ email, password }); - const res = await axios.post('/api/users', body, config); + try { + const res = await axios.post('/api/auth', body, config); dispatch({ - type: REGISTER_SUCCESS, + type: LOGIN_SUCCESS, payload: res.data, }); - + console.log(res); // dispatch(loadUser()); - console.log('user sent to database'); - - firebase.auth().onAuthStateChanged(async user => { - if (user) { - // dispatch({ - // type: LOGIN_SUCCESS, - // payload: { - // token: accessToken, - // }, - // }); - console.log('this is a facebook user'); - - dispatch(loadUser()); - } - }); } catch (err) { const errors = err.response.data.errors; - if (errors) { errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); } @@ -158,6 +131,35 @@ export const loginWithSocial = () => async dispatch => { } }; +// Facebook Register User +export const handleLogin = accessToken => async dispatch => { + const provider = new firebase.auth.FacebookAuthProvider(); + const result = await firebase.auth().signInWithPopup(provider); + console.log(result); + const _id = result.user.uid; + let name = result.user.displayName; + const avatar = result.user.providerData[0].photoURL; + // const date = result.user.metadata.creationTime; + const email = result.user.email; + const token = result.credential.accessToken; + + const config = { + headers: { + 'Content-Type': 'application/json', + }, + }; + + const body = JSON.stringify({ user: { _id, name, email, avatar } }); + const res = await axios.post('/api/users/facebook', body, config); + const user = res; + + dispatch({ + type: SOCIAL_SUCCESS, + payload: { token, user }, + }); + // dispatch(loadUser()); +}; + // Logout / Clear profile export const logout = () => dispatch => { dispatch({ type: CLEAR_PROFILE }); diff --git a/client/src/actions/types.js b/client/src/actions/types.js index 883c20e..68441c8 100755 --- a/client/src/actions/types.js +++ b/client/src/actions/types.js @@ -22,3 +22,4 @@ export const DELETE_POST = 'DELETE_POST'; export const ADD_POST = 'ADD_POST'; export const ADD_COMMENT = 'ADD_COMMENT'; export const REMOVE_COMMENT = 'REMOVE_COMMENT'; +export const SOCIAL_SUCCESS = 'SOCIAL_SUCCESS'; diff --git a/client/src/components/auth/FacebookAuth.js b/client/src/components/auth/FacebookAuth.js new file mode 100644 index 0000000..8b817b4 --- /dev/null +++ b/client/src/components/auth/FacebookAuth.js @@ -0,0 +1,37 @@ +import React from 'react'; +import firebase from 'firebase/app'; +import 'firebase/auth'; +import socialConfig from '../../social-config/firebaseConfig'; +import { handleLogin } from '../../actions/auth'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { Redirect } from 'react-router-dom'; + +if (!firebase.apps.length) { + firebase.initializeApp(socialConfig); +} + +const FacebookAuth = ({ handleLogin, isAuthenticated, accessToken }) => { + if (isAuthenticated) { + return ; + } + return ( +
+ +
+ ); +}; + +FacebookAuth.propTypes = { + handleLogin: PropTypes.func.isRequired, + isAuthenticated: PropTypes.bool, +}; + +const mapStateToProps = state => ({ + isAuthenticated: state.auth.isAuthenticated, + accessToken: state.auth.token, +}); + +export default connect(mapStateToProps, { handleLogin })(FacebookAuth); diff --git a/client/src/components/auth/Login.js b/client/src/components/auth/Login.js index 389632b..a1182ac 100755 --- a/client/src/components/auth/Login.js +++ b/client/src/components/auth/Login.js @@ -1,14 +1,14 @@ import React, { Fragment, useState } from 'react'; import { Link, Redirect } from 'react-router-dom'; import { connect } from 'react-redux'; -import { login, loginWithSocial } from '../../actions/auth'; +import { login } from '../../actions/auth'; import PropTypes from 'prop-types'; // if I remove import firebaseApp somehow login button doesn't work. Is it because I initialize firebase in firebaseConfig and you have to import it somewhere? -import firebaseApp from '../../social-config/firebaseConfig'; -import firebase from 'firebase'; +// import firebaseApp from '../../social-config/firebaseConfig'; +// import firebase from 'firebase'; -const Login = ({ login, isAuthenticated, loginWithSocial }) => { +const Login = ({ login, isAuthenticated }) => { const [formData, setFormData] = useState({ email: '', password: '', @@ -25,53 +25,51 @@ const Login = ({ login, isAuthenticated, loginWithSocial }) => { // Redirect if logged in if (isAuthenticated) { - return ; + return ; } return ( -

Sign In

-

- Sign Into Your Account +

Sign In

+

+ Sign Into Your Account

- onSubmit(e)}> -
+ onSubmit(e)}> +
onChange(e)} required />
-
+
onChange(e)} - minLength="6" + minLength='6' />
- + - {isAuthenticated ?
{firebase.auth().currentUser.displayName}
:
} - -

- Don't have an account? Sign Up +

+ Don't have an account? Sign Up

+ + Facebook + ); }; login.propTypes = { login: PropTypes.func.isRequired, - loginWithSocial: PropTypes.func.isRequired, isAuthenticated: PropTypes.bool, }; @@ -79,4 +77,4 @@ const mapStateToProps = state => ({ isAuthenticated: state.auth.isAuthenticated, }); -export default connect(mapStateToProps, { login, loginWithSocial })(Login); +export default connect(mapStateToProps, { login })(Login); diff --git a/client/src/components/routing/Routes.js b/client/src/components/routing/Routes.js index 3b4479c..d56000f 100755 --- a/client/src/components/routing/Routes.js +++ b/client/src/components/routing/Routes.js @@ -14,6 +14,7 @@ import Posts from '../posts/Posts'; import Post from '../post/Post'; import NotFound from '../layout/NotFound'; import PrivateRoute from '../routing/PrivateRoute'; +import FacebookAuth from '../auth/FacebookAuth'; const Routes = () => { return ( @@ -22,6 +23,7 @@ const Routes = () => { + diff --git a/client/src/reducers/auth.js b/client/src/reducers/auth.js index eb8a4c3..eb84968 100755 --- a/client/src/reducers/auth.js +++ b/client/src/reducers/auth.js @@ -6,14 +6,15 @@ import { LOGIN_SUCCESS, LOGIN_FAIL, LOGOUT, - ACCOUNT_DELETED + ACCOUNT_DELETED, + SOCIAL_SUCCESS, } from '../actions/types'; const initialState = { token: localStorage.getItem('token'), isAuthenticated: null, loading: true, - user: null + user: null, }; export default function(state = initialState, action) { @@ -25,7 +26,7 @@ export default function(state = initialState, action) { ...state, isAuthenticated: true, loading: false, - user: payload + user: payload, }; case REGISTER_SUCCESS: case LOGIN_SUCCESS: @@ -34,7 +35,15 @@ export default function(state = initialState, action) { ...state, ...payload, isAuthenticated: true, - loading: false + loading: false, + }; + case SOCIAL_SUCCESS: + localStorage.setItem('token', payload.token); + return { + ...state, + ...payload, + isAuthenticated: true, + loading: false, }; case REGISTER_FAIL: case AUTH_ERROR: @@ -46,7 +55,7 @@ export default function(state = initialState, action) { ...state, token: null, isAuthenticated: false, - loading: false + loading: false, }; default: return state; diff --git a/client/src/social-config/firebaseConfig.js b/client/src/social-config/firebaseConfig.js index 64108e9..3b69c75 100644 --- a/client/src/social-config/firebaseConfig.js +++ b/client/src/social-config/firebaseConfig.js @@ -3,14 +3,14 @@ import * as firebase from 'firebase'; // const settings = { timestampsInSnapshots: true }; const firebaseConfig = { - apiKey: '', - authDomain: '', - databaseURL: '', - projectId: '', - storageBucket: '', - messagingSenderId: '', - appId: '', - measurementId: '', + apiKey: 'AIzaSyDE8pABtUCH2kzGsN-htLQY8yDednivRiQ', + authDomain: 'ocean-d27d7.firebaseapp.com', + databaseURL: 'https://ocean-d27d7.firebaseio.com', + projectId: 'ocean-d27d7', + storageBucket: 'ocean-d27d7.appspot.com', + messagingSenderId: '732932278090', + appId: '1:732932278090:web:2f6389b5472052698663b3', + measurementId: 'G-3X88ZLXK9L', }; // Could not remove this into another file it breaks the code diff --git a/config/default.json b/config/default.json index 1c210b2..da28b5e 100755 --- a/config/default.json +++ b/config/default.json @@ -1,6 +1,6 @@ { - "mongoURI": "", - "jwtSecret": "secret", - "githubClientId": "", - "githubSecret": "" + "mongoURI": "mongodb+srv://mergorgec:mergorgec@socialnetwork-5bjss.mongodb.net/test?retryWrites=true&w=majority", + "jwtSecret": "mysecrettoken", + "githubClientId": "d6bc2d7e856de04bbc4f", + "githubSecret": "1b1b8df16fc5a57148dab746dc892ce0de2668b2" } diff --git a/routes/api/users.js b/routes/api/users.js index a6f62fe..68b31d5 100755 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -18,10 +18,7 @@ router.post( .not() .isEmpty(), check('email', 'Please include a valid email').isEmail(), - check( - 'password', - 'Please enter a password with 6 or more characters' - ).isLength({ min: 6 }) + check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 }), ], async (req, res) => { const errors = validationResult(req); @@ -35,22 +32,20 @@ router.post( let user = await User.findOne({ email }); if (user) { - return res - .status(400) - .json({ errors: [{ msg: 'User already exists' }] }); + return res.status(400).json({ errors: [{ msg: 'User already exists' }] }); } const avatar = gravatar.url(email, { s: '200', r: 'pg', - d: 'mm' + d: 'mm', }); user = new User({ name, email, avatar, - password + password, }); const salt = await bcrypt.genSalt(10); @@ -61,24 +56,46 @@ router.post( const payload = { user: { - id: user.id - } + id: user.id, + }, }; - jwt.sign( - payload, - config.get('jwtSecret'), - { expiresIn: 360000 }, - (err, token) => { - if (err) throw err; - res.json({ token }); - } - ); + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { + if (err) throw err; + res.json({ token }); + }); } catch (err) { console.error(err.message); res.status(500).send('Server error'); } - } + }, ); +// @route POST api/users/facebook +// @desc Register user +// @access Public +router.post('/facebook', async (req, res) => { + const { name, email, avatar } = req.body.user; + + try { + let user = await User.findOne({ email }); + if (user) { + return res.status(400).json({ errors: [{ msg: 'User already exists' }] }); + } + user = new User({ + name, + email, + avatar, + }); + res.json({ user: { name, email, avatar } }); + await user.save(); + } catch (err) { + const errors = err.response.data.errors; + + if (errors) { + errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); + } + } +}); + module.exports = router; From 230bebf152b9abb19c402381161e01ceb844702e Mon Sep 17 00:00:00 2001 From: 20twa07 <48910851+20twa07@users.noreply.github.com> Date: Fri, 29 Nov 2019 17:02:52 +0100 Subject: [PATCH 27/76] add social user model --- models/User.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/models/User.js b/models/User.js index d7386df..5eab98c 100755 --- a/models/User.js +++ b/models/User.js @@ -1,26 +1,38 @@ const mongoose = require('mongoose'); const UserSchema = new mongoose.Schema({ + user: { + name: { + type: String, + }, + email: { + type: String, + unique: true, + }, + avatar: { + type: String, + }, + }, name: { type: String, - required: true + required: true, }, email: { type: String, required: true, - unique: true + unique: true, }, password: { type: String, - required: true + required: true, }, avatar: { - type: String + type: String, }, date: { type: Date, - default: Date.now - } + default: Date.now, + }, }); module.exports = User = mongoose.model('user', UserSchema); From ae6a5c9777c8e4d4977615c2a8d461b24783318e Mon Sep 17 00:00:00 2001 From: Oguzhan Date: Fri, 29 Nov 2019 21:18:25 +0100 Subject: [PATCH 28/76] Fix PasswordReset component logic --- .../password-forms/PasswordEmailForm.js | 32 ++++--------- .../password-forms/PasswordReset.js | 45 +++---------------- routes/api/auth.js | 6 ++- 3 files changed, 21 insertions(+), 62 deletions(-) diff --git a/client/src/components/password-forms/PasswordEmailForm.js b/client/src/components/password-forms/PasswordEmailForm.js index bbe7d32..18be033 100644 --- a/client/src/components/password-forms/PasswordEmailForm.js +++ b/client/src/components/password-forms/PasswordEmailForm.js @@ -1,13 +1,11 @@ -import React, { Fragment, useState } from "react"; -import axios from "axios"; -import { Redirect } from "react-router-dom"; -import { setAlert } from "../../actions/alert"; -import { connect } from "react-redux"; -import PropTypes from "prop-types"; -import Spinner from "../layout/Spinner"; +import React, { Fragment, useState } from 'react'; +import axios from 'axios'; +import { setAlert } from '../../actions/alert'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import Spinner from '../layout/Spinner'; - -const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { +const PasswordEmailForm = ({ setAlert }) => { const [formData, setFormData] = useState({}); const [sent, setSent] = useState(false); const [showSpinner, setShowSpinner] = useState(false); @@ -27,7 +25,7 @@ const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { const reqBody = JSON.stringify({ email: formData.email }); setShowSpinner(true); - await axios.put(`/api/auth/forgot-password/`, reqBody, reqConfig); + const res = await axios.put(`/api/auth/forgot-password/`, reqBody, reqConfig); setAlert(res.data.msg, 'success'); setShowSpinner(false); setSent(true); @@ -40,12 +38,6 @@ const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { } }; - - if (sent) { - if (isAuthenticated) { - return ; - } -// TODO There is something wrong with this logic if (!sent && showSpinner) { return ( @@ -58,7 +50,6 @@ const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { } if (sent && !showSpinner) { - return (

Reset password email is sent.

@@ -95,11 +86,6 @@ const PasswordEmailForm = ({ setAlert, isAuthenticated }) => { PasswordEmailForm.propTypes = { setAlert: PropTypes.func.isRequired, - isAuthenticated: PropTypes.bool, }; -const mapStateToProps = state => ({ - isAuthenticated: state.auth.isAuthenticated, -}); - -export default connect(mapStateToProps, { setAlert })(PasswordEmailForm); +export default connect(null, { setAlert })(PasswordEmailForm); diff --git a/client/src/components/password-forms/PasswordReset.js b/client/src/components/password-forms/PasswordReset.js index 1b6da5e..4fed3da 100644 --- a/client/src/components/password-forms/PasswordReset.js +++ b/client/src/components/password-forms/PasswordReset.js @@ -7,7 +7,7 @@ import { setAlert } from '../../actions/alert'; import PropTypes from 'prop-types'; import Spinner from '../layout/Spinner'; -const PasswordReset = ({ match, setAlert, isAuthenticated }) => { +const PasswordReset = ({ match, setAlert }) => { const [formData, setFormData] = useState({ password: '', password2: '', @@ -44,16 +44,12 @@ const PasswordReset = ({ match, setAlert, isAuthenticated }) => { setRedirect(true); } catch (err) { console.error(err.message); - setAlert('Password Reset Link Invalid', 'danger'); + setAlert('Password reset link is invalid!', 'danger'); setResetError(true); } } }; - if (isAuthenticated) { - return ; - } - if (resetError) { return ( @@ -66,35 +62,13 @@ const PasswordReset = ({ match, setAlert, isAuthenticated }) => { } if (redirect) { - setAlert(`Password Reset success`, 'success'); + setAlert(`Password changed successfully!`, 'success'); return ; - - //TODO Commented out new Spinner feature not to get conflicted. - //TODO redirection is now working - - // if (!redirect && showSpinner) { - // return ( - // - //

Resetting Password, please wait...

- //

- // - //

- //
- // ); - // } - // if (redirect && !showSpinner) { - // setAlert(`Password Reset success`, "success"); - // return ( - //

- // Password Reset Success{" "} - // - // Login - // - //

- // ); } - return ( + return showSpinner ? ( + + ) : (

Reset Password

@@ -129,11 +103,6 @@ const PasswordReset = ({ match, setAlert, isAuthenticated }) => { PasswordReset.propTypes = { setAlert: PropTypes.func.isRequired, match: PropTypes.object.isRequired, - isAuthenticated: PropTypes.bool, }; -const mapStateToProps = state => ({ - isAuthenticated: state.auth.isAuthenticated, -}); - -export default connect(mapStateToProps, { setAlert })(PasswordReset); +export default connect(null, { setAlert })(PasswordReset); diff --git a/routes/api/auth.js b/routes/api/auth.js index 21b03ec..e8f2b30 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -88,7 +88,11 @@ router.put( try { const user = await User.findOne({ email }); - if (!user) return res.status(401).json({ errors: [{ msg: `No user found with ${email}` }] }); + // To hide if the user is registered or not. (Social engineering attack) + if (!user) + res.json({ + msg: `Email has been sent to ${email}. Follow the instructions to reset your password.`, + }); const token = jwt.sign({ _id: user._id }, config.get('jwtSecret')); From 22408a8c1d7a7331c907880881857aac9766267a Mon Sep 17 00:00:00 2001 From: 20twa07 <48910851+20twa07@users.noreply.github.com> Date: Fri, 29 Nov 2019 21:24:41 +0100 Subject: [PATCH 29/76] solve db bug --- client/src/actions/auth.js | 107 +++++++++++++++++++++++------------- client/src/actions/types.js | 1 + client/src/reducers/auth.js | 10 +++- models/User.js | 12 ---- routes/api/auth.js | 42 +++++++------- routes/api/users.js | 17 +++--- 6 files changed, 112 insertions(+), 77 deletions(-) diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index 07d8a0e..5bfaddc 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -10,6 +10,7 @@ import { LOGOUT, CLEAR_PROFILE, SOCIAL_SUCCESS, + SOCIAL_USER_LOADED, } from './types'; import setAuthToken from '../utils/setAuthToken'; import firebase from 'firebase/app'; @@ -34,6 +35,25 @@ export const loadUser = () => async dispatch => { } }; +// Load Social User +export const loadSocialUser = password => async dispatch => { + if (localStorage.token) { + setAuthToken(password); + } + + try { + const res = await axios.get('/api/auth/facebook'); + + dispatch({ + type: SOCIAL_USER_LOADED, + payload: res.data, + }); + } catch (err) { + dispatch({ + type: AUTH_ERROR, + }); + } +}; // Register User export const register = ({ name, email, password }) => async dispatch => { const config = { @@ -101,65 +121,76 @@ export const login = (email, password) => async dispatch => { // Facebook Login User(in progress) -export const handleSocialLogin = (email, password) => async dispatch => { +// export const handleSocialLogin = (email, password) => async dispatch => { +// const config = { +// headers: { +// 'Content-Type': 'application/json', +// }, +// }; + +// const body = JSON.stringify({ email, password }); + +// try { +// const res = await axios.post('/api/auth', body, config); + +// dispatch({ +// type: LOGIN_SUCCESS, +// payload: res.data, +// }); +// console.log(res); +// // dispatch(loadUser()); +// } catch (err) { +// const errors = err.response.data.errors; +// if (errors) { +// errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); +// } + +// dispatch({ +// type: LOGIN_FAIL, +// }); +// } +// }; + +// Facebook Register User +export const handleLogin = () => async dispatch => { + const provider = new firebase.auth.FacebookAuthProvider(); + const result = await firebase.auth().signInWithPopup(provider); + console.log(result); + const _id = result.user.uid; + let name = result.user.displayName; + const avatar = result.user.providerData[0].photoURL; + // const date = result.user.metadata.creationTime; + const email = result.user.email; + const password = result.credential.accessToken; + const config = { headers: { 'Content-Type': 'application/json', }, }; - const body = JSON.stringify({ email, password }); - + const body = JSON.stringify({ name, email, avatar, password }); try { - const res = await axios.post('/api/auth', body, config); - + const res = await axios.post('/api/users/facebook', body, config); + console.log(res); dispatch({ - type: LOGIN_SUCCESS, + type: SOCIAL_SUCCESS, payload: res.data, }); - console.log(res); - // dispatch(loadUser()); + dispatch(loadSocialUser(password)); } catch (err) { const errors = err.response.data.errors; + if (errors) { errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); } dispatch({ - type: LOGIN_FAIL, + type: REGISTER_FAIL, }); } }; -// Facebook Register User -export const handleLogin = accessToken => async dispatch => { - const provider = new firebase.auth.FacebookAuthProvider(); - const result = await firebase.auth().signInWithPopup(provider); - console.log(result); - const _id = result.user.uid; - let name = result.user.displayName; - const avatar = result.user.providerData[0].photoURL; - // const date = result.user.metadata.creationTime; - const email = result.user.email; - const token = result.credential.accessToken; - - const config = { - headers: { - 'Content-Type': 'application/json', - }, - }; - - const body = JSON.stringify({ user: { _id, name, email, avatar } }); - const res = await axios.post('/api/users/facebook', body, config); - const user = res; - - dispatch({ - type: SOCIAL_SUCCESS, - payload: { token, user }, - }); - // dispatch(loadUser()); -}; - // Logout / Clear profile export const logout = () => dispatch => { dispatch({ type: CLEAR_PROFILE }); diff --git a/client/src/actions/types.js b/client/src/actions/types.js index 68441c8..e037846 100755 --- a/client/src/actions/types.js +++ b/client/src/actions/types.js @@ -23,3 +23,4 @@ export const ADD_POST = 'ADD_POST'; export const ADD_COMMENT = 'ADD_COMMENT'; export const REMOVE_COMMENT = 'REMOVE_COMMENT'; export const SOCIAL_SUCCESS = 'SOCIAL_SUCCESS'; +export const SOCIAL_USER_LOADED = 'SOCIAL_USER_LOADED'; diff --git a/client/src/reducers/auth.js b/client/src/reducers/auth.js index eb84968..b6aedf8 100755 --- a/client/src/reducers/auth.js +++ b/client/src/reducers/auth.js @@ -8,6 +8,7 @@ import { LOGOUT, ACCOUNT_DELETED, SOCIAL_SUCCESS, + SOCIAL_USER_LOADED, } from '../actions/types'; const initialState = { @@ -28,6 +29,13 @@ export default function(state = initialState, action) { loading: false, user: payload, }; + case SOCIAL_USER_LOADED: + return { + ...state, + isAuthenticated: true, + loading: false, + user: payload, + }; case REGISTER_SUCCESS: case LOGIN_SUCCESS: localStorage.setItem('token', payload.token); @@ -38,10 +46,10 @@ export default function(state = initialState, action) { loading: false, }; case SOCIAL_SUCCESS: - localStorage.setItem('token', payload.token); return { ...state, ...payload, + token: payload.password, isAuthenticated: true, loading: false, }; diff --git a/models/User.js b/models/User.js index 5eab98c..1d4c5c6 100755 --- a/models/User.js +++ b/models/User.js @@ -1,18 +1,6 @@ const mongoose = require('mongoose'); const UserSchema = new mongoose.Schema({ - user: { - name: { - type: String, - }, - email: { - type: String, - unique: true, - }, - avatar: { - type: String, - }, - }, name: { type: String, required: true, diff --git a/routes/api/auth.js b/routes/api/auth.js index 34c182b..7756b2a 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -21,6 +21,19 @@ router.get('/', auth, async (req, res) => { } }); +// @route GET api/auth/facebook +// @desc Load user +// @access Public +router.get('/facebook', auth, async (req, res) => { + try { + const user = await User.findById(req.user.id).select('-password'); + res.json(user); + } catch (err) { + console.error(err.message); + res.status(500).send('Server Error'); + } +}); + // @route POST api/auth // @desc Authenticate user & get token // @access Public @@ -28,7 +41,7 @@ router.post( '/', [ check('email', 'Please include a valid email').isEmail(), - check('password', 'Password is required').exists() + check('password', 'Password is required').exists(), ], async (req, res) => { const errors = validationResult(req); @@ -42,39 +55,30 @@ router.post( let user = await User.findOne({ email }); if (!user) { - return res - .status(400) - .json({ errors: [{ msg: 'Invalid Credentials' }] }); + return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] }); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { - return res - .status(400) - .json({ errors: [{ msg: 'Invalid Credentials' }] }); + return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] }); } const payload = { user: { - id: user.id - } + id: user.id, + }, }; - jwt.sign( - payload, - config.get('jwtSecret'), - { expiresIn: 360000 }, - (err, token) => { - if (err) throw err; - res.json({ token }); - } - ); + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { + if (err) throw err; + res.json({ token }); + }); } catch (err) { console.error(err.message); res.status(500).send('Server error'); } - } + }, ); module.exports = router; diff --git a/routes/api/users.js b/routes/api/users.js index 68b31d5..9d203aa 100755 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -75,26 +75,29 @@ router.post( // @desc Register user // @access Public router.post('/facebook', async (req, res) => { - const { name, email, avatar } = req.body.user; + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + const { name, email, avatar, password } = req.body; try { let user = await User.findOne({ email }); if (user) { return res.status(400).json({ errors: [{ msg: 'User already exists' }] }); } + user = new User({ name, email, avatar, + password, }); - res.json({ user: { name, email, avatar } }); await user.save(); + res.json({ password }); } catch (err) { - const errors = err.response.data.errors; - - if (errors) { - errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); - } + console.error(err.message); + res.status(500).send('Server error'); } }); From aa8997a36880380aa848ce1a91ff25f74ae2da94 Mon Sep 17 00:00:00 2001 From: Adham Elias Botrous <50738429+Adham-Elias-Botrous@users.noreply.github.com> Date: Fri, 29 Nov 2019 22:20:23 +0100 Subject: [PATCH 30/76] add return Add 'return' to the response when the user is not found, so the code executing will stop after sending the response. --- routes/api/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api/auth.js b/routes/api/auth.js index e8f2b30..b3766d2 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -90,7 +90,7 @@ router.put( // To hide if the user is registered or not. (Social engineering attack) if (!user) - res.json({ + return res.json({ msg: `Email has been sent to ${email}. Follow the instructions to reset your password.`, }); From 3500833d1d55f0ea79a7952733083f2b90ab1850 Mon Sep 17 00:00:00 2001 From: 20twa07 <48910851+20twa07@users.noreply.github.com> Date: Sat, 30 Nov 2019 10:48:17 +0100 Subject: [PATCH 31/76] add loadSocialUser actionCreator --- client/src/actions/auth.js | 82 +++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index 5bfaddc..02263af 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -23,7 +23,7 @@ export const loadUser = () => async dispatch => { try { const res = await axios.get('/api/auth'); - + console.log(res); dispatch({ type: USER_LOADED, payload: res.data, @@ -36,14 +36,14 @@ export const loadUser = () => async dispatch => { }; // Load Social User -export const loadSocialUser = password => async dispatch => { +export const loadSocialUser = () => async dispatch => { if (localStorage.token) { - setAuthToken(password); + setAuthToken(localStorage.token); } - + console.log(localStorage.token); try { const res = await axios.get('/api/auth/facebook'); - + console.log(res.data); dispatch({ type: SOCIAL_USER_LOADED, payload: res.data, @@ -119,44 +119,46 @@ export const login = (email, password) => async dispatch => { } }; -// Facebook Login User(in progress) - -// export const handleSocialLogin = (email, password) => async dispatch => { -// const config = { -// headers: { -// 'Content-Type': 'application/json', -// }, -// }; - -// const body = JSON.stringify({ email, password }); - -// try { -// const res = await axios.post('/api/auth', body, config); - -// dispatch({ -// type: LOGIN_SUCCESS, -// payload: res.data, -// }); -// console.log(res); -// // dispatch(loadUser()); -// } catch (err) { -// const errors = err.response.data.errors; -// if (errors) { -// errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); -// } - -// dispatch({ -// type: LOGIN_FAIL, -// }); -// } -// }; +// Facebook Login User + +export const handleSocialLogin = (email, password) => async dispatch => { + await firebase.auth().signInWithEmailAndPassword(email, password); + + const config = { + headers: { + 'Content-Type': 'application/json', + }, + }; + + const body = JSON.stringify({ email, password }); + + try { + const res = await axios.post('/api/auth', body, config); + + dispatch({ + type: LOGIN_SUCCESS, + payload: res.data, + }); + console.log(res); + dispatch(loadUser()); + } catch (err) { + const errors = err.response.data.errors; + if (errors) { + errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); + } + + dispatch({ + type: LOGIN_FAIL, + }); + } +}; // Facebook Register User export const handleLogin = () => async dispatch => { const provider = new firebase.auth.FacebookAuthProvider(); const result = await firebase.auth().signInWithPopup(provider); console.log(result); - const _id = result.user.uid; + // const _id = result.user.uid; let name = result.user.displayName; const avatar = result.user.providerData[0].photoURL; // const date = result.user.metadata.creationTime; @@ -168,16 +170,16 @@ export const handleLogin = () => async dispatch => { 'Content-Type': 'application/json', }, }; - + console.log('pass' + password); const body = JSON.stringify({ name, email, avatar, password }); + try { const res = await axios.post('/api/users/facebook', body, config); - console.log(res); dispatch({ type: SOCIAL_SUCCESS, payload: res.data, }); - dispatch(loadSocialUser(password)); + dispatch(loadSocialUser()); } catch (err) { const errors = err.response.data.errors; From 1316db1b51dc7eea033ffa829fd6d15a1bcbb393 Mon Sep 17 00:00:00 2001 From: 20twa07 <48910851+20twa07@users.noreply.github.com> Date: Sat, 30 Nov 2019 10:49:29 +0100 Subject: [PATCH 32/76] add jwt --- routes/api/users.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/routes/api/users.js b/routes/api/users.js index 9d203aa..e00be42 100755 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -93,8 +93,22 @@ router.post('/facebook', async (req, res) => { avatar, password, }); + const salt = await bcrypt.genSalt(10); + + user.password = await bcrypt.hash(password, salt); + await user.save(); - res.json({ password }); + + const payload = { + user: { + id: user.id, + }, + }; + + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { + if (err) throw err; + res.json({ token }); + }); } catch (err) { console.error(err.message); res.status(500).send('Server error'); From 54c1e6817149c026c0e1a9793c206a4f3df6a611 Mon Sep 17 00:00:00 2001 From: 20twa07 <48910851+20twa07@users.noreply.github.com> Date: Sat, 30 Nov 2019 10:54:52 +0100 Subject: [PATCH 33/76] Add login & fix register issues --- client/src/components/auth/Login.js | 3 +- client/src/components/profiles/ProfileItem.js | 6 +- client/src/reducers/auth.js | 2 +- routes/api/profile.js | 100 ++++++++---------- 4 files changed, 49 insertions(+), 62 deletions(-) diff --git a/client/src/components/auth/Login.js b/client/src/components/auth/Login.js index a1182ac..5f3ffc5 100755 --- a/client/src/components/auth/Login.js +++ b/client/src/components/auth/Login.js @@ -1,7 +1,7 @@ import React, { Fragment, useState } from 'react'; import { Link, Redirect } from 'react-router-dom'; import { connect } from 'react-redux'; -import { login } from '../../actions/auth'; +import { login, handleSocialLogin } from '../../actions/auth'; import PropTypes from 'prop-types'; // if I remove import firebaseApp somehow login button doesn't work. Is it because I initialize firebase in firebaseConfig and you have to import it somewhere? @@ -21,6 +21,7 @@ const Login = ({ login, isAuthenticated }) => { const onSubmit = async e => { e.preventDefault(); login(email, password); + handleSocialLogin(email, password); }; // Redirect if logged in diff --git a/client/src/components/profiles/ProfileItem.js b/client/src/components/profiles/ProfileItem.js index d1bb1d6..9d388d7 100755 --- a/client/src/components/profiles/ProfileItem.js +++ b/client/src/components/profiles/ProfileItem.js @@ -8,8 +8,8 @@ const ProfileItem = ({ status, company, location, - skills - } + skills, + }, }) => { return (

@@ -36,7 +36,7 @@ const ProfileItem = ({ }; ProfileItem.propTypes = { - profile: PropTypes.object.isRequired + profile: PropTypes.object.isRequired, }; export default ProfileItem; diff --git a/client/src/reducers/auth.js b/client/src/reducers/auth.js index b6aedf8..e8a986e 100755 --- a/client/src/reducers/auth.js +++ b/client/src/reducers/auth.js @@ -46,10 +46,10 @@ export default function(state = initialState, action) { loading: false, }; case SOCIAL_SUCCESS: + localStorage.setItem('token', payload.token); return { ...state, ...payload, - token: payload.password, isAuthenticated: true, loading: false, }; diff --git a/routes/api/profile.js b/routes/api/profile.js index d358454..51ff183 100755 --- a/routes/api/profile.js +++ b/routes/api/profile.js @@ -14,10 +14,10 @@ const Post = require('../../models/Post'); // @access Private router.get('/me', auth, async (req, res) => { try { - const profile = await Profile.findOne({ user: req.user.id }).populate( - 'user', - ['name', 'avatar'] - ); + const profile = await Profile.findOne({ user: req.user.id }).populate('user', [ + 'name', + 'avatar', + ]); if (!profile) { return res.status(400).json({ msg: 'There is no profile for this user' }); @@ -43,8 +43,8 @@ router.post( .isEmpty(), check('skills', 'Skills is required') .not() - .isEmpty() - ] + .isEmpty(), + ], ], async (req, res) => { const errors = validationResult(req); @@ -64,7 +64,7 @@ router.post( facebook, twitter, instagram, - linkedin + linkedin, } = req.body; // Build profile object @@ -93,14 +93,14 @@ router.post( let profile = await Profile.findOneAndUpdate( { user: req.user.id }, { $set: profileFields }, - { new: true, upsert: true } + { new: true, upsert: true }, ); res.json(profile); } catch (err) { console.error(err.message); res.status(500).send('Server Error'); } - } + }, ); // @route GET api/profile @@ -122,7 +122,7 @@ router.get('/', async (req, res) => { router.get('/user/:user_id', async (req, res) => { try { const profile = await Profile.findOne({ - user: req.params.user_id + user: req.params.user_id, }).populate('user', ['name', 'avatar']); if (!profile) return res.status(400).json({ msg: 'Profile not found' }); @@ -172,8 +172,8 @@ router.put( .isEmpty(), check('from', 'From date is required') .not() - .isEmpty() - ] + .isEmpty(), + ], ], async (req, res) => { const errors = validationResult(req); @@ -181,15 +181,7 @@ router.put( return res.status(400).json({ errors: errors.array() }); } - const { - title, - company, - location, - from, - to, - current, - description - } = req.body; + const { title, company, location, from, to, current, description } = req.body; const newExp = { title, @@ -198,7 +190,7 @@ router.put( from, to, current, - description + description, }; try { @@ -213,7 +205,7 @@ router.put( console.error(err.message); res.status(500).send('Server Error'); } - } + }, ); // @route DELETE api/profile/experience/:exp_id @@ -246,20 +238,20 @@ router.delete('/experience/:exp_id', auth, async (req, res) => { // if i dont add .toString() it returns this weird mongoose coreArray and the ids are somehow objects and it still deletes anyway even if you put /experience/5 const removeIndex = expIds.indexOf(req.params.exp_id); if (removeIndex === -1) { - return res.status(500).json({ msg: "Server error" }); + return res.status(500).json({ msg: 'Server error' }); } else { // theses console logs helped me figure it out - console.log("expIds", expIds); - console.log("typeof expIds", typeof expIds); - console.log("req.params", req.params); - console.log("removed", expIds.indexOf(req.params.exp_id)); + console.log('expIds', expIds); + console.log('typeof expIds', typeof expIds); + console.log('req.params', req.params); + console.log('removed', expIds.indexOf(req.params.exp_id)); foundProfile.experience.splice(removeIndex, 1); await foundProfile.save(); return res.status(200).json(foundProfile); } } catch (error) { console.error(error); - return res.status(500).json({ msg: "Server error" }); + return res.status(500).json({ msg: 'Server error' }); } }); @@ -282,8 +274,8 @@ router.put( .isEmpty(), check('from', 'From date is required') .not() - .isEmpty() - ] + .isEmpty(), + ], ], async (req, res) => { const errors = validationResult(req); @@ -291,15 +283,7 @@ router.put( return res.status(400).json({ errors: errors.array() }); } - const { - school, - degree, - fieldofstudy, - from, - to, - current, - description - } = req.body; + const { school, degree, fieldofstudy, from, to, current, description } = req.body; const newEdu = { school, @@ -308,7 +292,7 @@ router.put( from, to, current, - description + description, }; try { @@ -323,20 +307,20 @@ router.put( console.error(err.message); res.status(500).send('Server Error'); } - } + }, ); // @route DELETE api/profile/education/:edu_id // @desc Delete education from profile // @access Private //router.delete('/education/:edu_id', auth, async (req, res) => { - //try { - //const profile = await Profile.findOne({ user: req.user.id }); +//try { +//const profile = await Profile.findOne({ user: req.user.id }); - // Get remove index - //const removeIndex = profile.education - //.map(item => item.id) - //.indexOf(req.params.edu_id); +// Get remove index +//const removeIndex = profile.education +//.map(item => item.id) +//.indexOf(req.params.edu_id); /* profile.education.splice(removeIndex, 1); @@ -350,14 +334,14 @@ router.put( }); */ -router.delete("/education/:edu_id", auth, async (req, res) => { +router.delete('/education/:edu_id', auth, async (req, res) => { try { const foundProfile = await Profile.findOne({ user: req.user.id }); const eduIds = foundProfile.education.map(edu => edu._id.toString()); // if i dont add .toString() it returns this weird mongoose coreArray and the ids are somehow objects and it still deletes anyway even if you put /education/5 const removeIndex = eduIds.indexOf(req.params.edu_id); if (removeIndex === -1) { - return res.status(500).json({ msg: "Server error" }); + return res.status(500).json({ msg: 'Server error' }); } else { // theses console logs helped me figure it out /* console.log("eduIds", eduIds); @@ -373,7 +357,7 @@ router.delete("/education/:edu_id", auth, async (req, res) => { } } catch (error) { console.error(error); - return res.status(500).json({ msg: "Server error" }); + return res.status(500).json({ msg: 'Server error' }); } }); // @route GET api/profile/github/:username @@ -382,13 +366,15 @@ router.delete("/education/:edu_id", auth, async (req, res) => { router.get('/github/:username', (req, res) => { try { const options = { - uri: encodeURI(`https://api.github.com/users/${ - req.params.username - }/repos?per_page=5&sort=created:asc&client_id=${config.get( - 'githubClientId' - )}&client_secret=${config.get('githubSecret')}`), + uri: encodeURI( + `https://api.github.com/users/${ + req.params.username + }/repos?per_page=5&sort=created:asc&client_id=${config.get( + 'githubClientId', + )}&client_secret=${config.get('githubSecret')}`, + ), method: 'GET', - headers: { 'user-agent': 'node.js' } + headers: { 'user-agent': 'node.js' }, }; request(options, (error, response, body) => { From a1e73488ac7a5fd59ba34e4b831cb13b97e254de Mon Sep 17 00:00:00 2001 From: Gul Date: Sat, 30 Nov 2019 13:02:56 +0100 Subject: [PATCH 34/76] Allow login with social after registering --- client/src/actions/auth.js | 2 +- client/src/social-config/firebaseConfig.js | 16 ++++++++-------- routes/api/users.js | 18 +++++++++++++++++- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index 02263af..3cec43b 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -163,7 +163,7 @@ export const handleLogin = () => async dispatch => { const avatar = result.user.providerData[0].photoURL; // const date = result.user.metadata.creationTime; const email = result.user.email; - const password = result.credential.accessToken; + const password = result.user.uid; const config = { headers: { diff --git a/client/src/social-config/firebaseConfig.js b/client/src/social-config/firebaseConfig.js index 3b69c75..37cc78d 100644 --- a/client/src/social-config/firebaseConfig.js +++ b/client/src/social-config/firebaseConfig.js @@ -3,14 +3,14 @@ import * as firebase from 'firebase'; // const settings = { timestampsInSnapshots: true }; const firebaseConfig = { - apiKey: 'AIzaSyDE8pABtUCH2kzGsN-htLQY8yDednivRiQ', - authDomain: 'ocean-d27d7.firebaseapp.com', - databaseURL: 'https://ocean-d27d7.firebaseio.com', - projectId: 'ocean-d27d7', - storageBucket: 'ocean-d27d7.appspot.com', - messagingSenderId: '732932278090', - appId: '1:732932278090:web:2f6389b5472052698663b3', - measurementId: 'G-3X88ZLXK9L', + apiKey: 'AIzaSyAIOjdj3MF7tlqCpU435u3ChU7E9t7FpoM', + authDomain: 'hyf-project-48ef4.firebaseapp.com', + databaseURL: 'https://hyf-project-48ef4.firebaseio.com', + projectId: 'hyf-project-48ef4', + storageBucket: 'hyf-project-48ef4.appspot.com', + messagingSenderId: '111507724756', + appId: '1:111507724756:web:a6d88666bc9fad05239e41', + measurementId: 'G-ZN6XZ9FDZE', }; // Could not remove this into another file it breaks the code diff --git a/routes/api/users.js b/routes/api/users.js index e00be42..cecdde4 100755 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -83,8 +83,24 @@ router.post('/facebook', async (req, res) => { try { let user = await User.findOne({ email }); + if (user) { - return res.status(400).json({ errors: [{ msg: 'User already exists' }] }); + const isMatch = await bcrypt.compare(password, user.password); + + if (!isMatch) { + return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] }); + } + + const payload = { + user: { + id: user.id, + }, + }; + + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { + if (err) throw err; + res.json({ token }); + }); } user = new User({ From e69c0eb8cddc7f1bc282ecda87c8c50521d22245 Mon Sep 17 00:00:00 2001 From: 20twa07 <48910851+20twa07@users.noreply.github.com> Date: Sat, 30 Nov 2019 16:30:56 +0100 Subject: [PATCH 35/76] add GoogleAuth --- client/src/actions/auth.js | 60 ++++++++++++++++++++++ client/src/components/auth/GoogleAuth.js | 37 +++++++++++++ client/src/components/auth/Login.js | 3 ++ client/src/components/routing/Routes.js | 2 + client/src/social-config/firebaseConfig.js | 16 +++--- routes/api/auth.js | 13 +++++ routes/api/users.js | 60 ++++++++++++++++++++++ 7 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 client/src/components/auth/GoogleAuth.js diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index 3cec43b..dec7dba 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -54,6 +54,26 @@ export const loadSocialUser = () => async dispatch => { }); } }; + +// Load Social User +export const loadSocialUserGoogle = () => async dispatch => { + if (localStorage.token) { + setAuthToken(localStorage.token); + } + console.log(localStorage.token); + try { + const res = await axios.get('/api/auth/google'); + console.log(res.data); + dispatch({ + type: SOCIAL_USER_LOADED, + payload: res.data, + }); + } catch (err) { + dispatch({ + type: AUTH_ERROR, + }); + } +}; // Register User export const register = ({ name, email, password }) => async dispatch => { const config = { @@ -193,6 +213,46 @@ export const handleLogin = () => async dispatch => { } }; +// Facebook Register User +export const handleLoginGoogle = () => async dispatch => { + const provider = new firebase.auth.GoogleAuthProvider(); + const result = await firebase.auth().signInWithPopup(provider); + console.log(result); + // const _id = result.user.uid; + let name = result.user.displayName; + const avatar = result.user.providerData[0].photoURL; + // const date = result.user.metadata.creationTime; + const email = result.user.email; + const password = result.user.uid; + + const config = { + headers: { + 'Content-Type': 'application/json', + }, + }; + console.log('pass' + password); + const body = JSON.stringify({ name, email, avatar, password }); + + try { + const res = await axios.post('/api/users/google', body, config); + dispatch({ + type: SOCIAL_SUCCESS, + payload: res.data, + }); + dispatch(loadSocialUserGoogle()); + } catch (err) { + const errors = err.response.data.errors; + + if (errors) { + errors.forEach(error => dispatch(setAlert(error.msg, 'danger'))); + } + + dispatch({ + type: REGISTER_FAIL, + }); + } +}; + // Logout / Clear profile export const logout = () => dispatch => { dispatch({ type: CLEAR_PROFILE }); diff --git a/client/src/components/auth/GoogleAuth.js b/client/src/components/auth/GoogleAuth.js new file mode 100644 index 0000000..440b86d --- /dev/null +++ b/client/src/components/auth/GoogleAuth.js @@ -0,0 +1,37 @@ +import React from 'react'; +import firebase from 'firebase/app'; +import 'firebase/auth'; +import socialConfig from '../../social-config/firebaseConfig'; +import { handleLoginGoogle } from '../../actions/auth'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { Redirect } from 'react-router-dom'; + +if (!firebase.apps.length) { + firebase.initializeApp(socialConfig); +} + +const GoogleAuth = ({ handleLoginGoogle, isAuthenticated, accessToken }) => { + if (isAuthenticated) { + return ; + } + return ( +
+ +
+ ); +}; + +GoogleAuth.propTypes = { + handleLoginGoogle: PropTypes.func.isRequired, + isAuthenticated: PropTypes.bool, +}; + +const mapStateToProps = state => ({ + isAuthenticated: state.auth.isAuthenticated, + accessToken: state.auth.token, +}); + +export default connect(mapStateToProps, { handleLoginGoogle })(GoogleAuth); diff --git a/client/src/components/auth/Login.js b/client/src/components/auth/Login.js index 5f3ffc5..07956fd 100755 --- a/client/src/components/auth/Login.js +++ b/client/src/components/auth/Login.js @@ -65,6 +65,9 @@ const Login = ({ login, isAuthenticated }) => { Facebook + + Google + ); }; diff --git a/client/src/components/routing/Routes.js b/client/src/components/routing/Routes.js index d56000f..0e617f9 100755 --- a/client/src/components/routing/Routes.js +++ b/client/src/components/routing/Routes.js @@ -15,6 +15,7 @@ import Post from '../post/Post'; import NotFound from '../layout/NotFound'; import PrivateRoute from '../routing/PrivateRoute'; import FacebookAuth from '../auth/FacebookAuth'; +import GoogleAuth from '../auth/GoogleAuth'; const Routes = () => { return ( @@ -24,6 +25,7 @@ const Routes = () => { + diff --git a/client/src/social-config/firebaseConfig.js b/client/src/social-config/firebaseConfig.js index 37cc78d..3b69c75 100644 --- a/client/src/social-config/firebaseConfig.js +++ b/client/src/social-config/firebaseConfig.js @@ -3,14 +3,14 @@ import * as firebase from 'firebase'; // const settings = { timestampsInSnapshots: true }; const firebaseConfig = { - apiKey: 'AIzaSyAIOjdj3MF7tlqCpU435u3ChU7E9t7FpoM', - authDomain: 'hyf-project-48ef4.firebaseapp.com', - databaseURL: 'https://hyf-project-48ef4.firebaseio.com', - projectId: 'hyf-project-48ef4', - storageBucket: 'hyf-project-48ef4.appspot.com', - messagingSenderId: '111507724756', - appId: '1:111507724756:web:a6d88666bc9fad05239e41', - measurementId: 'G-ZN6XZ9FDZE', + apiKey: 'AIzaSyDE8pABtUCH2kzGsN-htLQY8yDednivRiQ', + authDomain: 'ocean-d27d7.firebaseapp.com', + databaseURL: 'https://ocean-d27d7.firebaseio.com', + projectId: 'ocean-d27d7', + storageBucket: 'ocean-d27d7.appspot.com', + messagingSenderId: '732932278090', + appId: '1:732932278090:web:2f6389b5472052698663b3', + measurementId: 'G-3X88ZLXK9L', }; // Could not remove this into another file it breaks the code diff --git a/routes/api/auth.js b/routes/api/auth.js index 7756b2a..fef42ae 100755 --- a/routes/api/auth.js +++ b/routes/api/auth.js @@ -34,6 +34,19 @@ router.get('/facebook', auth, async (req, res) => { } }); +// @route GET api/auth/google +// @desc Load user +// @access Public +router.get('/google', auth, async (req, res) => { + try { + const user = await User.findById(req.user.id).select('-password'); + res.json(user); + } catch (err) { + console.error(err.message); + res.status(500).send('Server Error'); + } +}); + // @route POST api/auth // @desc Authenticate user & get token // @access Public diff --git a/routes/api/users.js b/routes/api/users.js index cecdde4..8d57f95 100755 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -131,4 +131,64 @@ router.post('/facebook', async (req, res) => { } }); +// @route POST api/users/google +// @desc Register user +// @access Public +router.post('/google', async (req, res) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + const { name, email, avatar, password } = req.body; + + try { + let user = await User.findOne({ email }); + + if (user) { + const isMatch = await bcrypt.compare(password, user.password); + + if (!isMatch) { + return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] }); + } + + const payload = { + user: { + id: user.id, + }, + }; + + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { + if (err) throw err; + res.json({ token }); + }); + } + + user = new User({ + name, + email, + avatar, + password, + }); + const salt = await bcrypt.genSalt(10); + + user.password = await bcrypt.hash(password, salt); + + await user.save(); + + const payload = { + user: { + id: user.id, + }, + }; + + jwt.sign(payload, config.get('jwtSecret'), { expiresIn: 360000 }, (err, token) => { + if (err) throw err; + res.json({ token }); + }); + } catch (err) { + console.error(err.message); + res.status(500).send('Server error'); + } +}); + module.exports = router; From f0663fee7e5632a476b987a83f8a454d7011d789 Mon Sep 17 00:00:00 2001 From: Gul Date: Sat, 30 Nov 2019 19:51:18 +0100 Subject: [PATCH 36/76] Combine facebook and google login in Login.js --- client/src/actions/auth.js | 8 +-- client/src/components/auth/FacebookAuth.js | 64 +++++++++--------- client/src/components/auth/GoogleAuth.js | 64 +++++++++--------- client/src/components/auth/Login.js | 77 ++++++++++++++-------- client/src/social-config/firebaseConfig.js | 18 +++-- config/default.json | 8 +-- 6 files changed, 130 insertions(+), 109 deletions(-) diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index dec7dba..d76c393 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -139,7 +139,7 @@ export const login = (email, password) => async dispatch => { } }; -// Facebook Login User +// Any Social Login User export const handleSocialLogin = (email, password) => async dispatch => { await firebase.auth().signInWithEmailAndPassword(email, password); @@ -174,7 +174,7 @@ export const handleSocialLogin = (email, password) => async dispatch => { }; // Facebook Register User -export const handleLogin = () => async dispatch => { +export const handleLoginFacebook = () => async dispatch => { const provider = new firebase.auth.FacebookAuthProvider(); const result = await firebase.auth().signInWithPopup(provider); console.log(result); @@ -213,7 +213,7 @@ export const handleLogin = () => async dispatch => { } }; -// Facebook Register User +// Google Register User export const handleLoginGoogle = () => async dispatch => { const provider = new firebase.auth.GoogleAuthProvider(); const result = await firebase.auth().signInWithPopup(provider); @@ -230,7 +230,7 @@ export const handleLoginGoogle = () => async dispatch => { 'Content-Type': 'application/json', }, }; - console.log('pass' + password); + const body = JSON.stringify({ name, email, avatar, password }); try { diff --git a/client/src/components/auth/FacebookAuth.js b/client/src/components/auth/FacebookAuth.js index 8b817b4..f2384bd 100644 --- a/client/src/components/auth/FacebookAuth.js +++ b/client/src/components/auth/FacebookAuth.js @@ -1,37 +1,37 @@ -import React from 'react'; -import firebase from 'firebase/app'; -import 'firebase/auth'; -import socialConfig from '../../social-config/firebaseConfig'; -import { handleLogin } from '../../actions/auth'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import { Redirect } from 'react-router-dom'; +// import React from 'react'; +// import firebase from 'firebase/app'; +// import 'firebase/auth'; +// import socialConfig from '../../social-config/firebaseConfig'; +// import { handleLogin } from '../../actions/auth'; +// import { connect } from 'react-redux'; +// import PropTypes from 'prop-types'; +// import { Redirect } from 'react-router-dom'; -if (!firebase.apps.length) { - firebase.initializeApp(socialConfig); -} +// if (!firebase.apps.length) { +// firebase.initializeApp(socialConfig); +// } -const FacebookAuth = ({ handleLogin, isAuthenticated, accessToken }) => { - if (isAuthenticated) { - return ; - } - return ( -
- -
- ); -}; +// const FacebookAuth = ({ handleLogin, isAuthenticated, accessToken }) => { +// if (isAuthenticated) { +// return ; +// } +// return ( +//
+// +//
+// ); +// }; -FacebookAuth.propTypes = { - handleLogin: PropTypes.func.isRequired, - isAuthenticated: PropTypes.bool, -}; +// FacebookAuth.propTypes = { +// handleLogin: PropTypes.func.isRequired, +// isAuthenticated: PropTypes.bool, +// }; -const mapStateToProps = state => ({ - isAuthenticated: state.auth.isAuthenticated, - accessToken: state.auth.token, -}); +// const mapStateToProps = state => ({ +// isAuthenticated: state.auth.isAuthenticated, +// accessToken: state.auth.token, +// }); -export default connect(mapStateToProps, { handleLogin })(FacebookAuth); +// export default connect(mapStateToProps, { handleLogin })(FacebookAuth); diff --git a/client/src/components/auth/GoogleAuth.js b/client/src/components/auth/GoogleAuth.js index 440b86d..9174d90 100644 --- a/client/src/components/auth/GoogleAuth.js +++ b/client/src/components/auth/GoogleAuth.js @@ -1,37 +1,37 @@ -import React from 'react'; -import firebase from 'firebase/app'; -import 'firebase/auth'; -import socialConfig from '../../social-config/firebaseConfig'; -import { handleLoginGoogle } from '../../actions/auth'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import { Redirect } from 'react-router-dom'; +// import React from 'react'; +// import firebase from 'firebase/app'; +// import 'firebase/auth'; +// import socialConfig from '../../social-config/firebaseConfig'; +// import { handleLoginGoogle } from '../../actions/auth'; +// import { connect } from 'react-redux'; +// import PropTypes from 'prop-types'; +// import { Redirect } from 'react-router-dom'; -if (!firebase.apps.length) { - firebase.initializeApp(socialConfig); -} +// if (!firebase.apps.length) { +// firebase.initializeApp(socialConfig); +// } -const GoogleAuth = ({ handleLoginGoogle, isAuthenticated, accessToken }) => { - if (isAuthenticated) { - return ; - } - return ( -
- -
- ); -}; +// const GoogleAuth = ({ handleLoginGoogle, isAuthenticated, accessToken }) => { +// if (isAuthenticated) { +// return ; +// } +// return ( +//
+// +//
+// ); +// }; -GoogleAuth.propTypes = { - handleLoginGoogle: PropTypes.func.isRequired, - isAuthenticated: PropTypes.bool, -}; +// GoogleAuth.propTypes = { +// handleLoginGoogle: PropTypes.func.isRequired, +// isAuthenticated: PropTypes.bool, +// }; -const mapStateToProps = state => ({ - isAuthenticated: state.auth.isAuthenticated, - accessToken: state.auth.token, -}); +// const mapStateToProps = state => ({ +// isAuthenticated: state.auth.isAuthenticated, +// accessToken: state.auth.token, +// }); -export default connect(mapStateToProps, { handleLoginGoogle })(GoogleAuth); +// export default connect(mapStateToProps, { handleLoginGoogle })(GoogleAuth); diff --git a/client/src/components/auth/Login.js b/client/src/components/auth/Login.js index 07956fd..3a96665 100755 --- a/client/src/components/auth/Login.js +++ b/client/src/components/auth/Login.js @@ -1,14 +1,22 @@ import React, { Fragment, useState } from 'react'; import { Link, Redirect } from 'react-router-dom'; import { connect } from 'react-redux'; -import { login, handleSocialLogin } from '../../actions/auth'; +import { + login, + handleLoginFacebook, + handleLoginGoogle, + handleSocialLogin, +} from '../../actions/auth'; import PropTypes from 'prop-types'; +import firebase from 'firebase/app'; +import 'firebase/auth'; +import firebaseConfig from '../../social-config/firebaseConfig'; -// if I remove import firebaseApp somehow login button doesn't work. Is it because I initialize firebase in firebaseConfig and you have to import it somewhere? -// import firebaseApp from '../../social-config/firebaseConfig'; -// import firebase from 'firebase'; +const Login = ({ login, isAuthenticated, handleLoginFacebook, handleLoginGoogle, accessToken }) => { + if (!firebase.apps.length) { + firebase.initializeApp(firebaseConfig); + } -const Login = ({ login, isAuthenticated }) => { const [formData, setFormData] = useState({ email: '', password: '', @@ -26,59 +34,74 @@ const Login = ({ login, isAuthenticated }) => { // Redirect if logged in if (isAuthenticated) { - return ; + return ; } return ( -

Sign In

-

- Sign Into Your Account +

Sign In

+

+ Sign Into Your Account

-
onSubmit(e)}> -
+ onSubmit(e)}> +
onChange(e)} required />
-
+
onChange(e)} - minLength='6' + minLength="6" />
- + -

- Don't have an account? Sign Up +

+ Don't have an account? Sign Up

- + {/* Facebook - - + */} + + {/* Google - + */} + ); }; login.propTypes = { login: PropTypes.func.isRequired, + handleLoginFacebook: PropTypes.func.isRequired, + handleLoginGoogle: PropTypes.func.isRequired, + handleSocialLogin: PropTypes.func.isRequired, isAuthenticated: PropTypes.bool, }; const mapStateToProps = state => ({ isAuthenticated: state.auth.isAuthenticated, + accessToken: state.auth.token, }); -export default connect(mapStateToProps, { login })(Login); +export default connect(mapStateToProps, { + login, + handleLoginFacebook, + handleLoginGoogle, + handleSocialLogin, +})(Login); diff --git a/client/src/social-config/firebaseConfig.js b/client/src/social-config/firebaseConfig.js index 3b69c75..dd3a197 100644 --- a/client/src/social-config/firebaseConfig.js +++ b/client/src/social-config/firebaseConfig.js @@ -1,16 +1,14 @@ import * as firebase from 'firebase'; -// const settings = { timestampsInSnapshots: true }; - const firebaseConfig = { - apiKey: 'AIzaSyDE8pABtUCH2kzGsN-htLQY8yDednivRiQ', - authDomain: 'ocean-d27d7.firebaseapp.com', - databaseURL: 'https://ocean-d27d7.firebaseio.com', - projectId: 'ocean-d27d7', - storageBucket: 'ocean-d27d7.appspot.com', - messagingSenderId: '732932278090', - appId: '1:732932278090:web:2f6389b5472052698663b3', - measurementId: 'G-3X88ZLXK9L', + apiKey: '', + authDomain: '', + databaseURL: '', + projectId: '', + storageBucket: '', + messagingSenderId: '', + appId: '', + measurementId: '', }; // Could not remove this into another file it breaks the code diff --git a/config/default.json b/config/default.json index da28b5e..2bdf4d1 100755 --- a/config/default.json +++ b/config/default.json @@ -1,6 +1,6 @@ { - "mongoURI": "mongodb+srv://mergorgec:mergorgec@socialnetwork-5bjss.mongodb.net/test?retryWrites=true&w=majority", - "jwtSecret": "mysecrettoken", - "githubClientId": "d6bc2d7e856de04bbc4f", - "githubSecret": "1b1b8df16fc5a57148dab746dc892ce0de2668b2" + "mongoURI": "", + "jwtSecret": "", + "githubClientId": "", + "githubSecret": "" } From 7314bfce5e6e12789bf479f76586610c6876fc7e Mon Sep 17 00:00:00 2001 From: Laurelinduilwen Date: Sat, 30 Nov 2019 22:22:00 +0100 Subject: [PATCH 37/76] did some garbage collection --- .gitignore | 4 +- client/package-lock.json | 5 ++ client/package.json | 1 + client/src/components/auth/FacebookAuth.js | 37 ------------- client/src/components/auth/GoogleAuth.js | 37 ------------- client/src/components/auth/Login.js | 58 ++++++++++---------- client/src/components/layout/Landing.js | 64 ++++++++++++++++------ client/src/components/routing/Routes.js | 60 ++++++++++---------- 8 files changed, 113 insertions(+), 153 deletions(-) delete mode 100644 client/src/components/auth/FacebookAuth.js delete mode 100644 client/src/components/auth/GoogleAuth.js diff --git a/.gitignore b/.gitignore index 6122ccb..2304dc4 100755 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ node_modules/ client/node_modules/ config/production.json client/debug.log -config/default.json package-lock.json client/src/social-config/ +client/package-lock.json +client/src/social-config/firebaseConfig.js +config/default.json \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 5c0c6da..c30f347 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10410,6 +10410,11 @@ } } }, + "react-social-login-buttons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-social-login-buttons/-/react-social-login-buttons-3.0.0.tgz", + "integrity": "sha512-G9/qDbxWWmX1Y3Oq/jVI5pQF9kml2CI/+YerZfCcbiHgO59592xfUXRrbh38gUfNs0lj/pXAS4dZ99VK27yO9w==" + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", diff --git a/client/package.json b/client/package.json index 48305e2..b97c153 100644 --- a/client/package.json +++ b/client/package.json @@ -12,6 +12,7 @@ "react-redux": "^7.0.2", "react-router-dom": "^5.0.0", "react-scripts": "3.0.0", + "react-social-login-buttons": "^3.0.0", "redux": "^4.0.1", "redux-devtools-extension": "^2.13.8", "redux-thunk": "^2.3.0", diff --git a/client/src/components/auth/FacebookAuth.js b/client/src/components/auth/FacebookAuth.js deleted file mode 100644 index f2384bd..0000000 --- a/client/src/components/auth/FacebookAuth.js +++ /dev/null @@ -1,37 +0,0 @@ -// import React from 'react'; -// import firebase from 'firebase/app'; -// import 'firebase/auth'; -// import socialConfig from '../../social-config/firebaseConfig'; -// import { handleLogin } from '../../actions/auth'; -// import { connect } from 'react-redux'; -// import PropTypes from 'prop-types'; -// import { Redirect } from 'react-router-dom'; - -// if (!firebase.apps.length) { -// firebase.initializeApp(socialConfig); -// } - -// const FacebookAuth = ({ handleLogin, isAuthenticated, accessToken }) => { -// if (isAuthenticated) { -// return ; -// } -// return ( -//
-// -//
-// ); -// }; - -// FacebookAuth.propTypes = { -// handleLogin: PropTypes.func.isRequired, -// isAuthenticated: PropTypes.bool, -// }; - -// const mapStateToProps = state => ({ -// isAuthenticated: state.auth.isAuthenticated, -// accessToken: state.auth.token, -// }); - -// export default connect(mapStateToProps, { handleLogin })(FacebookAuth); diff --git a/client/src/components/auth/GoogleAuth.js b/client/src/components/auth/GoogleAuth.js deleted file mode 100644 index 9174d90..0000000 --- a/client/src/components/auth/GoogleAuth.js +++ /dev/null @@ -1,37 +0,0 @@ -// import React from 'react'; -// import firebase from 'firebase/app'; -// import 'firebase/auth'; -// import socialConfig from '../../social-config/firebaseConfig'; -// import { handleLoginGoogle } from '../../actions/auth'; -// import { connect } from 'react-redux'; -// import PropTypes from 'prop-types'; -// import { Redirect } from 'react-router-dom'; - -// if (!firebase.apps.length) { -// firebase.initializeApp(socialConfig); -// } - -// const GoogleAuth = ({ handleLoginGoogle, isAuthenticated, accessToken }) => { -// if (isAuthenticated) { -// return ; -// } -// return ( -//
-// -//
-// ); -// }; - -// GoogleAuth.propTypes = { -// handleLoginGoogle: PropTypes.func.isRequired, -// isAuthenticated: PropTypes.bool, -// }; - -// const mapStateToProps = state => ({ -// isAuthenticated: state.auth.isAuthenticated, -// accessToken: state.auth.token, -// }); - -// export default connect(mapStateToProps, { handleLoginGoogle })(GoogleAuth); diff --git a/client/src/components/auth/Login.js b/client/src/components/auth/Login.js index 3a96665..c1cbfa4 100755 --- a/client/src/components/auth/Login.js +++ b/client/src/components/auth/Login.js @@ -1,35 +1,40 @@ -import React, { Fragment, useState } from 'react'; -import { Link, Redirect } from 'react-router-dom'; -import { connect } from 'react-redux'; +import React, { Fragment, useState } from "react"; +import { Link, Redirect } from "react-router-dom"; +import { connect } from "react-redux"; import { login, handleLoginFacebook, - handleLoginGoogle, - handleSocialLogin, -} from '../../actions/auth'; -import PropTypes from 'prop-types'; -import firebase from 'firebase/app'; -import 'firebase/auth'; -import firebaseConfig from '../../social-config/firebaseConfig'; + handleLoginGoogle +} from "../../actions/auth"; +import PropTypes from "prop-types"; +import firebase from "firebase/app"; +import "firebase/auth"; +import firebaseConfig from "../../social-config/firebaseConfig"; -const Login = ({ login, isAuthenticated, handleLoginFacebook, handleLoginGoogle, accessToken }) => { +const Login = ({ + login, + isAuthenticated, + handleLoginFacebook, + handleLoginGoogle, + accessToken +}) => { if (!firebase.apps.length) { firebase.initializeApp(firebaseConfig); } const [formData, setFormData] = useState({ - email: '', - password: '', + email: "", + password: "" }); const { email, password } = formData; - const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value }); + const onChange = e => + setFormData({ ...formData, [e.target.name]: e.target.value }); const onSubmit = async e => { e.preventDefault(); login(email, password); - handleSocialLogin(email, password); }; // Redirect if logged in @@ -70,17 +75,14 @@ const Login = ({ login, isAuthenticated, handleLoginFacebook, handleLoginGoogle,

Don't have an account? Sign Up

- {/* - Facebook - */} - {/* - Google - */} - ); @@ -90,18 +92,16 @@ login.propTypes = { login: PropTypes.func.isRequired, handleLoginFacebook: PropTypes.func.isRequired, handleLoginGoogle: PropTypes.func.isRequired, - handleSocialLogin: PropTypes.func.isRequired, - isAuthenticated: PropTypes.bool, + isAuthenticated: PropTypes.bool }; const mapStateToProps = state => ({ isAuthenticated: state.auth.isAuthenticated, - accessToken: state.auth.token, + accessToken: state.auth.token }); export default connect(mapStateToProps, { login, handleLoginFacebook, - handleLoginGoogle, - handleSocialLogin, + handleLoginGoogle })(Login); diff --git a/client/src/components/layout/Landing.js b/client/src/components/layout/Landing.js index bb72af9..2486e8a 100755 --- a/client/src/components/layout/Landing.js +++ b/client/src/components/layout/Landing.js @@ -1,30 +1,54 @@ -import React from 'react'; -import { Link, Redirect } from 'react-router-dom'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; +import React from "react"; +import { Link, Redirect } from "react-router-dom"; +import { connect } from "react-redux"; +import PropTypes from "prop-types"; +import { handleLoginFacebook, handleLoginGoogle } from "../../actions/auth"; +import { + FacebookLoginButton, + GoogleLoginButton +} from "react-social-login-buttons"; +import firebase from "firebase/app"; +import "firebase/auth"; +import firebaseConfig from "../../social-config/firebaseConfig"; -const Landing = ({ isAuthenticated }) => { +const Landing = ({ + isAuthenticated, + handleLoginFacebook, + handleLoginGoogle, + accessToken +}) => { + if (!firebase.apps.length) { + firebase.initializeApp(firebaseConfig); + } if (isAuthenticated) { - return ; + return ; } return ( -
-
-
-

Developer Connector

-

+

+
+
+

Developer Connector

+

Create a developer profile/portfolio, share posts and get help from other developers

-
- +
+ Sign Up - + Login
+ handleLoginFacebook(accessToken)} + style={{ fontSize: "1rem", width: "13rem" }} + /> + handleLoginGoogle(accessToken)} + style={{ width: "13rem" }} + />
@@ -32,11 +56,17 @@ const Landing = ({ isAuthenticated }) => { }; Landing.propTypes = { - isAuthenticated: PropTypes.bool + isAuthenticated: PropTypes.bool, + handleLoginFacebook: PropTypes.func.isRequired, + handleLoginGoogle: PropTypes.func.isRequired }; const mapStateToProps = state => ({ - isAuthenticated: state.auth.isAuthenticated + isAuthenticated: state.auth.isAuthenticated, + accessToken: state.auth.token }); -export default connect(mapStateToProps)(Landing); +export default connect(mapStateToProps, { + handleLoginFacebook, + handleLoginGoogle +})(Landing); diff --git a/client/src/components/routing/Routes.js b/client/src/components/routing/Routes.js index 0e617f9..3a0145b 100755 --- a/client/src/components/routing/Routes.js +++ b/client/src/components/routing/Routes.js @@ -1,40 +1,36 @@ -import React from 'react'; -import { Route, Switch } from 'react-router-dom'; -import Register from '../auth/Register'; -import Login from '../auth/Login'; -import Alert from '../layout/Alert'; -import Dashboard from '../dashboard/Dashboard'; -import CreateProfile from '../profile-forms/CreateProfile'; -import EditProfile from '../profile-forms/EditProfile'; -import AddExperience from '../profile-forms/AddExperience'; -import AddEducation from '../profile-forms/AddEducation'; -import Profiles from '../profiles/Profiles'; -import Profile from '../profile/Profile'; -import Posts from '../posts/Posts'; -import Post from '../post/Post'; -import NotFound from '../layout/NotFound'; -import PrivateRoute from '../routing/PrivateRoute'; -import FacebookAuth from '../auth/FacebookAuth'; -import GoogleAuth from '../auth/GoogleAuth'; +import React from "react"; +import { Route, Switch } from "react-router-dom"; +import Register from "../auth/Register"; +import Login from "../auth/Login"; +import Alert from "../layout/Alert"; +import Dashboard from "../dashboard/Dashboard"; +import CreateProfile from "../profile-forms/CreateProfile"; +import EditProfile from "../profile-forms/EditProfile"; +import AddExperience from "../profile-forms/AddExperience"; +import AddEducation from "../profile-forms/AddEducation"; +import Profiles from "../profiles/Profiles"; +import Profile from "../profile/Profile"; +import Posts from "../posts/Posts"; +import Post from "../post/Post"; +import NotFound from "../layout/NotFound"; +import PrivateRoute from "../routing/PrivateRoute"; const Routes = () => { return ( -
+
- - - - - - - - - - - - - + + + + + + + + + + +
From 4402f731c1ca736f2389d2ecaace9c7afba8cc75 Mon Sep 17 00:00:00 2001 From: Salih Date: Mon, 2 Dec 2019 14:18:19 +0100 Subject: [PATCH 38/76] create endpoints for sending, accepting, cancelling friend request. Modfy User Model added 3 parts that includes User friends and request information --- models/User.js | 64 ++++++++- routes/api/profile.js | 306 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 307 insertions(+), 63 deletions(-) diff --git a/models/User.js b/models/User.js index d7386df..3963d1c 100755 --- a/models/User.js +++ b/models/User.js @@ -3,24 +3,76 @@ const mongoose = require('mongoose'); const UserSchema = new mongoose.Schema({ name: { type: String, - required: true + required: true, }, email: { type: String, required: true, - unique: true + unique: true, }, password: { type: String, - required: true + required: true, }, avatar: { - type: String + type: String, }, date: { type: Date, - default: Date.now - } + default: Date.now, + }, + sentRequest: [ + { + username: { + type: String, + default: '', + }, + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'users', + }, + date: { + type: Date, + default: Date.now, + }, + }, + ], + request: [ + { + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'users', + }, + username: { + type: String, + default: '', + }, + date: { + type: Date, + default: Date.now, + }, + }, + ], + friendsList: [ + { + friendId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'users', + }, + friendName: { + type: String, + default: '', + }, + date: { + type: Date, + default: Date.now, + }, + }, + ], + totalRequest: { + type: Number, + default: 0, + }, }); module.exports = User = mongoose.model('user', UserSchema); diff --git a/routes/api/profile.js b/routes/api/profile.js index d358454..a9bf229 100755 --- a/routes/api/profile.js +++ b/routes/api/profile.js @@ -14,10 +14,10 @@ const Post = require('../../models/Post'); // @access Private router.get('/me', auth, async (req, res) => { try { - const profile = await Profile.findOne({ user: req.user.id }).populate( - 'user', - ['name', 'avatar'] - ); + const profile = await Profile.findOne({ user: req.user.id }).populate('user', [ + 'name', + 'avatar', + ]); if (!profile) { return res.status(400).json({ msg: 'There is no profile for this user' }); @@ -43,8 +43,8 @@ router.post( .isEmpty(), check('skills', 'Skills is required') .not() - .isEmpty() - ] + .isEmpty(), + ], ], async (req, res) => { const errors = validationResult(req); @@ -64,7 +64,7 @@ router.post( facebook, twitter, instagram, - linkedin + linkedin, } = req.body; // Build profile object @@ -93,14 +93,14 @@ router.post( let profile = await Profile.findOneAndUpdate( { user: req.user.id }, { $set: profileFields }, - { new: true, upsert: true } + { new: true, upsert: true }, ); res.json(profile); } catch (err) { console.error(err.message); res.status(500).send('Server Error'); } - } + }, ); // @route GET api/profile @@ -122,7 +122,7 @@ router.get('/', async (req, res) => { router.get('/user/:user_id', async (req, res) => { try { const profile = await Profile.findOne({ - user: req.params.user_id + user: req.params.user_id, }).populate('user', ['name', 'avatar']); if (!profile) return res.status(400).json({ msg: 'Profile not found' }); @@ -172,8 +172,8 @@ router.put( .isEmpty(), check('from', 'From date is required') .not() - .isEmpty() - ] + .isEmpty(), + ], ], async (req, res) => { const errors = validationResult(req); @@ -181,15 +181,7 @@ router.put( return res.status(400).json({ errors: errors.array() }); } - const { - title, - company, - location, - from, - to, - current, - description - } = req.body; + const { title, company, location, from, to, current, description } = req.body; const newExp = { title, @@ -198,7 +190,7 @@ router.put( from, to, current, - description + description, }; try { @@ -213,7 +205,7 @@ router.put( console.error(err.message); res.status(500).send('Server Error'); } - } + }, ); // @route DELETE api/profile/experience/:exp_id @@ -246,20 +238,20 @@ router.delete('/experience/:exp_id', auth, async (req, res) => { // if i dont add .toString() it returns this weird mongoose coreArray and the ids are somehow objects and it still deletes anyway even if you put /experience/5 const removeIndex = expIds.indexOf(req.params.exp_id); if (removeIndex === -1) { - return res.status(500).json({ msg: "Server error" }); + return res.status(500).json({ msg: 'Server error' }); } else { // theses console logs helped me figure it out - console.log("expIds", expIds); - console.log("typeof expIds", typeof expIds); - console.log("req.params", req.params); - console.log("removed", expIds.indexOf(req.params.exp_id)); + console.log('expIds', expIds); + console.log('typeof expIds', typeof expIds); + console.log('req.params', req.params); + console.log('removed', expIds.indexOf(req.params.exp_id)); foundProfile.experience.splice(removeIndex, 1); await foundProfile.save(); return res.status(200).json(foundProfile); } } catch (error) { console.error(error); - return res.status(500).json({ msg: "Server error" }); + return res.status(500).json({ msg: 'Server error' }); } }); @@ -282,8 +274,8 @@ router.put( .isEmpty(), check('from', 'From date is required') .not() - .isEmpty() - ] + .isEmpty(), + ], ], async (req, res) => { const errors = validationResult(req); @@ -291,15 +283,7 @@ router.put( return res.status(400).json({ errors: errors.array() }); } - const { - school, - degree, - fieldofstudy, - from, - to, - current, - description - } = req.body; + const { school, degree, fieldofstudy, from, to, current, description } = req.body; const newEdu = { school, @@ -308,7 +292,7 @@ router.put( from, to, current, - description + description, }; try { @@ -323,20 +307,20 @@ router.put( console.error(err.message); res.status(500).send('Server Error'); } - } + }, ); // @route DELETE api/profile/education/:edu_id // @desc Delete education from profile // @access Private //router.delete('/education/:edu_id', auth, async (req, res) => { - //try { - //const profile = await Profile.findOne({ user: req.user.id }); +//try { +//const profile = await Profile.findOne({ user: req.user.id }); - // Get remove index - //const removeIndex = profile.education - //.map(item => item.id) - //.indexOf(req.params.edu_id); +// Get remove index +//const removeIndex = profile.education +//.map(item => item.id) +//.indexOf(req.params.edu_id); /* profile.education.splice(removeIndex, 1); @@ -350,14 +334,14 @@ router.put( }); */ -router.delete("/education/:edu_id", auth, async (req, res) => { +router.delete('/education/:edu_id', auth, async (req, res) => { try { const foundProfile = await Profile.findOne({ user: req.user.id }); const eduIds = foundProfile.education.map(edu => edu._id.toString()); // if i dont add .toString() it returns this weird mongoose coreArray and the ids are somehow objects and it still deletes anyway even if you put /education/5 const removeIndex = eduIds.indexOf(req.params.edu_id); if (removeIndex === -1) { - return res.status(500).json({ msg: "Server error" }); + return res.status(500).json({ msg: 'Server error' }); } else { // theses console logs helped me figure it out /* console.log("eduIds", eduIds); @@ -373,7 +357,7 @@ router.delete("/education/:edu_id", auth, async (req, res) => { } } catch (error) { console.error(error); - return res.status(500).json({ msg: "Server error" }); + return res.status(500).json({ msg: 'Server error' }); } }); // @route GET api/profile/github/:username @@ -382,13 +366,15 @@ router.delete("/education/:edu_id", auth, async (req, res) => { router.get('/github/:username', (req, res) => { try { const options = { - uri: encodeURI(`https://api.github.com/users/${ - req.params.username - }/repos?per_page=5&sort=created:asc&client_id=${config.get( - 'githubClientId' - )}&client_secret=${config.get('githubSecret')}`), + uri: encodeURI( + `https://api.github.com/users/${ + req.params.username + }/repos?per_page=5&sort=created:asc&client_id=${config.get( + 'githubClientId', + )}&client_secret=${config.get('githubSecret')}`, + ), method: 'GET', - headers: { 'user-agent': 'node.js' } + headers: { 'user-agent': 'node.js' }, }; request(options, (error, response, body) => { @@ -406,4 +392,210 @@ router.get('/github/:username', (req, res) => { } }); +/// Friend Requests + +// Sender >>> Receiver + +// @route POST api/profile/friend/:id(receiver user id) +// @desc Send a friend request to a user +// @access Private + +router.post('/friend/:id', auth, async (req, res) => { + try { + const receiver = await User.findById(req.params.id).select('-password'); + const sender = await User.findById(req.user.id).select('-password'); + + // Check receiver database whether the sender has sent friend request to receiver or not + const isRequested = receiver.request.find(reques => reques.userId.toString() === req.user.id); + // Check receiver database whether the sender is already friend or not + const isFriend = receiver.friendsList.find( + friend => friend.friendId.toString() === req.user.id, + ); + // Check sender database whether the sender has sent friend request to receiver or not + const isSentRequest = sender.sentRequest.find( + reques => reques.userId.toString() === req.params.id, + ); + + if (!isRequested && !isSentRequest && !isFriend) { + // Add to sentRequest array in the sender database + sender.sentRequest.push({ + userId: req.params.id, + username: receiver.name, + }); + + await sender.save(); + // Add to request array in the receiver database + receiver.request.push({ + userId: req.user.id, + username: sender.name, + }); + // Increment total request in the receiver database + receiver.totalRequest += 1; + + await receiver.save(); + return res.json({ msg: `Your friend request is successfully sent to ${receiver.name}` }); // @Todo + } + + return res.status(500).json({ msg: 'Server error' }); + } catch (err) { + console.error(err.message); + res.status(500).send('Server Error'); + } +}); + +// Receiver >>> Sender +// @route PUT api/profile/friend/:senderId +// @desc Accept a friend request +// @access Private + +router.put('/friend/:senderId', auth, async (req, res) => { + try { + const receiver = await User.findById(req.user.id).select('-password'); + + const sender = await User.findById(req.params.senderId).select('-password'); + + const isFriend = receiver.friendsList.find( + friend => friend.friendId.toString() === req.params.senderId, + ); + const isFriendListOnSender = sender.friendsList.find( + friend => friend.friendId.toString() === req.user.id, + ); + + // Receiver must own request from sender in his database + const isRequested = receiver.request.find( + reques => reques.userId.toString() === req.params.senderId, + ); + //receiver in friendListinde senderin userIdsi yoksa, bi zahmet ekle + // senderin friendListinde receiverin yoksa senderin friendListine ekle + if (!isFriend && !isFriendListOnSender && isRequested) { + // Add to Sender info to friendList array in the receiver database + receiver.friendsList.push({ + friendId: req.params.senderId, + friendName: sender.name, + }); + // Remove sender info from Receiver request database because they are going to be friend + const getSenderId = receiver.request + .map(reques => reques.userId.toString()) + .indexOf(req.params.senderId); + receiver.request.splice(getSenderId, 1); + + receiver.totalRequest -= 1; + await receiver.save(); + // Add to Receiver info to friendList array in the sender database + sender.friendsList.push({ + friendId: req.user.id, + friendName: receiver.name, + }); + // Remove Receiver info from Sender senrRequest database because they are going to be friend + const getReceiverId = sender.sentRequest + .map(reques => reques.userId.toString()) + .indexOf(req.user.id); + + sender.sentRequest.splice(getReceiverId, 1); + + await sender.save(); + return res.json({ msg: `You have accepted ${sender.name} as a friend` }); // @Todo + } + + return res.status(500).json({ msg: 'Server error' }); + } catch (err) { + console.error(err.message); + res.status(500).send('Server Error'); + } +}); + +// Receiver >>> Sender +// @route PATCH api/profile/friend/:senderId +// @desc Cancel friend request +// @access Private + +router.patch('/friend/:senderId', auth, async (req, res) => { + try { + const receiver = await User.findById(req.user.id).select('-password'); + const sender = await User.findById(req.params.senderId).select('-password'); + + // Receiver must own request from sender in his database + const isRequested = receiver.request.find( + reques => reques.userId.toString() === req.params.senderId, + ); + + // Sender must own receiver in his sentRequest database + const isSentRequest = sender.sentRequest.find( + reques => reques.userId.toString() === req.user.id, + ); + + if (isRequested && isSentRequest) { + // Remove sender request from receiver request database + const getSenderId = receiver.request + .map(reques => reques.userId.toString()) + .indexOf(req.params.senderId); + receiver.request.splice(getSenderId, 1); + // Decrement total request of receiver + receiver.totalRequest -= 1; + await receiver.save(); + + // Remove sender request from sender sentRequest database + + const getReceiverId = sender.sentRequest + .map(reques => reques.userId.toString()) + .indexOf(req.user.id); + + sender.sentRequest.splice(getReceiverId, 1); + await sender.save(); + return res.json({ msg: `You have rejected ${sender.name} friend request` }); // @Todo + } + return res.status(500).json({ msg: 'Server error' }); + } catch (err) { + console.error(err.message); + res.status(500).send('Server Error'); + } +}); + +// Receiver >>> Sender +// @route DELETE api/profile/friend/:senderId +// @desc Break up the friendship +// @access Private + +router.delete('/friend/:senderId', auth, async (req, res) => { + try { + const receiver = await User.findById(req.user.id).select('-password'); + + const sender = await User.findById(req.params.senderId).select('-password'); + + // Check they are friends of each other + + const isFriend = receiver.friendsList.find( + friend => friend.friendId.toString() === req.params.senderId, + ); + const isFriendListOnSender = sender.friendsList.find( + friend => friend.friendId.toString() === req.user.id, + ); + + if (isFriend && isFriendListOnSender) { + // remove Sender Info from receiver friendList database. They are not friend right now + const getSenderId = receiver.friendsList + .map(friend => friend.friendId.toString()) + .indexOf(req.params.senderId); + + receiver.friendsList.splice(getSenderId, 1); + await receiver.save(); + + // remove receiver Info from sender friendList database. They are not friend right now + const getReceiverId = sender.friendsList + .map(friend => friend.friendId.toString()) + .indexOf(req.user.id); + + sender.friendsList.splice(getReceiverId, 1); + + await sender.save(); + return res.json({ msg: `You are not a friend with ${sender.name} ` }); // @Todo + } + + return res.status(500).json({ msg: 'Server error' }); + } catch (err) { + console.error(err.message); + res.status(500).send('Server Error'); + } +}); + module.exports = router; From dfce9386dbc6fffd8c38f87078dc8b7f21bb0daf Mon Sep 17 00:00:00 2001 From: Adham Elias Botrous <50738429+Adham-Elias-Botours@users.noreply.github.com> Date: Tue, 3 Dec 2019 11:36:15 +0100 Subject: [PATCH 39/76] add default.josn to git ignore --- .gitignore | 1 + config/default.json | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c03feab..0f57920 100755 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ client/debug.log config/default.json package-lock.json +config/default.json diff --git a/config/default.json b/config/default.json index 1c210b2..b14e1a2 100755 --- a/config/default.json +++ b/config/default.json @@ -1,6 +1,7 @@ { - "mongoURI": "", + "mongoURI": "mongodb+srv://adham123:adham123@devconnector-5ex4n.mongodb.net/test?retryWrites=true&w=majority", "jwtSecret": "secret", - "githubClientId": "", - "githubSecret": "" + "githubClientId": "4807401adfe078bad374", + "githubSecret": "6e069ffe38e4dd6fad7eb69c7d768e8b21dfad8d", + "local_url": "http://localhost:3000" } From 6cdba8cfadb8921dcc30f687fa56ec5ef734f872 Mon Sep 17 00:00:00 2001 From: Adham Elias Botrous <50738429+Adham-Elias-Botours@users.noreply.github.com> Date: Tue, 3 Dec 2019 13:02:08 +0100 Subject: [PATCH 40/76] add input, searchFiels state and filter. --- client/src/components/profiles/Profiles.js | 27 ++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/client/src/components/profiles/Profiles.js b/client/src/components/profiles/Profiles.js index 9bcac9b..bea3698 100755 --- a/client/src/components/profiles/Profiles.js +++ b/client/src/components/profiles/Profiles.js @@ -1,4 +1,4 @@ -import React, { Fragment, useEffect } from 'react'; +import React, { Fragment, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import Spinner from '../layout/Spinner'; @@ -10,18 +10,30 @@ const Profiles = ({ getProfiles, profile: { profiles, loading } }) => { getProfiles(); }, [getProfiles]); + const [searchField, setSearchField] = useState(''); + + const filteredProfiles = profiles.filter(profile => + profile.user.name.toLowerCase().includes(searchField.toLowerCase()) + ); + console.log(filteredProfiles); + return ( {loading ? ( ) : ( -

Developers

-

- Browse and connect with +

Developers

+

+ Browse and connect with developers

-
+ setSearchField(e.target.value)} + /> +
{profiles.length > 0 ? ( profiles.map(profile => ( @@ -45,7 +57,4 @@ const mapStateToProps = state => ({ profile: state.profile }); -export default connect( - mapStateToProps, - { getProfiles } -)(Profiles); +export default connect(mapStateToProps, { getProfiles })(Profiles); From 4e31ca55749e695ba19692ba022d35471cb1e308 Mon Sep 17 00:00:00 2001 From: Adham Elias Botrous <50738429+Adham-Elias-Botours@users.noreply.github.com> Date: Tue, 3 Dec 2019 13:02:34 +0100 Subject: [PATCH 41/76] render filteredProfiles --- client/src/components/profiles/Profiles.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/profiles/Profiles.js b/client/src/components/profiles/Profiles.js index bea3698..3303bc3 100755 --- a/client/src/components/profiles/Profiles.js +++ b/client/src/components/profiles/Profiles.js @@ -34,8 +34,8 @@ const Profiles = ({ getProfiles, profile: { profiles, loading } }) => { onChange={e => setSearchField(e.target.value)} />
- {profiles.length > 0 ? ( - profiles.map(profile => ( + {filteredProfiles.length > 0 ? ( + filteredProfiles.map(profile => ( )) ) : ( From 492eff232f001014e5646430f0a6c627a059bab2 Mon Sep 17 00:00:00 2001 From: Adham Elias Botrous <50738429+Adham-Elias-Botours@users.noreply.github.com> Date: Tue, 3 Dec 2019 21:29:21 +0100 Subject: [PATCH 42/76] add simple style to : input[type='search'] --- client/src/App.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/src/App.css b/client/src/App.css index 0957f54..be6338f 100755 --- a/client/src/App.css +++ b/client/src/App.css @@ -244,6 +244,14 @@ img { margin-top: 0.3rem; color: #888; } +input[type='search'] { + display: block; + width: 100%; + padding: 0.4rem; + font-size: 1.2rem; + border: 1px solid #ccc; + margin-bottom: 8px; +} .form input[type='text'], .form input[type='email'], From daeef7e303dcc4b71dd8f555e3125e25117ede58 Mon Sep 17 00:00:00 2001 From: 20twa07 <48910851+20twa07@users.noreply.github.com> Date: Wed, 4 Dec 2019 11:08:39 +0100 Subject: [PATCH 43/76] add Pagination component --- client/src/components/profiles/Pagination.js | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 client/src/components/profiles/Pagination.js diff --git a/client/src/components/profiles/Pagination.js b/client/src/components/profiles/Pagination.js new file mode 100644 index 0000000..bffa9fa --- /dev/null +++ b/client/src/components/profiles/Pagination.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +const Pagination = ({ profilesPerPage, totalProfiles, paginate }) => { + const pageNumbers = []; + + for (let i = 1; i <= Math.ceil(totalProfiles / profilesPerPage); i++) { + pageNumbers.push(i); + } + + return ( + + ); +}; + +export default Pagination; From e802ca4b5c2d4ce3643a3944822e946cc380abd7 Mon Sep 17 00:00:00 2001 From: 20twa07 <48910851+20twa07@users.noreply.github.com> Date: Wed, 4 Dec 2019 11:08:54 +0100 Subject: [PATCH 44/76] refactor for pagination --- client/src/components/profiles/Profiles.js | 41 +++++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/client/src/components/profiles/Profiles.js b/client/src/components/profiles/Profiles.js index 3303bc3..b45dacd 100755 --- a/client/src/components/profiles/Profiles.js +++ b/client/src/components/profiles/Profiles.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import Spinner from '../layout/Spinner'; import ProfileItem from './ProfileItem'; import { getProfiles } from '../../actions/profile'; +import Pagination from './Pagination'; const Profiles = ({ getProfiles, profile: { profiles, loading } }) => { useEffect(() => { @@ -13,31 +14,45 @@ const Profiles = ({ getProfiles, profile: { profiles, loading } }) => { const [searchField, setSearchField] = useState(''); const filteredProfiles = profiles.filter(profile => - profile.user.name.toLowerCase().includes(searchField.toLowerCase()) + profile.user.name.toLowerCase().includes(searchField.toLowerCase()), ); console.log(filteredProfiles); + const [currentPage, setCurrentPage] = useState(1); + const [profilesPerPage] = useState(5); + + // Get current posts + const indexOfLastProfile = currentPage * profilesPerPage; + const indexOfFirstProfile = indexOfLastProfile - profilesPerPage; + const currentProfiles = filteredProfiles.slice(indexOfFirstProfile, indexOfLastProfile); + console.log(currentProfiles); + + // Change page + const paginate = pageNumber => setCurrentPage(pageNumber); + return ( {loading ? ( ) : ( -

Developers

-

- Browse and connect with - developers +

Developers

+

+ Browse and connect with developers

setSearchField(e.target.value)} /> -
+ +
{filteredProfiles.length > 0 ? ( - filteredProfiles.map(profile => ( - - )) + currentProfiles.map(profile => ) ) : (

No profiles found...

)} @@ -50,11 +65,11 @@ const Profiles = ({ getProfiles, profile: { profiles, loading } }) => { Profiles.propTypes = { getProfiles: PropTypes.func.isRequired, - profile: PropTypes.object.isRequired + profile: PropTypes.object.isRequired, }; const mapStateToProps = state => ({ - profile: state.profile + profile: state.profile, }); export default connect(mapStateToProps, { getProfiles })(Profiles); From bb2da92f17f0f7ad056e49dae28b3c22d1dd760e Mon Sep 17 00:00:00 2001 From: 20twa07 <48910851+20twa07@users.noreply.github.com> Date: Wed, 4 Dec 2019 12:04:07 +0100 Subject: [PATCH 45/76] fix searching issue --- client/src/components/profiles/Profiles.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/components/profiles/Profiles.js b/client/src/components/profiles/Profiles.js index b45dacd..0940b77 100755 --- a/client/src/components/profiles/Profiles.js +++ b/client/src/components/profiles/Profiles.js @@ -43,7 +43,10 @@ const Profiles = ({ getProfiles, profile: { profiles, loading } }) => { setSearchField(e.target.value)} + onChange={e => { + setSearchField(e.target.value); + setCurrentPage(1); + }} /> Date: Wed, 4 Dec 2019 13:03:40 +0100 Subject: [PATCH 46/76] added scoket io etc to friend request --- client/package-lock.json | 233 ++++++++++++++++++ client/package.json | 1 + client/src/App.js | 2 +- client/src/actions/auth.js | 68 ++++- client/src/components/dashboard/Dashboard.js | 29 +-- client/src/components/profiles/ProfileItem.js | 48 +++- client/src/utils/socketClient.js | 2 + config/default.json | 4 +- package.json | 3 +- routes/api/profile.js | 2 +- server.js | 14 +- socketIO.js | 47 ++++ utils/deneme.js | 28 +++ utils/friend.js | 44 ++++ 14 files changed, 480 insertions(+), 45 deletions(-) create mode 100644 client/src/utils/socketClient.js create mode 100644 socketIO.js create mode 100644 utils/deneme.js create mode 100644 utils/friend.js diff --git a/client/package-lock.json b/client/package-lock.json index 6572e44..6f46875 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1565,6 +1565,11 @@ "resolved": "https://registry.npmjs.org/address/-/address-1.0.3.tgz", "integrity": "sha512-z55ocwKBRLryBs394Sm3ushTtBeg6VAeuku7utSoSnsJKvKcnXFIyC6vh27n3rXyxSgkJBBCAvyOn7gSUcTYjg==" }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, "ajv": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", @@ -1735,6 +1740,11 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -2126,6 +2136,11 @@ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, "bail": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", @@ -2191,6 +2206,11 @@ } } }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, "base64-js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", @@ -2209,6 +2229,14 @@ "tweetnacl": "^0.14.3" } }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2219,6 +2247,11 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, "bluebird": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", @@ -2518,6 +2551,11 @@ "caller-callsite": "^2.0.0" } }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2804,11 +2842,21 @@ "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz", "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==" }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, "compressible": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.16.tgz", @@ -3698,6 +3746,64 @@ "once": "^1.4.0" } }, + "engine.io-client": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.0.tgz", + "integrity": "sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", + "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, "enhanced-resolve": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", @@ -5351,6 +5457,26 @@ } } }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -7608,6 +7734,11 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -7921,6 +8052,22 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -10354,6 +10501,77 @@ "kind-of": "^3.2.0" } }, + "socket.io-client": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", + "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "engine.io-client": "~3.4.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, "sockjs": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", @@ -10958,6 +11176,11 @@ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -12025,6 +12248,11 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-1.3.1.tgz", "integrity": "sha512-tGkGJkN8XqCod7OT+EvGYK5Z4SfDQGD30zAa58OcnAa0RRWgzUEK72tkXhsX1FZd+rgnhRxFtmO+ihkp8LHSkw==" }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, "xregexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", @@ -12079,6 +12307,11 @@ "camelcase": "^5.0.0", "decamelize": "^1.2.0" } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" } } } diff --git a/client/package.json b/client/package.json index 5209eb0..7b67b02 100644 --- a/client/package.json +++ b/client/package.json @@ -14,6 +14,7 @@ "redux": "^4.0.1", "redux-devtools-extension": "^2.13.8", "redux-thunk": "^2.3.0", + "socket.io-client": "^2.3.0", "uuid": "^3.3.2" }, "scripts": { diff --git a/client/src/App.js b/client/src/App.js index 4c20465..a93ffac 100755 --- a/client/src/App.js +++ b/client/src/App.js @@ -27,7 +27,7 @@ const App = () => { - + diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js index 60adae0..93d25dd 100755 --- a/client/src/actions/auth.js +++ b/client/src/actions/auth.js @@ -8,9 +8,10 @@ import { LOGIN_SUCCESS, LOGIN_FAIL, LOGOUT, - CLEAR_PROFILE + CLEAR_PROFILE, } from './types'; import setAuthToken from '../utils/setAuthToken'; +import { socket } from '../utils/socketClient'; // Load User export const loadUser = () => async dispatch => { @@ -23,11 +24,12 @@ export const loadUser = () => async dispatch => { dispatch({ type: USER_LOADED, - payload: res.data + payload: res.data, }); + socket.emit('sendAuth', res.data._id); } catch (err) { dispatch({ - type: AUTH_ERROR + type: AUTH_ERROR, }); } }; @@ -36,8 +38,8 @@ export const loadUser = () => async dispatch => { export const register = ({ name, email, password }) => async dispatch => { const config = { headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }; const body = JSON.stringify({ name, email, password }); @@ -47,7 +49,7 @@ export const register = ({ name, email, password }) => async dispatch => { dispatch({ type: REGISTER_SUCCESS, - payload: res.data + payload: res.data, }); dispatch(loadUser()); @@ -59,7 +61,7 @@ export const register = ({ name, email, password }) => async dispatch => { } dispatch({ - type: REGISTER_FAIL + type: REGISTER_FAIL, }); } }; @@ -68,8 +70,8 @@ export const register = ({ name, email, password }) => async dispatch => { export const login = (email, password) => async dispatch => { const config = { headers: { - 'Content-Type': 'application/json' - } + 'Content-Type': 'application/json', + }, }; const body = JSON.stringify({ email, password }); @@ -79,7 +81,7 @@ export const login = (email, password) => async dispatch => { dispatch({ type: LOGIN_SUCCESS, - payload: res.data + payload: res.data, }); dispatch(loadUser()); @@ -91,7 +93,7 @@ export const login = (email, password) => async dispatch => { } dispatch({ - type: LOGIN_FAIL + type: LOGIN_FAIL, }); } }; @@ -101,3 +103,47 @@ export const logout = () => dispatch => { dispatch({ type: CLEAR_PROFILE }); dispatch({ type: LOGOUT }); }; + +// Friend Request +// Send Friend Request api/profile/friend/:id(receiver user id) +export const sendFriendRequest = id => async dispatch => { + try { + const res = await axios.post(`/api/profile/friend/${id}`); + dispatch(loadUser()); + dispatch(setAlert(res.data.msg, 'success')); + } catch (err) { + dispatch(setAlert(err.response.data.msg, 'danger')); + } +}; +// Accept Friend Request api/profile/friend/:senderId +export const acceptFriendRequest = id => async dispatch => { + try { + const res = await axios.put(`/api/profile/friend/${id}`); + dispatch(loadUser()); + dispatch(setAlert(res.data.msg, 'success')); + } catch (err) { + dispatch(setAlert(err.response.data.msg, 'danger')); + } +}; + +// Cancel Friend Request api/profile/friend/:senderId +export const cancelFriendRequest = id => async dispatch => { + try { + const res = await axios.patch(`/api/profile/friend/${id}`); + dispatch(loadUser()); + dispatch(setAlert(res.data.msg, 'success')); + } catch (err) { + dispatch(setAlert(err.response.data.msg, 'danger')); + } +}; + +// Remove Friend api/profile/friend/:senderId +export const removeFriend = id => async dispatch => { + try { + const res = await axios.delete(`/api/profile/friend/${id}`); + dispatch(loadUser()); + dispatch(setAlert(res.data.msg, 'success')); + } catch (err) { + dispatch(setAlert(err.response.data.msg, 'danger')); + } +}; diff --git a/client/src/components/dashboard/Dashboard.js b/client/src/components/dashboard/Dashboard.js index 7a4037a..6886a35 100755 --- a/client/src/components/dashboard/Dashboard.js +++ b/client/src/components/dashboard/Dashboard.js @@ -7,24 +7,28 @@ import DashboardActions from './DashboardActions'; import Experience from './Experience'; import Education from './Education'; import { getCurrentProfile, deleteAccount } from '../../actions/profile'; +import { socket } from './../../utils/socketClient'; const Dashboard = ({ getCurrentProfile, deleteAccount, auth: { user }, - profile: { profile, loading } + profile: { profile, loading }, }) => { useEffect(() => { getCurrentProfile(); + socket.on('newFriendRequest', data => { + console.log(data); + }); }, [getCurrentProfile]); return loading && profile === null ? ( ) : ( -

Dashboard

-

- Welcome {user && user.name} +

Dashboard

+

+ Welcome {user && user.name}

{profile !== null ? ( @@ -32,16 +36,16 @@ const Dashboard = ({ -
-
) : (

You have not yet setup a profile, please add some info

- + Create Profile
@@ -54,15 +58,12 @@ Dashboard.propTypes = { getCurrentProfile: PropTypes.func.isRequired, deleteAccount: PropTypes.func.isRequired, auth: PropTypes.object.isRequired, - profile: PropTypes.object.isRequired + profile: PropTypes.object.isRequired, }; const mapStateToProps = state => ({ auth: state.auth, - profile: state.profile + profile: state.profile, }); -export default connect( - mapStateToProps, - { getCurrentProfile, deleteAccount } -)(Dashboard); +export default connect(mapStateToProps, { getCurrentProfile, deleteAccount })(Dashboard); diff --git a/client/src/components/profiles/ProfileItem.js b/client/src/components/profiles/ProfileItem.js index d1bb1d6..e2dc5e2 100755 --- a/client/src/components/profiles/ProfileItem.js +++ b/client/src/components/profiles/ProfileItem.js @@ -1,33 +1,53 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; +import { sendFriendRequest } from '../../actions/auth'; +import { socket } from '../../utils/socketClient'; const ProfileItem = ({ + sendFriendRequest, + auth, profile: { user: { _id, name, avatar }, status, company, location, - skills - } + skills, + }, }) => { + const handleClick = e => { + // e.preventDefault(); + // sendFriendRequest(_id); + // send message + socket.emit('friendRequest', { + senderId: auth.user._id, + senderName: auth.user.name, + receiverId: _id, + receiverName: name, + }); + }; + return ( -
- +
+

{name}

{status} {company && at {company}}

-

{location && {location}}

- +

{location && {location}}

+ View Profile +
    {skills.slice(0, 4).map((skill, index) => ( -
  • - {skill} +
  • + {skill}
  • ))}
@@ -36,7 +56,15 @@ const ProfileItem = ({ }; ProfileItem.propTypes = { - profile: PropTypes.object.isRequired + sendFriendRequest: PropTypes.func.isRequired, + profile: PropTypes.object.isRequired, + auth: PropTypes.object.isRequired, }; -export default ProfileItem; +const mapStateToProps = state => ({ + auth: state.auth, +}); + +export default connect(mapStateToProps, { + sendFriendRequest, +})(ProfileItem); diff --git a/client/src/utils/socketClient.js b/client/src/utils/socketClient.js new file mode 100644 index 0000000..4b77533 --- /dev/null +++ b/client/src/utils/socketClient.js @@ -0,0 +1,2 @@ +import io from 'socket.io-client'; +export const socket = io('http://localhost:5000'); diff --git a/config/default.json b/config/default.json index 1c210b2..d689216 100755 --- a/config/default.json +++ b/config/default.json @@ -1,6 +1,6 @@ { - "mongoURI": "", + "mongoURI": "mongodb+srv://salih123:salih123@devconnector-tc7ou.mongodb.net/test?retryWrites=true&w=majority", "jwtSecret": "secret", "githubClientId": "", "githubSecret": "" -} +} \ No newline at end of file diff --git a/package.json b/package.json index 964b915..ed16aa0 100755 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "gravatar": "^1.8.0", "jsonwebtoken": "^8.5.1", "mongoose": "^5.7.5", - "request": "^2.88.0" + "request": "^2.88.0", + "socket.io": "^2.3.0" }, "devDependencies": { "concurrently": "^4.1.0", diff --git a/routes/api/profile.js b/routes/api/profile.js index a9bf229..10f8fce 100755 --- a/routes/api/profile.js +++ b/routes/api/profile.js @@ -436,7 +436,7 @@ router.post('/friend/:id', auth, async (req, res) => { return res.json({ msg: `Your friend request is successfully sent to ${receiver.name}` }); // @Todo } - return res.status(500).json({ msg: 'Server error' }); + return res.status(404).json({ msg: 'Not found' }); } catch (err) { console.error(err.message); res.status(500).send('Server Error'); diff --git a/server.js b/server.js index ed72f71..01ad66d 100755 --- a/server.js +++ b/server.js @@ -1,24 +1,26 @@ +const path = require('path'); const express = require('express'); const connectDB = require('./config/db'); -const path = require('path'); const app = express(); // Connect Database + connectDB(); // Init Middleware app.use(express.json({ extended: false })); // Define Routes + app.use('/api/users', require('./routes/api/users')); app.use('/api/auth', require('./routes/api/auth')); app.use('/api/profile', require('./routes/api/profile')); app.use('/api/posts', require('./routes/api/posts')); -// Serve static assets in production +//Serve static assets in production if (process.env.NODE_ENV === 'production') { - // Set static folder + //Set static folder app.use(express.static('client/build')); app.get('*', (req, res) => { @@ -26,6 +28,8 @@ if (process.env.NODE_ENV === 'production') { }); } -const PORT = process.env.PORT || 5000; +module.exports = app; +const server = require('./socketIO'); -app.listen(PORT, () => console.log(`Server started on port ${PORT}`)); +const PORT = process.env.PORT || 5000; +server.listen(PORT, () => console.log(`Server started on port ${PORT}`)); diff --git a/socketIO.js b/socketIO.js new file mode 100644 index 0000000..a9167d4 --- /dev/null +++ b/socketIO.js @@ -0,0 +1,47 @@ +const http = require('http'); +const socketio = require('socket.io'); +const app = require('./server'); +const server = http.createServer(app); +const io = socketio(server); +const { + addSocketClient, + removeSocketClient, + getSocketClient, + getReceiverSocketId, +} = require('./utils/friend'); + +io.on('connection', socket => { + console.log('New WebSocket connection', socket.id); + + socket.on('sendAuth', senderId => { + addSocketClient({ id: socket.id, senderId }); + }); + + socket.on('friendRequest', ({ senderName, senderId, receiverName, receiverId }) => { + const socketClient = getSocketClient(socket.id); + socketClient.senderName = senderName; + socketClient.senderId = senderId; + socketClient.receiverName = receiverName; + socketClient.receiverId = receiverId; + + const { socketClientId, error } = getReceiverSocketId(socketClient.receiverId); + console.log('status', socketClient, error); + + if (!error) { + io.to(socketClientId).emit('newFriendRequest', { + socketClientId, + senderId, + senderName, + receiverId, + receiverName, + }); + } + }); + + socket.on('disconnect', function() { + removeSocketClient(socket.id); + console.log('user disconnected'); + }); +}); + +module.exports = server; diff --git a/utils/deneme.js b/utils/deneme.js new file mode 100644 index 0000000..085eace --- /dev/null +++ b/utils/deneme.js @@ -0,0 +1,28 @@ +const obj = [ + { + id: '8Che_3fRd0ZzAZvkAAAE', + senderName: 'Salih', + senderId: '5dc464f7e3f7002e22da54ac', + receiverName: 'Salih', + receiverId: '5dc464f7e3f7002e22da54ac', + }, + { + id: '8Che_3fRd0ZzAZvkAAAE', + senderName: 'Salih', + senderId: '5dc464f7e3f7002e22da54ac', + receiverName: 'test', + receiverId: '5dc5edf78aeb2f1bc224db2b', + }, +]; + +const recId = '5dc5edf78aeb2f1bc224db2b'; + +const getReceiverSocket = () => { + const get = obj.find(el => { + return el.receiverId === recId; + }); + return get.id; +}; +const a = getReceiverSocket(); + +console.log(a); diff --git a/utils/friend.js b/utils/friend.js new file mode 100644 index 0000000..c1d3eb5 --- /dev/null +++ b/utils/friend.js @@ -0,0 +1,44 @@ +const socketClients = []; + +const addSocketClient = ({ id, senderName, senderId, receiverName, receiverId }) => { + // Store user + const socketClient = { id, senderName, senderId, receiverName, receiverId }; + + socketClients.push(socketClient); + return { socketClient }; +}; + +const removeSocketClient = id => { + const index = socketClients.findIndex(socket => socket.id === id); + + if (index !== -1) { + return socketClients.splice(index, 1)[0]; + } +}; + +const getSocketClient = id => socketClients.find(socket => socket.id === id); + +const getSocketClients = () => { + return socketClients.map(socket => { + return socket; + }); +}; + +const getReceiverSocketId = receiverId => { + try { + const obj = socketClients.find(socket => { + return socket.senderId === receiverId; + }); + return { socketClientId: obj.id }; + } catch (err) { + return { error: 'Not Online' }; + } +}; + +module.exports = { + addSocketClient, + removeSocketClient, + getSocketClient, + getReceiverSocketId, + getSocketClients, +}; From 92705f56bbe893100e623fe632d4f82ed45a8a91 Mon Sep 17 00:00:00 2001 From: ShyarK Date: Wed, 4 Dec 2019 13:22:02 +0100 Subject: [PATCH 47/76] add active class for pagination --- client/public/index.html | 6 ++++++ client/src/App.css | 6 +----- client/src/components/profiles/Pagination.js | 10 +++++----- client/src/components/profiles/Profiles.js | 18 +++++++++++------- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/client/public/index.html b/client/public/index.html index d9069be..1ab11b3 100755 --- a/client/public/index.html +++ b/client/public/index.html @@ -11,6 +11,12 @@ integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous" /> + Welcome To DevConnector diff --git a/client/src/App.css b/client/src/App.css index be6338f..74f3049 100755 --- a/client/src/App.css +++ b/client/src/App.css @@ -67,11 +67,7 @@ img { } .text-primary { - color: var(--primary-color); -} - -.text-dark { - color: var(--dark-color); + color: var(--primary-color) !important; } /* Padding */ diff --git a/client/src/components/profiles/Pagination.js b/client/src/components/profiles/Pagination.js index bffa9fa..162269d 100644 --- a/client/src/components/profiles/Pagination.js +++ b/client/src/components/profiles/Pagination.js @@ -1,7 +1,7 @@ import React from 'react'; import { Link } from 'react-router-dom'; -const Pagination = ({ profilesPerPage, totalProfiles, paginate }) => { +const Pagination = ({ profilesPerPage, totalProfiles, paginate, currentPage }) => { const pageNumbers = []; for (let i = 1; i <= Math.ceil(totalProfiles / profilesPerPage); i++) { @@ -10,10 +10,10 @@ const Pagination = ({ profilesPerPage, totalProfiles, paginate }) => { return (