From 6c15bbc4a39a26e3cc00eefb4636e4f41a8c845c Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Wed, 8 May 2019 16:28:41 +1000 Subject: [PATCH] Password change is working --- server/accounts.js | 56 ++++++++- server/index.js | 1 + src/actions/accounts.js | 8 ++ src/components/pages/home.jsx | 26 +++- src/components/panels/password_change.jsx | 137 ++++++++++++++++++++++ src/reducers/accounts.js | 13 +- 6 files changed, 234 insertions(+), 7 deletions(-) create mode 100644 src/components/panels/password_change.jsx diff --git a/server/accounts.js b/server/accounts.js index 9729a67..7f4934a 100644 --- a/server/accounts.js +++ b/server/accounts.js @@ -48,7 +48,7 @@ function signup(connection) { bcrypt.hash(fields.password, salt, (err, hash) => { if (err) throw err; - //generate a random number as a key + //generate a random number as a token let rand = Math.floor(Math.random() * 100000); //save the generated data to the signups table @@ -198,9 +198,61 @@ function logout(connection) { } } +function passwordChange(connection) { + return (req, res) => { + //formidable handles forms + let form = formidable.IncomingForm(); + + //parse form + form.parse(req, (err, fields) => { + if (err) throw err; + + //validate password, retype + if (!validateEmail(fields.email) || fields.password.length < 8 || fields.password !== fields.retype) { + res.write('

Invalid password change data

'); + res.end(); + return; + } + + //generate the new salt, hash + bcrypt.genSalt(11, (err, salt) => { + if (err) throw err; + bcrypt.hash(fields.password, salt, (err, hash) => { + if (err) throw err; + + let query = 'UPDATE accounts SET salt = ?, hash = ? WHERE email = ?;'; + connection.query(query, [salt, hash, fields.email], (err) => { + if (err) throw err; + + //clear all session data for this user (a 'feature') + let query = 'DELETE FROM sessions WHERE sessions.accountId IN (SELECT accounts.id FROM accounts WHERE email = ?);'; + connection.query(query, [fields.email], (err) => { + if (err) throw err; + + //create the new session + let rand = Math.floor(Math.random() * 100000); + + let query = 'INSERT INTO sessions (accountId, token) VALUES ((SELECT accounts.id FROM accounts WHERE email = ?), ?);'; + connection.query(query, [fields.email, rand], (err) => { + if (err) throw err; + + //send json containing the account info + res.status(200).json({ + token: rand + }); + }); + }); + }); + }); + }); + }); + } +} + module.exports = { signup: signup, verify: verify, login: login, - logout: logout + logout: logout, + passwordChange: passwordChange }; \ No newline at end of file diff --git a/server/index.js b/server/index.js index 92619a3..598bb22 100644 --- a/server/index.js +++ b/server/index.js @@ -20,6 +20,7 @@ app.post('/signup', accounts.signup(connection)); app.get('/verify', accounts.verify(connection)); app.post('/login', accounts.login(connection)); app.post('/logout', accounts.logout(connection)); +app.post('/passwordchange', accounts.passwordChange(connection)); //static directories app.use('/styles', express.static(path.resolve(__dirname + '/../public/styles')) ); diff --git a/src/actions/accounts.js b/src/actions/accounts.js index 6388305..1253bb3 100644 --- a/src/actions/accounts.js +++ b/src/actions/accounts.js @@ -1,5 +1,6 @@ export const LOGIN = 'LOGIN'; export const LOGOUT = 'LOGOUT'; +export const SESSIONCHANGE = 'SESSIONCHANGE'; export function login(id, email, username, token) { return { @@ -16,3 +17,10 @@ export function logout() { type: LOGOUT }; } + +export function sessionChange(token) { + return { + type: SESSIONCHANGE, + token: token + } +} \ No newline at end of file diff --git a/src/components/pages/home.jsx b/src/components/pages/home.jsx index d876fa6..0f767e1 100644 --- a/src/components/pages/home.jsx +++ b/src/components/pages/home.jsx @@ -7,28 +7,47 @@ import PropTypes from 'prop-types'; import Signup from '../panels/signup.jsx'; import Login from '../panels/login.jsx'; import Logout from '../panels/logout.jsx'; +import PasswordChange from '../panels/password_change.jsx'; class Home extends React.Component { constructor(props) { super(props); - this.state = {}; + this.state = { + changedPassword: false + }; } render() { - //well this is goofy + //DEBUGGING: well this is goofy let SidePanel; if (this.props.id) { SidePanel = () => { + let PasswordChangePanel; + + if (!this.state.changedPassword) { + PasswordChangePanel = () => { + return ( { this.setState({changedPassword: true}) }} />); + } + } else { + PasswordChangePanel = () => { + return (

Password changed!

); + } + } + return (

You are logged in.

+
); }; } else { SidePanel = () => { + if (this.state.changedPassword) { + this.setState({changedPassword: false}); + } return (
@@ -49,7 +68,8 @@ class Home extends React.Component { function mapStoreToProps(store) { return { - id: store.account.id + id: store.account.id, + token: store.account.token } } diff --git a/src/components/panels/password_change.jsx b/src/components/panels/password_change.jsx new file mode 100644 index 0000000..2ebfddd --- /dev/null +++ b/src/components/panels/password_change.jsx @@ -0,0 +1,137 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { sessionChange } from '../../actions/accounts.js'; + +class PasswordChange extends React.Component { + constructor(props) { + super(props); + this.state = { + password: '', + retype: '', + warning: '' + }; + } + + render() { + let warningStyle = { + display: this.state.warning.length > 0 ? 'flex' : 'none' + }; + + return ( +
+

Change Password

+ +
+

{this.state.warning}

+
+ +
this.submit(e)}> +
+ + +
+ +
+ + +
+ + +
+
+ ); + } + + submit(e) { + e.preventDefault(); + + if (!this.validateInput()) { + return; + } + + //build the XHR + let form = e.target; + let formData = new FormData(form); + let xhr = new XMLHttpRequest(); + + formData.append('email', this.props.email); + + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + let json = JSON.parse(xhr.responseText); + this.props.sessionChange(json.token); + } + + else if (xhr.status === 400) { + this.setWarning(xhr.responseText); + } + } + }; + + //send the XHR + xhr.open('POST', form.action, true); + xhr.send(formData); + + //DEBUGGING + if (this.props.onSubmit) { + this.props.onSubmit(); + } + } + + validateInput(e) { + if (this.state.password.length < 8) { + this.setWarning('Minimum password length is 8 characters'); + return false; + } + + if (this.state.password !== this.state.retype) { + this.setWarning('Passwords do not match'); + return false; + } + + return true; + } + + setWarning(s) { + this.setState({ + warning: s + }); + } + + clearInput() { + this.setState({ + password: '', + retype: '', + warning: '' + }); + } + + updatePassword(evt) { + this.setState({ + password: evt.target.value + }); + } + + updateRetype(evt) { + this.setState({ + retype: evt.target.value + }); + } +} + +function mapStoreToProps(store) { + return { + email: store.account.email + } +} + +function mapDispatchToProps(dispatch) { + return { + sessionChange: (token) => { dispatch(sessionChange(token)); } + } +} + +PasswordChange = connect(mapStoreToProps, mapDispatchToProps)(PasswordChange); + +export default PasswordChange; \ No newline at end of file diff --git a/src/reducers/accounts.js b/src/reducers/accounts.js index 58a038c..4739fa2 100644 --- a/src/reducers/accounts.js +++ b/src/reducers/accounts.js @@ -1,4 +1,4 @@ -import { LOGIN, LOGOUT } from "../actions/accounts.js"; +import { LOGIN, LOGOUT, SESSIONCHANGE } from "../actions/accounts.js"; const initialStore = { id: 0, @@ -9,7 +9,7 @@ const initialStore = { export function accountReducer(store = initialStore, action) { switch(action.type) { - case LOGIN: + case LOGIN: { let newStore = JSON.parse(JSON.stringify(initialStore)); newStore.id = action.id; @@ -18,10 +18,19 @@ export function accountReducer(store = initialStore, action) { newStore.token = action.token; return newStore; + } case LOGOUT: return initialStore; + case SESSIONCHANGE: { + let newStore = JSON.parse(JSON.stringify(store)); + + newStore.token = action.token; + + return newStore; + } + default: return store; }