Finished Forgot Password request, Reset Password request

This commit is contained in:
Alexander Wong 2017-09-03 13:22:27 -06:00
parent ffe3cba510
commit a5df76d7e9
13 changed files with 364 additions and 16 deletions

View File

@ -30,12 +30,16 @@ export function setAuthRequestError(exception) {
error["Email"] = error.email; error["Email"] = error.email;
delete error["email"]; delete error["email"];
} }
if (error.password) {
error["Password"] = error.password;
delete error["password"];
}
if (error.password1) { if (error.password1) {
error["Password"] = error.password1; error["Password"] = error.password1;
delete error["password1"]; delete error["password1"];
} }
if (error.password2) { if (error.password2) {
error["Password Confirmation"] = error.password2; error["Confirm Password"] = error.password2;
delete error["password2"]; delete error["password2"];
} }
if (error.non_field_errors) { if (error.non_field_errors) {
@ -58,6 +62,14 @@ export function setAuthRequestError(exception) {
error["Confirm New Password"] = error.new_password2; error["Confirm New Password"] = error.new_password2;
delete error["new_password2"]; delete error["new_password2"];
} }
if (error.token) {
error["Token"] = error.token;
delete error["token"];
}
if (error.uid) {
error["UID"] = error.uid;
delete error["uid"];
}
return { return {
type: SET_AUTH_REQUEST_ERROR, type: SET_AUTH_REQUEST_ERROR,

View File

@ -3,7 +3,9 @@ import {
SEND_EMAIL_VERIFICATION_REQUEST, SEND_EMAIL_VERIFICATION_REQUEST,
SEND_LOGIN_REQUEST, SEND_LOGIN_REQUEST,
SEND_LOGOUT_REQUEST, SEND_LOGOUT_REQUEST,
SEND_CHANGE_PASSWORD_REQUEST SEND_CHANGE_PASSWORD_REQUEST,
SEND_FORGOT_PASSWORD_REQUEST,
SEND_RESET_PASSWORD_REQUEST
} from "../../constants/auth.constants"; } from "../../constants/auth.constants";
export function sendRegisterRequest(postBody) { export function sendRegisterRequest(postBody) {
@ -30,12 +32,26 @@ export function sendLoginRequest(postBody) {
export function sendLogoutRequest() { export function sendLogoutRequest() {
return { return {
type: SEND_LOGOUT_REQUEST type: SEND_LOGOUT_REQUEST
} };
} }
export function sendChangePasswordRequest(postBody) { export function sendChangePasswordRequest(postBody) {
return { return {
type: SEND_CHANGE_PASSWORD_REQUEST, type: SEND_CHANGE_PASSWORD_REQUEST,
data: postBody data: postBody
} };
}
export function sendForgotPasswordRequest(postBody) {
return {
type: SEND_FORGOT_PASSWORD_REQUEST,
data: postBody
};
}
export function sendResetPasswordRequest(postBody) {
return {
type: SEND_RESET_PASSWORD_REQUEST,
data: postBody
};
} }

View File

@ -55,3 +55,29 @@ export function changePassword(new_password1, new_password2, old_password) {
old_password old_password
}).then(resp => Promise.resolve(resp)); }).then(resp => Promise.resolve(resp));
} }
/**
* Function wrapping POST request for a forget password email call
* @param {string} email - email of user for password reset
*/
export function forgotPassword(email) {
return post("/rest-auth/password/reset/", { email }).then(resp =>
Promise.resolve(resp)
);
}
/**
* Function wrapping POST request for resetting a user's password
* @param {string} uid - uid supplied by forgot password email
* @param {string} token - token supplied by forgot password email
* @param {string} new_password1 - user new password
* @param {string} new_password2 - user new password confirmation
*/
export function resetPassword(uid, token, new_password1, new_password2) {
return post("/rest-auth/password/reset/confirm/", {
uid,
token,
new_password1,
new_password2
}).then(resp => Promise.resolve(resp));
}

View File

