create password reset token from FE, start to add flash text

This commit is contained in:
Cassie Tarakajian 2016-10-12 17:19:43 -04:00
parent ea6d30c430
commit 5aa5032961
7 changed files with 93 additions and 21 deletions

View file

@ -94,6 +94,7 @@ export const DETECT_INFINITE_LOOPS = 'DETECT_INFINITE_LOOPS';
export const RESET_INFINITE_LOOPS = 'RESET_INFINITE_LOOPS'; export const RESET_INFINITE_LOOPS = 'RESET_INFINITE_LOOPS';
export const RESET_PASSWORD_INITIATE = 'RESET_PASSWORD_INITIATE'; export const RESET_PASSWORD_INITIATE = 'RESET_PASSWORD_INITIATE';
export const RESET_PASSWORD_RESET = 'RESET_PASSWORD_RESET';
// eventually, handle errors more specifically and better // eventually, handle errors more specifically and better
export const ERROR = 'ERROR'; export const ERROR = 'ERROR';

View file

@ -13,12 +13,12 @@ function ResetPasswordForm(props) {
{...email} {...email}
/> />
</p> </p>
<input type="submit" disabled={submitting || invalid || pristine} value="Send email to reset password" aria-label="Send email to reset password" /> <input type="submit" disabled={submitting || invalid || pristine} value="Send password reset email" aria-label="Send email to reset password" />
</form> </form>
); );
} }
ResetPassword.propTypes = { ResetPasswordForm.propTypes = {
fields: PropTypes.shape({ fields: PropTypes.shape({
email: PropTypes.object.isRequired email: PropTypes.object.isRequired
}).isRequired, }).isRequired,
@ -27,6 +27,6 @@ ResetPassword.propTypes = {
submitting: PropTypes.bool, submitting: PropTypes.bool,
invalid: PropTypes.bool, invalid: PropTypes.bool,
pristine: PropTypes.bool pristine: PropTypes.bool
} };
export default ResetPasswordForm; export default ResetPasswordForm;

View file

@ -1,29 +1,51 @@
import React from 'react'; import React, { PropTypes } from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import * as UserActions from '../../User/actions'; import * as UserActions from '../../User/actions';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { reduxForm } from 'redux-form'; import { reduxForm } from 'redux-form';
import ResetPasswordForm from './ResetPasswordForm'; import ResetPasswordForm from './ResetPasswordForm';
import classNames from 'classnames';
class ResetPasswordView extends React.Component { class ResetPasswordView extends React.Component {
componentWillMount() {
this.props.resetPasswordReset();
}
componentDidMount() { componentDidMount() {
this.refs.resetPassword.focus(); this.refs.resetPassword.focus();
} }
render() { render() {
const resetPasswordClass = classNames({
'reset-password': true,
'reset-password--submitted': this.props.user.passwordResetInitialized
});
return ( return (
<div className="reset-password" ref="resetPassword" tabIndex="0"> <div className={resetPasswordClass} ref="resetPassword" tabIndex="0">
<h1>Reset Your Password</h1> <h1>Reset Your Password</h1>
<ResetPasswordForm {...this.props} /> <ResetPasswordForm {...this.props} />
<p className="reset-password__submitted">
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.
</p>
<p className="form__navigation-options">
<Link className="form__login-button" to="/login">Login</Link> <Link className="form__login-button" to="/login">Login</Link>
or &nbsp;or&nbsp;
<Link className="form__signup-button" to="/signup">Sign up</Link> <Link className="form__signup-button" to="/signup">Sign up</Link>
</p>
<Link className="form__cancel-button" to="/">Cancel</Link> <Link className="form__cancel-button" to="/">Cancel</Link>
</div> </div>
); );
} }
} }
ResetPasswordView.propTypes = {
resetPasswordReset: PropTypes.func.isRequired,
user: PropTypes.shape({
passwordResetInitialized: PropTypes.bool
}).isRequired,
};
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
user: state.user user: state.user
@ -37,13 +59,13 @@ function mapDispatchToProps(dispatch) {
function validate(formProps) { function validate(formProps) {
const errors = {}; const errors = {};
if (!formProps.email) { if (!formProps.email) {
errors.email = 'Please enter an email' errors.email = 'Please enter an email';
} }
return errors; return errors;
} }
export default reduxForm({ export default reduxForm({
form: 'reset-password', form: 'reset-password',
fields: 'email', fields: ['email'],
validate validate
}, mapStateToProps, mapDispatchToProps)(ResetPasswordView); }, mapStateToProps, mapDispatchToProps)(ResetPasswordView);

View file

@ -105,15 +105,23 @@ export function logoutUser() {
}; };
} }
export function initiatePasswordReset(formValues) { export function initiateResetPassword(formValues) {
return (dispatch) => { return (dispatch) => {
axios.post(`${ROOT_URL}/reset-password`, formValues, { withCredentials: true }) axios.post(`${ROOT_URL}/reset-password`, formValues, { withCredentials: true })
.then(response => { .then(() => {
dispatch({ dispatch({
type: ActionTypes.RESET_PASSWORD_INITIATE 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
};
}

View file

@ -13,6 +13,8 @@ const user = (state = { authenticated: false }, action) => {
return { return {
authenticated: false authenticated: false
}; };
case ActionTypes.RESET_PASSWORD_INITIATE:
return Object.assign(state, {}, { resetPasswordInitiate: true });
default: default:
return state; return state;
} }

View file

@ -7,3 +7,20 @@
padding: #{20 / $base-font-size}rem; padding: #{20 / $base-font-size}rem;
align-items: center; align-items: center;
} }
.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;
}
}

View file

@ -1,4 +1,6 @@
import User from '../models/user'; import User from '../models/user';
import crypto from 'crypto';
import async from 'async';
export function createUser(req, res, next) { export function createUser(req, res, next) {
const user = new User({ const user = new User({
@ -74,11 +76,31 @@ export function updatePreferences(req, res) {
} }
export function resetPasswordInitiate(req, res) { export function resetPasswordInitiate(req, res) {
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) => { User.findOne({ email: req.body.email }, (err, user) => {
if (!user) { if (!user) {
return res.json({message: 'If the email is registered with the editor, an email has been sent.'}); return res.json({success: true, message: 'If the email is registered with the editor, an email has been sent.'});
} }
user.resetPasswordToken = token; user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
user.save(function(err) {
done(err, token, user);
});
});
}
], (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.'});
}); });
} }