Browse Source

Added forgot password page.

KrisVos130 7 years ago
parent
commit
032f5d7121

+ 8 - 0
frontend/app/js/views/App/index.jsx

@@ -106,6 +106,14 @@ class App extends Component { // eslint-disable-line react/no-multi-comp
 							) }
 							authRequired={ true }
 						/>
+						<AuthRoute
+							exact
+							path="/reset_password"
+							component={ asyncComponent(() =>
+								System.import("views/Auth/ForgotPassword").then(module => module.default)
+							) }
+							authRequired={ false }
+						/>
 						<Route
 							exact
 							path="/template"

+ 10 - 0
frontend/app/js/views/Auth/CustomInput.jsx

@@ -4,7 +4,9 @@ import reactTriggerChange from "react-trigger-change";
 
 const regex = {
 	azAZ09_: /^[A-Za-z0-9_]+$/,
+	azAZ09: /^[A-Za-z0-9]+$/,
 	az09_: /^[a-z0-9_]+$/,
+	az09: /^[a-z0-9]+$/,
 	emailSimple: /^[\x00-\x7F]+@[a-z0-9]+\.[a-z0-9]+(\.[a-z0-9]+)?$/,
 	password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]/,
 	ascii: /^[\x00-\x7F]+$/,
@@ -14,6 +16,8 @@ const isLength = (string, min, max) => {
 	return !(typeof string !== "string" || string.length < min || string.length > max);
 };
 
