Bladeren bron

Sessions for a user can now be removed by admins
- More work on removing all sessions for a user

theflametrooper 8 jaren geleden
bovenliggende
commit
1d17e6b7a4
6 gewijzigde bestanden met toevoegingen van 100 en 5 verwijderingen
  1. 1 0
      .env
  2. 69 2
      backend/logic/actions/users.js
  3. 14 1
      backend/logic/utils.js
  4. 6 0
      frontend/App.vue
  5. 9 1
      frontend/components/Modals/EditUser.vue
  6. 1 1
      frontend/io.js

+ 1 - 0
.env

@@ -0,0 +1 @@
+REDIS_PASSWORD=PASSWORD

+ 69 - 2
backend/logic/actions/users.js

@@ -22,6 +22,14 @@ cache.sub('user.updateUsername', user => {
 	});
 	});
 });
 });
 
 
+cache.sub('user.removeSessions', userId => {
+	utils.socketsFromUserWithoutCache(userId, sockets => {
+		sockets.forEach(socket => {
+			socket.emit('keep.event:user.session.removed');
+		});
+	});
+});
+
 cache.sub('user.linkPassword', userId => {
 cache.sub('user.linkPassword', userId => {
 	console.log("LINK4", userId);
 	console.log("LINK4", userId);
 	utils.socketsFromUser(userId, sockets => {
 	utils.socketsFromUser(userId, sockets => {
@@ -281,15 +289,74 @@ module.exports = {
 			if (err && err !== true) {
 			if (err && err !== true) {
 				err = utils.getError(err);
 				err = utils.getError(err);
 				logger.error("USER_LOGOUT", `Logout failed. "${err}" `);
 				logger.error("USER_LOGOUT", `Logout failed. "${err}" `);
-				cb({status: 'failure', message: err});
+				cb({ status: 'failure', message: err });
 			} else {
 			} else {
 				logger.success("USER_LOGOUT", `Logout successful.`);
 				logger.success("USER_LOGOUT", `Logout successful.`);
-				cb({status: 'success', message: 'Successfully logged out.'});
+				cb({ status: 'success', message: 'Successfully logged out.' });
 			}
 			}
 		});
 		});
 
 
 	},
 	},
 
 
+	/**
+	 * Removes all sessions for a user
+	 *
+	 * @param {Object} session - the session object automatically added by socket.io
+	 * @param {String} userId - the id of the user we are trying to delete the sessions of
+	 * @param {Function} cb - gets called with the result
+	 * @param {String} loggedInUser - the logged in userId automatically added by hooks
+	 */
+	removeSessions:  hooks.loginRequired((session, userId, cb, loggedInUser) => {
+
+		async.waterfall([
+
+			(next) => {
+				db.models.user.findOne({ _id: loggedInUser }, (err, user) => {
+					if (user.role !== 'admin' && loggedInUser !== userId) return next('Only admins and the owner of the account can remove their sessions.');
+					else return next();
+				});
+			},
+
+			(next) => {
+				cache.hgetall('sessions', next);
+			},
+
+			(sessions, next) => {
+				if (!sessions) return next('There are no sessions for this user to remove.');
+				else {
+					let keys = Object.keys(sessions);
+					next(null, keys, sessions);
+				}
+			},
+
+			(keys, sessions, next) => {
+				cache.pub('user.removeSessions', userId);
+				async.each(keys, (sessionId, callback) => {
+					let session = sessions[sessionId];
+					if (session.userId === userId) {
+						cache.hdel('sessions', sessionId, err => {
+							if (err) return callback(err);
+							else callback(null);
+						});
+					}
+				}, err => {
+					next(err);
+				});
+			}
+
+		], err => {
+			if (err) {
+				err = utils.getError(err);
+				logger.error("REMOVE_SESSIONS_FOR_USER", `Couldn't remove all sessions for user "${userId}". "${err}"`);
+				return cb({ status: 'failure', message: err });
+			} else {
+				logger.success("REMOVE_SESSIONS_FOR_USER", `Removed all sessions for user "${userId}".`);
+				return cb({ status: 'success', message: 'Successfully removed all sessions.' });
+			}
+		});
+
+	}),
+
 	/**
 	/**
 	 * Gets user object from username (only a few properties)
 	 * Gets user object from username (only a few properties)
 	 *
 	 *

+ 14 - 1
backend/logic/utils.js

@@ -173,6 +173,19 @@ module.exports = {
 			});
 			});
 		}
 		}
 	},
 	},
+	socketsFromUserWithoutCache: function(userId, cb) {
+		let ns = io.io.of("/");
+		let sockets = [];
+		if (ns) {
+			async.each(Object.keys(ns.connected), (id, next) => {
+				let session = ns.connected[id].session;
+				if (session.userId === userId) sockets.push(ns.connected[id]);
+				next();
+			}, () => {
+				cb(sockets);
+			});
+		}
+	},
 	socketLeaveRooms: function(socketid) {
 	socketLeaveRooms: function(socketid) {
 		let socket = this.socketFromSession(socketid);
 		let socket = this.socketFromSession(socketid);
 		let rooms = socket.rooms;
 		let rooms = socket.rooms;
@@ -295,7 +308,7 @@ module.exports = {
 		}
 		}
 	},
 	},
 	getPlaylistFromYouTube: (url, cb) => {
 	getPlaylistFromYouTube: (url, cb) => {
-		
+
 		let name = 'list'.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
 		let name = 'list'.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
 		var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
 		var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
 		let playlistId = regex.exec(url)[1];
 		let playlistId = regex.exec(url)[1];

+ 6 - 0
frontend/App.vue

@@ -88,6 +88,12 @@
 				err = err.replace(new RegExp('<', 'g'), '&lt;').replace(new RegExp('>', 'g'), '&gt;');
 				err = err.replace(new RegExp('<', 'g'), '&lt;').replace(new RegExp('>', 'g'), '&gt;');
 				Toast.methods.addToast(err, 20000);
 				Toast.methods.addToast(err, 20000);
 			}
 			}
+			io.getSocket(true, socket => {
+				socket.on('keep.event:user.session.removed', () => {
+					location.reload();
+				});
+			});
+
 		},
 		},
 		events: {
 		events: {
 			'register': function (recaptchaId) {
 			'register': function (recaptchaId) {

+ 9 - 1
frontend/components/Modals/EditUser.vue

@@ -32,6 +32,9 @@
 				<button class='button is-warning'>
 				<button class='button is-warning'>
 					<span>&nbsp;Send Password Reset Email</span>
 					<span>&nbsp;Send Password Reset Email</span>
 				</button-->
 				</button-->
+				<button class='button is-warning' @click='removeSessions()'>
+					<span>&nbsp;Remove all sessions</span>
+				</button>
 				<button class='button is-danger' @click='$parent.toggleModal()'>
 				<button class='button is-danger' @click='$parent.toggleModal()'>
 					<span>&nbsp;Close</span>
 					<span>&nbsp;Close</span>
 				</button>
 				</button>
@@ -85,7 +88,7 @@
 					) location.reload();
 					) location.reload();
 				});
 				});
 			},
 			},
-			banUser: function() {
+			banUser: function () {
 				const reason = this.ban.reason;
 				const reason = this.ban.reason;
 				if (!validation.isLength(reason, 1, 64)) return Toast.methods.addToast('Reason must have between 1 and 64 characters.', 8000);
 				if (!validation.isLength(reason, 1, 64)) return Toast.methods.addToast('Reason must have between 1 and 64 characters.', 8000);
 				if (!validation.regex.ascii.test(reason)) return Toast.methods.addToast('Invalid reason format. Only ascii characters are allowed.', 8000);
 				if (!validation.regex.ascii.test(reason)) return Toast.methods.addToast('Invalid reason format. Only ascii characters are allowed.', 8000);
@@ -93,6 +96,11 @@
 				this.socket.emit(`users.banUserById`, this.editing._id, this.ban.reason, '1h', res => {
 				this.socket.emit(`users.banUserById`, this.editing._id, this.ban.reason, '1h', res => {
 					Toast.methods.addToast(res.message, 4000);
 					Toast.methods.addToast(res.message, 4000);
 				});
 				});
+			},
+			removeSessions: function () {
+				this.socket.emit(`users.removeSessions`, this.editing._id, res => {
+					Toast.methods.addToast(res.message, 4000);
+				});
 			}
 			}
 		},
 		},
 		ready: function () {
 		ready: function () {

+ 1 - 1
frontend/io.js

@@ -94,4 +94,4 @@ export default {
 		callbacks = [];
 		callbacks = [];
 		callbacksPersist = [];
 		callbacksPersist = [];
 	}
 	}
-}
+}