@ -1,8 +1,10 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Route, Switch } from "react-router-dom"; import { Route, Switch } from "react-router-dom";
import ForgotPassword from "./Auth/ForgotPassword";
import Login from "./Auth/Login"; import Login from "./Auth/Login";
import Register from "./Auth/Register"; import Register from "./Auth/Register";
import ResetPassword from "./Auth/ResetPassword";
import Settings from "./Auth/Settings"; import Settings from "./Auth/Settings";
import VerifyEmail from "./Auth/VerifyEmail"; import VerifyEmail from "./Auth/VerifyEmail";
import PrivateRoute from "./Shared/PrivateRoute"; import PrivateRoute from "./Shared/PrivateRoute";
@ -34,6 +36,8 @@ class App extends Component {
component={VerifyEmail} component={VerifyEmail}
/> />
<PrivateRoute path="/auth/settings" component={Settings} /> <PrivateRoute path="/auth/settings" component={Settings} />
<Route path="/auth/forgot-password" component={ForgotPassword} />
<Route path="/auth/reset-password/:uid/:token" component={ResetPassword} />
<Route component={NoMatch} /> <Route component={NoMatch} />
</Switch> </Switch>
</div> </div>

View File

@ -0,0 +1,98 @@
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link, Redirect } from "react-router-dom";
import { Button, Container, Form, Header, Message } from "semantic-ui-react";
import {
clearAuthRequestError,
clearAuthRequestSuccess,
setFormEmail
} from "../../actions/auth/reducer.actions";
import { sendForgotPasswordRequest } from "../../actions/auth/saga.actions";
import Error from "../Shared/Error";
class ForgotPassword extends Component {
componentWillMount() {
this.props.dispatch(clearAuthRequestError());
this.props.dispatch(clearAuthRequestSuccess());
}
changeEmail = event => {
this.props.dispatch(setFormEmail(event.target.value));
};
onSubmitForgotPassword = event => {
event.preventDefault();
const { dispatch, email } = this.props;
dispatch(sendForgotPasswordRequest({ email }));
};
render() {
const {
isSendingAuthRequest,
authRequestError,
authRequestSuccess,
email,
userToken
} = this.props;
if (userToken) return <Redirect to={"/"} />;
return (
<ForgotPasswordView
isSendingAuthRequest={isSendingAuthRequest}
authRequestError={authRequestError}
authRequestSuccess={authRequestSuccess}
email={email}
changeEmail={this.changeEmail}
onSubmitForgotPassword={this.onSubmitForgotPassword}
/>
);
}
}
function mapStateToProps(state) {
return { ...state.auth };
}
const ForgotPasswordView = ({
isSendingAuthRequest,
authRequestError,
authRequestSuccess,
email,
changeEmail,
onSubmitForgotPassword
}) => (
<Container>
<Header>Forgot Password</Header>
<p>Enter your email and we will send you a link to reset your password.</p>
<Form
loading={isSendingAuthRequest}
onSubmit={onSubmitForgotPassword}
error={!!authRequestError}
success={!!authRequestSuccess}
>
<Form.Field>
<label>Email</label>
<input
placeholder="bob@gmail.com"
type="email"
value={email}
onChange={changeEmail}
/>
</Form.Field>
<Error
header="Forgot Password Request failed!"
error={authRequestError}
/>
<Message success>
<Message.Header>Password Reset Sent!</Message.Header>
<p>If the email exists, a password reset link will be sent to it.</p>
</Message>
<Button.Group>
<Button type="submit" primary>Request Password Reset</Button>
<Button as={Link} to="/auth/login" secondary>Login</Button>
</Button.Group>
</Form>
</Container>
);
export default connect(mapStateToProps)(ForgotPassword);

View File

