diff --git a/client/constants.js b/client/constants.js index 77b3ff51..e9d79ac1 100644 --- a/client/constants.js +++ b/client/constants.js @@ -94,6 +94,7 @@ export const DETECT_INFINITE_LOOPS = 'DETECT_INFINITE_LOOPS'; export const RESET_INFINITE_LOOPS = 'RESET_INFINITE_LOOPS'; export const RESET_PASSWORD_INITIATE = 'RESET_PASSWORD_INITIATE'; +export const RESET_PASSWORD_RESET = 'RESET_PASSWORD_RESET'; // eventually, handle errors more specifically and better export const ERROR = 'ERROR'; diff --git a/client/modules/IDE/components/ResetPasswordForm.js b/client/modules/IDE/components/ResetPasswordForm.js index d4c7d81a..3efe16b3 100644 --- a/client/modules/IDE/components/ResetPasswordForm.js +++ b/client/modules/IDE/components/ResetPasswordForm.js @@ -13,12 +13,12 @@ function ResetPasswordForm(props) { {...email} />

- + ); } -ResetPassword.propTypes = { +ResetPasswordForm.propTypes = { fields: PropTypes.shape({ email: PropTypes.object.isRequired }).isRequired, @@ -27,6 +27,6 @@ ResetPassword.propTypes = { submitting: PropTypes.bool, invalid: PropTypes.bool, pristine: PropTypes.bool -} +}; export default ResetPasswordForm; diff --git a/client/modules/IDE/components/ResetPasswordView.js b/client/modules/IDE/components/ResetPasswordView.js index 18e5c3a7..2aa0df3d 100644 --- a/client/modules/IDE/components/ResetPasswordView.js +++ b/client/modules/IDE/components/ResetPasswordView.js @@ -1,29 +1,51 @@ -import React from 'react'; +import React, { PropTypes } from 'react'; import { Link } from 'react-router'; import * as UserActions from '../../User/actions'; import { bindActionCreators } from 'redux'; import { reduxForm } from 'redux-form'; import ResetPasswordForm from './ResetPasswordForm'; +import classNames from 'classnames'; class ResetPasswordView extends React.Component { + componentWillMount() { + this.props.resetPasswordReset(); + } + componentDidMount() { this.refs.resetPassword.focus(); } render() { + const resetPasswordClass = classNames({ + 'reset-password': true, + 'reset-password--submitted': this.props.user.passwordResetInitialized + }); return ( -
+

Reset Your Password

- Login - or - Sign up +

+ Your password reset email should arrive shortly. If you don't see it, check + in your spam folder as sometimes it can end up there. +

+

+ Login +  or  + Sign up +

Cancel
); } } +ResetPasswordView.propTypes = { + resetPasswordReset: PropTypes.func.isRequired, + user: PropTypes.shape({ + passwordResetInitialized: PropTypes.bool + }).isRequired, +}; + function mapStateToProps(state) { return { user: state.user @@ -37,13 +59,13 @@ function mapDispatchToProps(dispatch) { function validate(formProps) { const errors = {}; if (!formProps.email) { - errors.email = 'Please enter an email' + errors.email = 'Please enter an email'; } return errors; } export default reduxForm({ form: 'reset-password', - fields: 'email', + fields: ['email'], validate }, mapStateToProps, mapDispatchToProps)(ResetPasswordView); diff --git a/client/modules/User/actions.js b/client/modules/User/actions.js index 2ab47918..b6422d78 100644 --- a/client/modules/User/actions.js +++ b/client/modules/User/actions.js @@ -105,15 +105,23 @@ export function logoutUser() { }; } -export function initiatePasswordReset(formValues) { +export function initiateResetPassword(formValues) { return (dispatch) => { - axios.post(`${ROOT_URL}/reset-password`, formValues, { withCredentials: true}) - .then(response => { + axios.post(`${ROOT_URL}/reset-password`, formValues, { withCredentials: true }) + .then(() => { dispatch({ type: ActionTypes.RESET_PASSWORD_INITIATE }); }) - .catch(response => dispatch({ActionTypes.ERROR})); - } + .catch(response => dispatch({ + type: ActionTypes.ERROR, + message: response.data + })); + }; } +export function resetPasswordReset() { + return { + type: ActionTypes.RESET_PASSWORD_RESET + }; +} diff --git a/client/modules/User/reducers.js b/client/modules/User/reducers.js index c2f3f8ab..01b6a6fe 100644 --- a/client/modules/User/reducers.js +++ b/client/modules/User/reducers.js @@ -13,6 +13,8 @@ const user = (state = { authenticated: false }, action) => { return { authenticated: false }; + case ActionTypes.RESET_PASSWORD_INITIATE: + return Object.assign(state, {}, { resetPasswordInitiate: true }); default: return state; } diff --git a/client/styles/components/_reset-password.scss b/client/styles/components/_reset-password.scss index 5bd6d662..ad63f76f 100644 --- a/client/styles/components/_reset-password.scss +++ b/client/styles/components/_reset-password.scss @@ -6,4 +6,21 @@ justify-content: center; padding: #{20 / $base-font-size}rem; align-items: center; -} \ No newline at end of file +} + +.reset-password-form__email-input { + width: #{300 / $base-font-size}rem; +} + +.reset-password-form__field { + margin: #{20 / $base-font-size}rem 0; +} + +.reset-password__submitted { + width: #{300 / $base-font-size}rem; + display: none; + .reset-password--submitted { + display: block; + + } +} diff --git a/server/controllers/user.controller.js b/server/controllers/user.controller.js index b3b93c69..d453d37c 100644 --- a/server/controllers/user.controller.js +++ b/server/controllers/user.controller.js @@ -1,4 +1,6 @@ import User from '../models/user'; +import crypto from 'crypto'; +import async from 'async'; export function createUser(req, res, next) { const user = new User({ @@ -74,11 +76,31 @@ export function updatePreferences(req, res) { } export function resetPasswordInitiate(req, res) { - User.findOne({ email: req.body.email }, (err, user) => { - if (!user) { - return res.json({message: 'If the email is registered with the editor, an email has been sent.'}); + async.waterfall([ + (done) => { + crypto.randomBytes(20, function(err, buf) { + var token = buf.toString('hex'); + done(err, token); + }); + }, + (token, done) => { + User.findOne({ email: req.body.email }, (err, user) => { + if (!user) { + return res.json({success: true, message: 'If the email is registered with the editor, an email has been sent.'}); + } + user.resetPasswordToken = token; + user.resetPasswordExpires = Date.now() + 3600000; // 1 hour + + user.save(function(err) { + done(err, token, user); + }); + }); } - user.resetPasswordToken = token; - user.resetPasswordExpires = Date.now() + 3600000; // 1 hour + ], (err) => { + if (err) { + console.log(err); + return res.json({success: false}); + } + return res.json({success: true, message: 'If the email is registered with the editor, an email has been sent.'}); }); }