+// TODO add features where inputs need to be the same
+
 const validation = {
 	username: (value) => {
 		const errors = [];
@@ -33,6 +37,12 @@ const validation = {
 		if (!regex.password.test(value)) errors.push("Invalid password format.");
 		return errors;
 	},
+	uniqueCode: (value) => {
+		const errors = [];
+		if (!isLength(value, 8, 8)) errors.push("Code must be 8 characters long.");
+		if (!regex.azAZ09.test(value)) errors.push("Invalid code format.");
+		return errors;
+	},
 };
 
 export default class CustomInput extends Component {

+ 169 - 0
frontend/app/js/views/Auth/ForgotPassword.jsx

@@ -0,0 +1,169 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+
+import CustomInput from "./CustomInput.jsx";
+import CustomErrors from "./CustomErrors.jsx";
+
+import io from "../../io";
+
+@connect(state => ({
+	user: {
+		userId: state.user.get("userId"),
+	},
+}))
+
+export default class Settings extends Component {
+	static propTypes = {
+		user: PropTypes.object,
+	};
+
+	static defaultProps = {
+		user: {
+			userId: "",
+		},
+	};
+
+	constructor(props) {
+		super(props);
+
+		this.state = {
+			step: 1,
+			email: "",
+			resetCode: "",
+			newPassword: "",
+			newPasswordAgain: "",
+			inputInvalid: {
+				email: true,
+				resetCode: true,
+				newPassword: true,
+				newPasswordAgain: true,
+			},
+			errors: [],
+		};
+	}
+
+	getActions = () => {
+		const emailInput = <CustomInput key="email" label="Email" placeholder="Email" inputType="email" type="email" name="email" value={ this.state.email } customInputEvents={ { onChange: event => this.updateField("email", event) } } validationCallback={ this.validationCallback } />;
+		const requestResetCodeButton = (<button key="requestResetCode" onClick={ this.requestResetCode }>
+			Request reset code
+		</button>);
+		const iAlreadyHaveAResetCodeButton = (<button key="skipRequestResetCode" onClick={ this.skipRequestResetCode }>
+			I already have a reset code
+		</button>);
+
+		const resetCodeInput = <CustomInput key="resetCode" label="Reset code" placeholder="Reset code" inputType="text" type="uniqueCode" name="resetCode" value={ this.state.resetCode } customInputEvents={ { onChange: event => this.updateField("resetCode", event) } } validationCallback={ this.validationCallback } />;
+		const verifyResetCode = (<button key="verifyResetCode" onClick={ this.verifyResetCode }>
+			Verify reset code
+		</button>);
+
+		const newPasswordInput = <CustomInput key="newPassword" label="New password" placeholder="New password" inputType="password" type="password" name="newPassword" value={ this.state.newPassword } customInputEvents={ { onChange: event => this.updateField("newPassword", event) } } validationCallback={ this.validationCallback } />;
+		const newPasswordAgainInput = <CustomInput key="newPasswordAgain" label="New password again" placeholder="New password again" inputType="password" type="password" name="newPasswordAgain" value={ this.state.newPasswordAgain } customInputEvents={ { onChange: event => this.updateField("newPasswordAgain", event) } } validationCallback={ this.validationCallback } />;
+		const changePassword = (<button key="changePassword" onClick={ this.changePassword }>
+			Change password
+		</button>);
+
+		if (this.state.step === 1) {
+			return [emailInput, requestResetCodeButton, iAlreadyHaveAResetCodeButton];
+		} else if (this.state.step === 2) {
+			return [resetCodeInput, verifyResetCode];
+		} return [newPasswordInput, newPasswordAgainInput, changePassword];
+	};
+
+	validationCallback = CustomInput.validationCallback(this);
+
+	requestResetCode = () => {
+		if (CustomInput.hasInvalidInput(this.state.inputInvalid, ["email"])) {
+			alert("Input invalid. Fix before continuing.");
+		} else {
+			this.setState({ errors: [] });
+			io.getSocket(socket => {
+				socket.emit("users.requestPasswordReset", this.state.email, res => {
+					if (res.status === "success") {
+						alert("Success!");
+						this.setState({
+							step: 2,
+						});
+					} else {
+						this.setState({
+							errors: this.state.errors.concat([res.message]),
+						});
+					}
+				});
+			});
+		}
+	};
+
+	verifyResetCode = () => {
+		if (CustomInput.hasInvalidInput(this.state.inputInvalid, ["resetCode"])) {
+			alert("Input invalid. Fix before continuing.");
+		} else {
+			this.setState({ errors: [] });
+			io.getSocket(socket => {
+				socket.emit("users.verifyPasswordResetCode", this.state.resetCode, res => {
+					if (res.status === "success") {
+						alert("Success!");
+						this.setState({
+							step: 3,
+						});
+					} else {
+						this.setState({
+							errors: this.state.errors.concat([res.message]),
+						});
+					}
+				});
+			});
+		}
+	};
+
+	changePassword = () => {
+		if (CustomInput.hasInvalidInput(this.state.inputInvalid, ["newPassword", "newPasswordAgain"])) {
+			alert("Input invalid. Fix before continuing.");
+		} else if (this.state.newPassword !== this.state.newPasswordAgain) {
+			alert("Passwords need to be the same.");
+		} else {
+			this.setState({ errors: [] });
+			io.getSocket(socket => {
+				socket.emit("users.changePasswordWithResetCode", this.state.resetCode, this.state.newPassword, res => {
+					if (res.status === "success") {
+						alert("Success!");
+						location.href = "/login";
+					} else {
+						this.setState({
+							errors: this.state.errors.concat([res.message]),
+						});
+					}
+				});
+			});
+		}
+	};
+
+	skipRequestResetCode = () => {
+		this.setState({
+			step: 2,
+		});
+	};
+
+	updateField(field, event) {
+		this.setState({
+			[field]: event.target.value,
+		});
+	}
+
+	render() {
+		return (
+			<div>
+				<h1>Reset password</h1>
+				<div className="steps">
+					<span className={ `step-circle-1 ${ this.state.step === 1 ? "step-circle-active" : "" }` }>1</span>
+					<span className="step-line-1" />
+					<span className={ `step-circle-2 ${ this.state.step === 2 ? "step-circle-active" : "" }` }>2</span>
+					<span className="step-line-2" />
+					<span className={ `step-circle-3 ${ this.state.step === 3 ? "step-circle-active" : "" }` }>3</span>
+				</div>
+				<CustomErrors errors={ this.state.errors } />
+				{ this.getActions() }
+			</div>
+		);
+	}
+}

+ 4 - 4
frontend/app/js/views/Auth/Settings.jsx

@@ -227,12 +227,12 @@ export default class Settings extends Component {
 	};
 
 	linkButtons = () => {
-		const linkPassword = <button>TODO</button>;
-		const linkGitHub = <a href="http://localhost:8080/auth/github/link">Link GitHub to account</a>;
-		const unlinkGitHub = (<button onClick={ this.unlinkGitHub }>
+		const linkPassword = <button key="linkPassword">TODO</button>;
+		const linkGitHub = <a key="linkGitHub" href="http://localhost:8080/auth/github/link">Link GitHub to account</a>;
+		const unlinkGitHub = (<button key="unlinkGitHub" onClick={ this.unlinkGitHub }>
 				Remove logging in with GitHub
 			</button>);
-		const unlinkPassword = (<button onClick={ this.unlinkPassword }>
+		const unlinkPassword = (<button key="unlinkPassword" onClick={ this.unlinkPassword }>
 			Remove logging in with password
 		</button>);
 		if (this.state.passwordLinked && this.state.gitHubLinked) {