Переглянути джерело

feat(Security_Settings): passwords can now be changed in Settings, some layout changes

Signed-off-by: Jonathan <theflametrooper@gmail.com>
Jonathan 4 роки тому
батько
коміт
88a74a177c

+ 16 - 7
backend/logic/actions/users.js

@@ -1311,11 +1311,13 @@ module.exports = {
      * Updates a user's password
      *
      * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} previousPassword - the previous password
      * @param {String} newPassword - the new password
      * @param {Function} cb - gets called with the result
      */
-    updatePassword: hooks.loginRequired(async (session, newPassword, cb) => {
+    updatePassword: hooks.loginRequired(async (session, previousPassword, newPassword, cb) => {
         const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+
         async.waterfall(
             [
                 (next) => {
@@ -1327,13 +1329,20 @@ module.exports = {
                         return next(
                             "This account does not have a password set."
                         );
-                    next();
+                    return next(null, user.services.password.password);
+                },
+
+                (storedPassword, next) => {
+                    bcrypt.compare(sha256(previousPassword), storedPassword).then(res => {
+                        if (res) return next();
+                        else return next("Please enter the correct previous password.")
+                    });
                 },
 
                 (next) => {
                     if (!db.passwordValid(newPassword))
                         return next(
-                            "Invalid password. Check if it meets all the requirements."
+                            "Invalid new password. Check if it meets all the requirements."
                         );
                     return next();
                 },
@@ -1475,7 +1484,7 @@ module.exports = {
             [
                 (next) => {
                     if (!code || typeof code !== "string")
-                        return next("Invalid code1.");
+                        return next("Invalid code.");
                     userModel.findOne(
                         {
                             "services.password.set.code": code,
@@ -1486,7 +1495,7 @@ module.exports = {
                 },
 
                 (user, next) => {
-                    if (!user) return next("Invalid code2.");
+                    if (!user) return next("Invalid code.");
                     if (user.services.password.set.expires < new Date())
                         return next("That code has expired.");
                     next(null);
@@ -1533,7 +1542,7 @@ module.exports = {
                 [
                     (next) => {
                         if (!code || typeof code !== "string")
-                            return next("Invalid code1.");
+                            return next("Invalid code.");
                         userModel.findOne(
                             { "services.password.set.code": code },
                             next
@@ -1541,7 +1550,7 @@ module.exports = {
                     },
 
                     (user, next) => {
-                        if (!user) return next("Invalid code2.");
+                        if (!user) return next("Invalid code.");
                         if (!user.services.password.set.expires > new Date())
                             return next("That code has expired.");
                         next();

+ 1 - 4
frontend/src/main.js

@@ -97,10 +97,7 @@ const router = new VueRouter({
 		},
 		{
 			path: "/reset_password",
-			component: () => import("./pages/ResetPassword.vue"),
-			meta: {
-				loginRequired: true
-			}
+			component: () => import("./pages/ResetPassword.vue")
 		},
 		{
 			path: "/set_password",

+ 2 - 32
frontend/src/pages/Settings/index.vue

@@ -96,7 +96,7 @@ export default {
 				);
 
 				this.socket.on("event:user.unlinkPassword", () =>
-					this.updateOriginalUser("github", false)
+					this.updateOriginalUser("password", false)
 				);
 
 				this.socket.on("event:user.linkGithub", () =>
@@ -109,37 +109,7 @@ export default {
 			});
 		}
 	},
-	methods: {
-		// changePassword() {
-		// 	const { newPassword } = this;
-		// 	if (!validation.isLength(newPassword, 6, 200))
-		// 		return new Toast({
-		// 			content: "Password must have between 6 and 200 characters.",
-		// 			timeout: 8000
-		// 		});
-		// 	if (!validation.regex.password.test(newPassword))
-		// 		return new Toast({
-		// 			content:
-		// 				"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character.",
-		// 			timeout: 8000
-		// 		});
-
-		// 	return this.socket.emit(
-		// 		"users.updatePassword",
-		// 		newPassword,
-		// 		res => {
-		// 			if (res.status !== "success")
-		// 				new Toast({ content: res.message, timeout: 8000 });
-		// 			else
-		// 				new Toast({
-		// 					content: "Successfully changed password",
-		// 					timeout: 4000
-		// 				});
-		// 		}
-		// 	);
-		// },
-		...mapActions("settings", ["updateOriginalUser", "setUser"])
-	}
+	methods: mapActions("settings", ["updateOriginalUser", "setUser"])
 };
 </script>
 

+ 154 - 35
frontend/src/pages/Settings/tabs/Security.vue

@@ -1,56 +1,94 @@
 <template>
 	<div class="content security-tab">
-		<div v-if="!isPasswordLinked || (isPasswordLinked && isGithubLinked)">
-			<h4 class="section-title" v-if="!isPasswordLinked">
-				Set a password
-			</h4>
-			<h4 class="section-title" v-else>
-				Remove password
+		<div v-if="isPasswordLinked">
+			<h4 class="section-title">
+				Change password
 			</h4>
 
-			<p class="section-description" v-if="!isPasswordLinked">
-				Set a password, as an alternative to signing in with GitHub.
+			<p class="section-description">
+				To change your password, you will need to know your previous
+				password.
+			</p>
+
+			<br />
+
+			<p class="control is-expanded">
+				<label for="previous-password">Previous password</label>
+				<input
+					class="input"
+					id="previous-password"
+					type="password"
+					placeholder="Enter your old password here..."
+					v-model="previousPassword"
+				/>
 			</p>
-			<p class="section-description" v-else>
-				Remove password from your Musare account.
+
+			<label for="new-password">New password</label>
+			<div class="control is-grouped input-with-button">
+				<p id="new-password-again-input" class="control is-expanded">
+					<input
+						class="input"
+						id="new-password"
+						type="password"
+						placeholder="Enter new password here..."
+						v-model="validation.newPassword.value"
+						@keyup.enter="changePassword()"
+						@blur="onInputBlur('newPassword')"
+					/>
+				</p>
+				<p class="control">
+					<a
+						id="change-password-button"
+						class="button is-success"
+						href="#"
+						@click.prevent="changePassword()"
+					>
+						Change password</a
+					>
+				</p>
+			</div>
+			<p
+				class="help"
+				v-if="validation.newPassword.entered"
+				:class="
+					validation.newPassword.valid ? 'is-success' : 'is-danger'
+				"
+			>
+				{{ validation.newPassword.message }}
+			</p>
+
+			<hr style="margin: 30px 0;" />
+		</div>
+
+		<div v-if="!isPasswordLinked">
+			<h4 class="section-title">
+				Add a password
+			</h4>
+			<p class="section-description">
+				Add a password, as an alternative to signing in with GitHub.
 			</p>
 
 			<br />
 
-			<router-link
-				v-if="!isPasswordLinked"
-				to="/set_password"
-				class="button is-default"
-				href="#"
+			<router-link to="/set_password" class="button is-default" href="#"
 				><i class="material-icons icon-with-button">create</i>Set
 				Password
 			</router-link>
 
-			<a
-				v-else
-				class="button is-danger"
-				href="#"
-				@click.prevent="unlinkPassword()"
-				><i class="material-icons icon-with-button">link_off</i>Remove
-				logging in with password
-			</a>
-
 			<hr style="margin: 30px 0;" />
 		</div>
 
-		<div v-if="!isGithubLinked || (isPasswordLinked && isGithubLinked)">
+		<div v-if="!isGithubLinked">
 			<h4 class="section-title">
-				{{ isGithubLinked ? "Unlink" : "Link" }} GitHub
+				Link GitHub
 			</h4>
 			<p class="section-description">
-				{{ isGithubLinked ? "Unlink" : "Link" }} your Musare account
-				with GitHub.
+				Link your Musare account with GitHub.
 			</p>
 
 			<br />
 
 			<a
-				v-if="!isGithubLinked"
 				class="button is-github"
 				:href="`${serverDomain}/auth/github/link`"
 			>
@@ -60,13 +98,31 @@
 				&nbsp; Link GitHub to account
 			</a>
 
+			<hr style="margin: 30px 0;" />
+		</div>
+
+		<div v-if="isPasswordLinked && isGithubLinked">
+			<h4 class="section-title">
+				Remove login methods
+			</h4>
+			<p class="section-description">
+				Remove your password as a login method or unlink GitHub.
+			</p>
+
+			<br />
+
 			<a
-				v-else
+				v-if="isPasswordLinked"
 				class="button is-danger"
 				href="#"
-				@click.prevent="unlinkGitHub()"
+				@click.prevent="unlinkPassword()"
+				><i class="material-icons icon-with-button">close</i>Remove
+				password
+			</a>
+
+			<a class="button is-danger" href="#" @click.prevent="unlinkGitHub()"
 				><i class="material-icons icon-with-button">link_off</i>Remove
-				logging in with GitHub
+				GitHub from account
 			</a>
 
 			<hr style="margin: 30px 0;" />
@@ -75,7 +131,7 @@
 		<div>
 			<h4 class="section-title">Log out everywhere</h4>
 			<p class="section-description">
-				Remove all sessions for your account.
+				Remove all currently logged-in sessions for your account.
 			</p>
 
 			<br />
@@ -96,11 +152,21 @@ import Toast from "toasters";
 import { mapGetters, mapState } from "vuex";
 
 import io from "../../../io";
+import validation from "../../../validation";
 
 export default {
 	data() {
 		return {
-			serverDomain: ""
+			serverDomain: "",
+			previousPassword: "",
+			validation: {
+				newPassword: {
+					value: "",
+					valid: false,
+					entered: false,
+					message: "Please enter a valid password."
+				}
+			}
 		};
 	},
 	computed: {
@@ -122,6 +188,43 @@ export default {
 		});
 	},
 	methods: {
+		onInputBlur(inputName) {
+			this.validation[inputName].entered = true;
+		},
+		changePassword() {
+			const newPassword = this.validation.newPassword.value;
+
+			if (this.previousPassword === "")
+				return new Toast({
+					content: "Please enter a previous password.",
+					timeout: 8000
+				});
+
+			if (!this.validation.newPassword.valid)
+				return new Toast({
+					content: "Please enter a valid new password.",
+					timeout: 8000
+				});
+
+			return this.socket.emit(
+				"users.updatePassword",
+				this.previousPassword,
+				newPassword,
+				res => {
+					if (res.status !== "success")
+						new Toast({ content: res.message, timeout: 8000 });
+					else {
+						this.previousPassword = "";
+						this.validation.newPassword.value = "";
+
+						new Toast({
+							content: "Successfully changed password.",
+							timeout: 4000
+						});
+					}
+				}
+			);
+		},
 		unlinkPassword() {
 			this.socket.emit("users.unlinkPassword", res => {
 				new Toast({ content: res.message, timeout: 8000 });
@@ -130,7 +233,6 @@ export default {
 		unlinkGitHub() {
 			this.socket.emit("users.unlinkGitHub", res => {
 				new Toast({ content: res.message, timeout: 8000 });
-				console.log("hi");
 			});
 		},
 		removeSessions() {
@@ -138,6 +240,23 @@ export default {
 				new Toast({ content: res.message, timeout: 4000 });
 			});
 		}
+	},
+	watch: {
+		// eslint-disable-next-line func-names
+		"validation.newPassword.value": function(value) {
+			if (!validation.isLength(value, 6, 200)) {
+				this.validation.newPassword.message =
+					"Password must have between 6 and 200 characters.";
+				this.validation.newPassword.valid = false;
+			} else if (!validation.regex.password.test(value)) {
+				this.validation.newPassword.message =
+					"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character.";
+				this.validation.newPassword.valid = false;
+			} else {
+				this.validation.newPassword.message = "Everything looks great!";
+				this.validation.newPassword.valid = true;
+			}
+		}
 	}
 };
 </script>