From a5df76d7e9f57dae741e591bb3cf0f467a51fd4c Mon Sep 17 00:00:00 2001 From: Alexander Wong Date: Sun, 3 Sep 2017 13:22:27 -0600 Subject: [PATCH] Finished Forgot Password request, Reset Password request --- src/actions/auth/reducer.actions.js | 14 ++- src/actions/auth/saga.actions.js | 22 ++++- src/api/auth.api.js | 26 ++++++ src/components/App.jsx | 4 + src/components/Auth/ForgotPassword.jsx | 98 ++++++++++++++++++++ src/components/Auth/Login.jsx | 11 ++- src/components/Auth/Register.jsx | 4 +- src/components/Auth/ResetPassword.jsx | 118 +++++++++++++++++++++++++ src/components/Auth/Settings.jsx | 2 +- src/components/Auth/VerifyEmail.jsx | 2 +- src/constants/auth.constants.js | 2 + src/sagas/auth.sagas.js | 67 +++++++++++++- src/sagas/index.js | 10 ++- 13 files changed, 364 insertions(+), 16 deletions(-) create mode 100644 src/components/Auth/ForgotPassword.jsx create mode 100644 src/components/Auth/ResetPassword.jsx diff --git a/src/actions/auth/reducer.actions.js b/src/actions/auth/reducer.actions.js index a9ac78b..9425c6f 100644 --- a/src/actions/auth/reducer.actions.js +++ b/src/actions/auth/reducer.actions.js @@ -30,12 +30,16 @@ export function setAuthRequestError(exception) { error["Email"] = error.email; delete error["email"]; } + if (error.password) { + error["Password"] = error.password; + delete error["password"]; + } if (error.password1) { error["Password"] = error.password1; delete error["password1"]; } if (error.password2) { - error["Password Confirmation"] = error.password2; + error["Confirm Password"] = error.password2; delete error["password2"]; } if (error.non_field_errors) { @@ -58,6 +62,14 @@ export function setAuthRequestError(exception) { error["Confirm New Password"] = 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 { type: SET_AUTH_REQUEST_ERROR, diff --git a/src/actions/auth/saga.actions.js b/src/actions/auth/saga.actions.js index 16a5032..528e0a8 100644 --- a/src/actions/auth/saga.actions.js +++ b/src/actions/auth/saga.actions.js @@ -3,7 +3,9 @@ import { SEND_EMAIL_VERIFICATION_REQUEST, SEND_LOGIN_REQUEST, SEND_LOGOUT_REQUEST, - SEND_CHANGE_PASSWORD_REQUEST + SEND_CHANGE_PASSWORD_REQUEST, + SEND_FORGOT_PASSWORD_REQUEST, + SEND_RESET_PASSWORD_REQUEST } from "../../constants/auth.constants"; export function sendRegisterRequest(postBody) { @@ -30,12 +32,26 @@ export function sendLoginRequest(postBody) { export function sendLogoutRequest() { return { type: SEND_LOGOUT_REQUEST - } + }; } export function sendChangePasswordRequest(postBody) { return { type: SEND_CHANGE_PASSWORD_REQUEST, 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 + }; } diff --git a/src/api/auth.api.js b/src/api/auth.api.js index 9ff8c3d..ee083a3 100644 --- a/src/api/auth.api.js +++ b/src/api/auth.api.js @@ -55,3 +55,29 @@ export function changePassword(new_password1, new_password2, old_password) { old_password }).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)); +} diff --git a/src/components/App.jsx b/src/components/App.jsx index e444629..29e178b 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -1,8 +1,10 @@ import React, { Component } from "react"; import { Route, Switch } from "react-router-dom"; +import ForgotPassword from "./Auth/ForgotPassword"; import Login from "./Auth/Login"; import Register from "./Auth/Register"; +import ResetPassword from "./Auth/ResetPassword"; import Settings from "./Auth/Settings"; import VerifyEmail from "./Auth/VerifyEmail"; import PrivateRoute from "./Shared/PrivateRoute"; @@ -34,6 +36,8 @@ class App extends Component { component={VerifyEmail} /> + + diff --git a/src/components/Auth/ForgotPassword.jsx b/src/components/Auth/ForgotPassword.jsx new file mode 100644 index 0000000..d85c135 --- /dev/null +++ b/src/components/Auth/ForgotPassword.jsx @@ -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 ; + return ( + + ); + } +} + +function mapStateToProps(state) { + return { ...state.auth }; +} + +const ForgotPasswordView = ({ + isSendingAuthRequest, + authRequestError, + authRequestSuccess, + email, + changeEmail, + onSubmitForgotPassword +}) => ( + +
Forgot Password
+

Enter your email and we will send you a link to reset your password.

+
+ + + + + + + Password Reset Sent! +

If the email exists, a password reset link will be sent to it.

+
+ + + + + +
+); + +export default connect(mapStateToProps)(ForgotPassword); diff --git a/src/components/Auth/Login.jsx b/src/components/Auth/Login.jsx index 5048364..3d819c8 100644 --- a/src/components/Auth/Login.jsx +++ b/src/components/Auth/Login.jsx @@ -1,7 +1,7 @@ import React, { Component } from "react"; import { connect } from "react-redux"; -import { Redirect } from "react-router-dom"; -import { Container, Form, Header, Message } from "semantic-ui-react"; +import { Link, Redirect } from "react-router-dom"; +import { Button, Container, Form, Header, Message } from "semantic-ui-react"; import { clearAuthRequestError, @@ -102,7 +102,12 @@ const LoginView = ({ Login successful!

Redirecting you now...

- Login + + + + ); diff --git a/src/components/Auth/Register.jsx b/src/components/Auth/Register.jsx index 582a790..49637dc 100644 --- a/src/components/Auth/Register.jsx +++ b/src/components/Auth/Register.jsx @@ -117,7 +117,7 @@ const RegisterView = ({ /> - + Confirmation Email Sent!

Please check your email to confirm your registration.

- Register + Register ); diff --git a/src/components/Auth/ResetPassword.jsx b/src/components/Auth/ResetPassword.jsx new file mode 100644 index 0000000..80e31ca --- /dev/null +++ b/src/components/Auth/ResetPassword.jsx @@ -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 ; + return ( + + ); + } +} + +function mapStateToProps(state) { + return { ...state.auth }; +} + +const ResetPasswordView = ({ + isSendingAuthRequest, + authRequestError, + authRequestSuccess, + password, + passwordConfirmation, + changePassword, + changePasswordConfirmation, + onSubmitResetPassword +}) => ( + +
Reset Password
+
+ + + + + + + + + + + Password Reset! +

You may now log in with the new credentials.

+
+ Reset Password{" "} + +
+); + +export default connect(mapStateToProps)(ResetPassword); diff --git a/src/components/Auth/Settings.jsx b/src/components/Auth/Settings.jsx index 9ca9570..a05db1b 100644 --- a/src/components/Auth/Settings.jsx +++ b/src/components/Auth/Settings.jsx @@ -131,7 +131,7 @@ const SettingsView = ({ Password Successfully Changed!

New password has been set.

- Change Password + Change Password diff --git a/src/components/Auth/VerifyEmail.jsx b/src/components/Auth/VerifyEmail.jsx index 2d07b95..e6e8c00 100644 --- a/src/components/Auth/VerifyEmail.jsx +++ b/src/components/Auth/VerifyEmail.jsx @@ -88,7 +88,7 @@ const VerifyEmailView = ({ Email Verified!

Please proceed to log in.

- Verify Email + Verify Email ); diff --git a/src/constants/auth.constants.js b/src/constants/auth.constants.js index a89c0bd..0a645ca 100644 --- a/src/constants/auth.constants.js +++ b/src/constants/auth.constants.js @@ -23,3 +23,5 @@ export const SEND_EMAIL_VERIFICATION_REQUEST = export const SEND_LOGIN_REQUEST = "SEND_LOGIN_REQUEST"; export const SEND_LOGOUT_REQUEST = "SEND_LOGOUT_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"; diff --git a/src/sagas/auth.sagas.js b/src/sagas/auth.sagas.js index ff0f4d7..11d135a 100644 --- a/src/sagas/auth.sagas.js +++ b/src/sagas/auth.sagas.js @@ -14,14 +14,16 @@ import { setFormPassword, setFormPasswordConfirmation, setFormEmailVerification, - setFormOldPassword, + setFormOldPassword } from "../actions/auth/reducer.actions"; import { registerUser, verifyEmail, loginUser, logoutUser, - changePassword + changePassword, + forgotPassword, + resetPassword } from "../api/auth.api"; function* registerUserCall(postBody) { @@ -71,7 +73,44 @@ function* changePasswordCall(postBody) { yield effects.put(isSendingAuthRequest(true)); const { new_password1, new_password2, old_password } = postBody; 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) { yield effects.put(setAuthRequestError(exception)); return false; @@ -137,3 +176,25 @@ export function* changePasswordFlow(request) { 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("")); + } +} diff --git a/src/sagas/index.js b/src/sagas/index.js index 7367245..73f6d25 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -4,14 +4,18 @@ import { SEND_EMAIL_VERIFICATION_REQUEST, SEND_LOGIN_REQUEST, SEND_LOGOUT_REQUEST, - SEND_CHANGE_PASSWORD_REQUEST + SEND_CHANGE_PASSWORD_REQUEST, + SEND_FORGOT_PASSWORD_REQUEST, + SEND_RESET_PASSWORD_REQUEST } from "../constants/auth.constants"; import { registerUserFlow, verifyEmailFlow, loginUserFlow, logoutUserFlow, - changePasswordFlow + changePasswordFlow, + forgotPasswordFlow, + resetPasswordFlow, } from "./auth.sagas"; export default function* rootSaga() { @@ -20,4 +24,6 @@ export default function* rootSaga() { yield takeLatest(SEND_LOGIN_REQUEST, loginUserFlow); yield takeLatest(SEND_LOGOUT_REQUEST, logoutUserFlow); yield takeLatest(SEND_CHANGE_PASSWORD_REQUEST, changePasswordFlow); + yield takeLatest(SEND_FORGOT_PASSWORD_REQUEST, forgotPasswordFlow); + yield takeLatest(SEND_RESET_PASSWORD_REQUEST, resetPasswordFlow); }