@ -1,7 +1,7 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Redirect } from "react-router-dom"; import { Link, Redirect } from "react-router-dom";
import { Container, Form, Header, Message } from "semantic-ui-react"; import { Button, Container, Form, Header, Message } from "semantic-ui-react";
import { import {
clearAuthRequestError, clearAuthRequestError,
@ -102,7 +102,12 @@ const LoginView = ({
<Message.Header>Login successful!</Message.Header> <Message.Header>Login successful!</Message.Header>
<p>Redirecting you now...</p> <p>Redirecting you now...</p>
</Message> </Message>
<Form.Button>Login</Form.Button> <Button.Group>
<Button type="submit" primary>Login</Button>
<Button as={Link} to="/auth/forgot-password" secondary>
Forgot Password
</Button>
</Button.Group>
</Form> </Form>
</Container> </Container>
); );

View File

@ -117,7 +117,7 @@ const RegisterView = ({
/> />
</Form.Field> </Form.Field>
<Form.Field> <Form.Field>
<label>Password Confirmation</label> <label>Confirm Password</label>
<input <input
placeholder="••••••••" placeholder="••••••••"
type="password" type="password"
@ -130,7 +130,7 @@ const RegisterView = ({
<Message.Header>Confirmation Email Sent!</Message.Header> <Message.Header>Confirmation Email Sent!</Message.Header>
<p>Please check your email to confirm your registration.</p> <p>Please check your email to confirm your registration.</p>
</Message> </Message>
<Form.Button>Register</Form.Button> <Form.Button primary>Register</Form.Button>
</Form> </Form>
</Container> </Container>
); );

View File

@ -0,0 +1,118 @@
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link, Redirect } from "react-router-dom";
import { Container, Form, Header, Message } from "semantic-ui-react";
import {
clearAuthRequestError,
clearAuthRequestSuccess,
setFormPassword,
setFormPasswordConfirmation
} from "../../actions/auth/reducer.actions";
import { sendResetPasswordRequest } from "../../actions/auth/saga.actions";
import Error from "../Shared/Error";
class ResetPassword extends Component {
componentWillMount() {
this.props.dispatch(clearAuthRequestError());
this.props.dispatch(clearAuthRequestSuccess());
}
changePassword = event => {
this.props.dispatch(setFormPassword(event.target.value));
};
changePasswordConfirmation = event => {
this.props.dispatch(setFormPasswordConfirmation(event.target.value));
};
onSubmitResetPassword = event => {
event.preventDefault();
const { dispatch, password, passwordConfirmation } = this.props;
const { uid, token } = this.props.match.params;
dispatch(
sendResetPasswordRequest({
uid,
token,
new_password1: password,
new_password2: passwordConfirmation
})
);
};
render() {
const {
isSendingAuthRequest,
authRequestError,
authRequestSuccess,
password,
passwordConfirmation,
userToken
} = this.props;
if (userToken) return <Redirect to={"/"} />;
return (
<ResetPasswordView
isSendingAuthRequest={isSendingAuthRequest}
authRequestError={authRequestError}
authRequestSuccess={authRequestSuccess}
password={password}
passwordConfirmation={passwordConfirmation}
changePassword={this.changePassword}
changePasswordConfirmation={this.changePasswordConfirmation}
onSubmitResetPassword={this.onSubmitResetPassword}
/>
);
}
}
function mapStateToProps(state) {
return { ...state.auth };
}
const ResetPasswordView = ({
isSendingAuthRequest,
authRequestError,
authRequestSuccess,
password,
passwordConfirmation,
changePassword,
changePasswordConfirmation,
onSubmitResetPassword
}) => (
<Container>
<Header>Reset Password</Header>
<Form
loading={isSendingAuthRequest}
onSubmit={onSubmitResetPassword}
error={!!authRequestError}
success={!!authRequestSuccess}
>
<Form.Field>
<label>New Password</label>
<input
placeholder="••••••••"
type="password"
value={password}
onChange={changePassword}
/>
</Form.Field>
<Form.Field>
<label>Confirm New Password</label>
<input
placeholder="••••••••"
type="password"
value={passwordConfirmation}
onChange={changePasswordConfirmation}
/>
</Form.Field>
<Error header="Reset Password failed!" error={authRequestError} />
<Message success>
<Message.Header>Password Reset!</Message.Header>
<p>You may now <Link to="/auth/login">log in</Link> with the new credentials.</p>
</Message>
<Form.Button primary>Reset Password</Form.Button>{" "}
</Form>
</Container>
);
export default connect(mapStateToProps)(ResetPassword);

View File

@ -131,7 +131,7 @@ const SettingsView = ({
<Message.Header>Password Successfully Changed!</Message.Header> <Message.Header>Password Successfully Changed!</Message.Header>
<p>New password has been set.</p> <p>New password has been set.</p>
</Message> </Message>
<Form.Button>Change Password</Form.Button> <Form.Button primary>Change Password</Form.Button>
</Form> </Form>
</Segment> </Segment>
</Container> </Container>

View File

@ -88,7 +88,7 @@ const VerifyEmailView = ({
<Message.Header>Email Verified!</Message.Header> <Message.Header>Email Verified!</Message.Header>
<p>Please proceed to <Link to="/auth/login">log in</Link>.</p> <p>Please proceed to <Link to="/auth/login">log in</Link>.</p>
</Message> </Message>
<Form.Button>Verify Email</Form.Button> <Form.Button primary>Verify Email</Form.Button>
</Form> </Form>
</Container> </Container>
); );

View File

@ -23,3 +23,5 @@ export const SEND_EMAIL_VERIFICATION_REQUEST =
export const SEND_LOGIN_REQUEST = "SEND_LOGIN_REQUEST"; export const SEND_LOGIN_REQUEST = "SEND_LOGIN_REQUEST";
export const SEND_LOGOUT_REQUEST = "SEND_LOGOUT_REQUEST"; export const SEND_LOGOUT_REQUEST = "SEND_LOGOUT_REQUEST";
export const SEND_CHANGE_PASSWORD_REQUEST = "SEND_CHANGE_PASSWORD_REQUEST"; export const SEND_CHANGE_PASSWORD_REQUEST = "SEND_CHANGE_PASSWORD_REQUEST";
export const SEND_FORGOT_PASSWORD_REQUEST = "SEND_FORGOT_PASSWORD_REQUEST";
export const SEND_RESET_PASSWORD_REQUEST = "SEND_RESET_PASSWORD_REQUEST";

View File

@ -14,14 +14,16 @@ import {
setFormPassword, setFormPassword,
setFormPasswordConfirmation, setFormPasswordConfirmation,
setFormEmailVerification, setFormEmailVerification,
setFormOldPassword, setFormOldPassword
} from "../actions/auth/reducer.actions"; } from "../actions/auth/reducer.actions";
import { import {
registerUser, registerUser,
verifyEmail, verifyEmail,
loginUser, loginUser,
logoutUser, logoutUser,
changePassword changePassword,
forgotPassword,
resetPassword
} from "../api/auth.api"; } from "../api/auth.api";
function* registerUserCall(postBody) { function* registerUserCall(postBody) {
@ -71,7 +73,44 @@ function* changePasswordCall(postBody) {
yield effects.put(isSendingAuthRequest(true)); yield effects.put(isSendingAuthRequest(true));
const { new_password1, new_password2, old_password } = postBody; const { new_password1, new_password2, old_password } = postBody;
try { try {
return yield effects.call(changePassword, new_password1, new_password2, old_password); return yield effects.call(
changePassword,
new_password1,
new_password2,
old_password
);
} catch (exception) {
yield effects.put(setAuthRequestError(exception));
return false;
} finally {
yield effects.put(isSendingAuthRequest(false));
}
}
function* forgotPasswordCall(postBody) {
yield effects.put(isSendingAuthRequest(true));
const { email } = postBody;
try {
return yield effects.call(forgotPassword, email);
} catch (exception) {
yield effects.put(setAuthRequestError(exception));
return false;
} finally {
yield effects.put(isSendingAuthRequest(false));
}
}
function* resetPasswordCall(postBody) {
yield effects.put(isSendingAuthRequest(true));
const { uid, token, new_password1, new_password2 } = postBody;
try {
return yield effects.call(
resetPassword,
uid,
token,
new_password1,
new_password2
);
} catch (exception) { } catch (exception) {
yield effects.put(setAuthRequestError(exception)); yield effects.put(setAuthRequestError(exception));
return false; return false;
@ -137,3 +176,25 @@ export function* changePasswordFlow(request) {
yield effects.put(setFormPasswordConfirmation("")); yield effects.put(setFormPasswordConfirmation(""));
} }
} }
export function* forgotPasswordFlow(request) {
yield effects.put(clearAuthRequestSuccess());
yield effects.put(clearAuthRequestError());
const wasSuccessful = yield effects.call(forgotPasswordCall, request.data);
if (wasSuccessful) {
yield effects.put(setAuthRequestSuccess(wasSuccessful));
yield effects.put(clearAuthRequestError());
}
}
export function* resetPasswordFlow(request) {
yield effects.put(clearAuthRequestSuccess());
yield effects.put(clearAuthRequestError());
const wasSuccessful = yield effects.call(resetPasswordCall, request.data);
if (wasSuccessful) {
yield effects.put(setAuthRequestSuccess(wasSuccessful));
yield effects.put(clearAuthRequestError());
yield effects.put(setFormPassword(""));
yield effects.put(setFormPasswordConfirmation(""));
}
}

View File

@ -4,14 +4,18 @@ import {
SEND_EMAIL_VERIFICATION_REQUEST, SEND_EMAIL_VERIFICATION_REQUEST,
SEND_LOGIN_REQUEST, SEND_LOGIN_REQUEST,
SEND_LOGOUT_REQUEST, SEND_LOGOUT_REQUEST,
SEND_CHANGE_PASSWORD_REQUEST SEND_CHANGE_PASSWORD_REQUEST,
SEND_FORGOT_PASSWORD_REQUEST,
SEND_RESET_PASSWORD_REQUEST
} from "../constants/auth.constants"; } from "../constants/auth.constants";
import { import {
registerUserFlow, registerUserFlow,
verifyEmailFlow, verifyEmailFlow,
loginUserFlow, loginUserFlow,
logoutUserFlow, logoutUserFlow,
changePasswordFlow changePasswordFlow,
forgotPasswordFlow,
resetPasswordFlow,
} from "./auth.sagas"; } from "./auth.sagas";
export default function* rootSaga() { export default function* rootSaga() {
@ -20,4 +24,6 @@ export default function* rootSaga() {
yield takeLatest(SEND_LOGIN_REQUEST, loginUserFlow); yield takeLatest(SEND_LOGIN_REQUEST, loginUserFlow);
yield takeLatest(SEND_LOGOUT_REQUEST, logoutUserFlow); yield takeLatest(SEND_LOGOUT_REQUEST, logoutUserFlow);
yield takeLatest(SEND_CHANGE_PASSWORD_REQUEST, changePasswordFlow); yield takeLatest(SEND_CHANGE_PASSWORD_REQUEST, changePasswordFlow);
yield takeLatest(SEND_FORGOT_PASSWORD_REQUEST, forgotPasswordFlow);
yield takeLatest(SEND_RESET_PASSWORD_REQUEST, resetPasswordFlow);
} }