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_PASSWORD_INITIATE = 'RESET_PASSWORD_INITIATE';
export const RESET_PASSWORD_RESET = 'RESET_PASSWORD_RESET';
// eventually, handle errors more specifically and better
export const ERROR = 'ERROR';

View file

@ -13,12 +13,12 @@ function ResetPasswordForm(props) {
{...email}
/>
</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>
);
}
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;

View file

@ -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 (
<div className="reset-password" ref="resetPassword" tabIndex="0">
<div className={resetPasswordClass} ref="resetPassword" tabIndex="0">
<h1>Reset Your Password</h1>
<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>
or
&nbsp;or&nbsp;
<Link className="form__signup-button" to="/signup">Sign up</Link>
</p>
<Link className="form__cancel-button" to="/">Cancel</Link>
</div>
);
}
}
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);

View file

@ -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 => {
.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
};
}

View file

@ -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;
}

View file

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