Parcourir la source

Added hooks and fixed security exploit.

KrisVos130 il y a 8 ans
Parent
commit
3f6b2754bd

+ 24 - 0
backend/logic/actions/hooks/adminRequired.js

@@ -0,0 +1,24 @@
+const cache = require('../../cache');
+const db = require('../../db');
+
+module.exports = function(next) {
+	return function(sessionId) {
+		let args = [];
+		for (let prop in arguments) {
+			args.push(arguments[prop]);
+		}
+		let cb = args[args.length - 1];
+		cache.hget('sessions', sessionId, (err, session) => {
+			if (err || !session || !session.userSessionId) return cb({ status: 'failure', message: 'Login required.' });
+			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
+				if (err || !userSession || !userSession.userId) return cb({ status: 'failure', message: 'Login required.' });
+				db.models.user.findOne({_id: userSession.userId}, (err, user) => {
+					if (err || !user) return cb({ status: 'failure', message: 'Login required.' });
+					if (user.role !== 'admin') return cb({ status: 'failure', message: 'Admin required.' });
+					args.push(userSession.userId);
+					next.apply(null, args);
+				});
+			});
+		});
+	}
+};

+ 6 - 0
backend/logic/actions/hooks/index.js

@@ -0,0 +1,6 @@
+'use strict';
+
+module.exports = {
+	loginRequired: require('./loginRequired'),
+	adminRequired: require('./adminRequired')
+};

+ 19 - 0
backend/logic/actions/hooks/loginRequired.js

@@ -0,0 +1,19 @@
+const cache = require('../../cache');
+
+module.exports = function(next) {
+	return function(sessionId) {
+		let args = [];
+		for (let prop in arguments) {
+			args.push(arguments[prop]);
+		}
+		let cb = args[args.length - 1];
+		cache.hget('sessions', sessionId, (err, session) => {
+			if (err || !session || !session.userSessionId) return cb({ status: 'failure', message: 'Login required.' });
+			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
+				if (err || !userSession || !userSession.userId) return cb({ status: 'failure', message: 'Login required.' });
+				args.push(userSession.userId);
+				next.apply(null, args);
+			});
+		});
+	}
+};

+ 9 - 11
backend/logic/actions/queueSongs.js

@@ -7,6 +7,7 @@ const cache = require('../cache');
 const async = require('async');
 const config = require('config');
 const request = require('request');
+const hooks = require('./hooks');
 
 notifications.subscribe('queue.newSong', songId => {
 	io.to('admin.queue').emit('event:song.new', { songId });
@@ -23,16 +24,14 @@ notifications.subscribe('queue.updatedSong', songId => {
 
 module.exports = {
 
-	index: (session, cb) => {
-		//TODO Require admin/login
+	index: hooks.adminRequired((session, cb) => {
 		db.models.queueSong.find({}, (err, songs) => {
 			if (err) throw err;
 			cb(songs);
 		});
-	},
+	}),
 
-	update: (session, _id, updatedSong, cb) => {
-		//TODO Require admin/login
+	update: hooks.adminRequired((session, _id, updatedSong, cb) => {
 		//TODO Check if id and updatedSong is valid
 		db.models.queueSong.findOne({ _id }, (err, currentSong) => {
 			if (err) throw err;
@@ -47,16 +46,15 @@ module.exports = {
 				});
 			}
 		});
-	},
+	}),
 
-	remove: (session, _id, cb) => {
+	remove: hooks.adminRequired((session, _id, cb) => {
 		// TODO Require admin/login
 		db.models.queueSong.find({ _id }).remove().exec();
 		return cb({ status: 'success', message: 'Song was removed successfully' });
-	},
+	}),
 
-	add: (session, id, cb) => {
-		//TODO Require login
+	add: hooks.loginRequired((session, id, cb) => {
 		//TODO Check if id is valid
 		//TODO Check if id is already in queue/rotation
 		// if (!session.logged_in) return cb({ status: 'failure', message: 'You must be logged in to add a song' });
@@ -184,6 +182,6 @@ module.exports = {
 			cache.pub('queue.newSong', newSong._id);
 			return cb({ status: 'success', message: 'Successfully added that song to the queue' });
 		});
-	}
+	})
 
 };

+ 5 - 4
backend/logic/actions/reports.js

@@ -1,18 +1,19 @@
 'use strict';
 
 const db = require('../db');
+const hooks = require('./hooks');
 
 module.exports = {
 
-	index: (session, cb) => {
+	index: hooks.adminRequired((session, cb) => {
 		db.models.reports.find({}).sort({ released: 'desc' }).exec((err, reports) => {
 			if (err) throw err;
 			else cb({ status: 'success', data: reports });
 		});
-	},
+	}),
 
-	add: (session, report, cb) => {
+	add: hooks.loginRequired((session, report, cb) => {
 		console.log(report);
-	}
+	})
 
 };

+ 32 - 40
backend/logic/actions/songs.js

@@ -5,6 +5,7 @@ const io = require('../io');
 const songs = require('../songs');
 const cache = require('../cache');
 const utils = require('../utils');
+const hooks = require('./hooks');
 
 cache.sub('song.like', (data) => {
 	io.io.to(`song.${data.songId}`).emit('event:song.like', {songId: data.songId, undisliked: data.undisliked});
@@ -51,20 +52,19 @@ module.exports = {
 		});
 	},
 
-	update: (session, id, song, cb) => {
-		//TODO Require admin/login
+	update: hooks.adminRequired((session, id, song, cb) => {
 		db.models.song.findOneAndUpdate({ id }, song, { upsert: true }, (err, updatedSong) => {
 			if (err) throw err;
 			return cb({ status: 'success', message: 'Song has been successfully updated', data: updatedSong });
 		});
-	},
+	}),
 
-	remove: (session, _id, cb) => {
+	remove: hooks.adminRequired((session, _id, cb) => {
 		//TODO Require admin/login
 		db.models.song.find({ _id }).remove().exec();
-	},
+	}),
 
-	add: (session, song, cb) => {
+	add: hooks.adminRequired((session, song, cb) => {
 		//TODO Require admin/login
 		const newSong = new db.models.song(song);
 		db.models.song.findOne({ _id: song._id }, (err, existingSong) => {
@@ -75,9 +75,9 @@ module.exports = {
 			});
 		});
 		//TODO Check if video is in queue and Add the song to the appropriate stations
-	},
+	}),
 
-	like: (sessionId, songId, cb) => {
+	like: hooks.loginRequired((sessionId, songId, cb) => {
 		cache.hget('sessions', sessionId, (err, session) => {
 			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
 				db.models.user.findOne({ _id: userSession.userId }, (err, user) => {
@@ -102,9 +102,9 @@ module.exports = {
 				});
 			});
 		});
-	},
+	}),
 
-	dislike: (sessionId, songId, cb) => {
+	dislike: hooks.loginRequired((sessionId, songId, cb) => {
 		cache.hget('sessions', sessionId, (err, session) => {
 			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
 				db.models.user.findOne({_id: userSession.userId}, (err, user) => {
@@ -128,9 +128,9 @@ module.exports = {
 				});
 			});
 		});
-	},
+	}),
 
-	undislike: (sessionId, songId, cb) => {
+	undislike: hooks.loginRequired((sessionId, songId, cb) => {
 		cache.hget('sessions', sessionId, (err, session) => {
 			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
 				db.models.user.findOne({_id: userSession.userId}, (err, user) => {
@@ -152,9 +152,9 @@ module.exports = {
 				});
 			});
 		});
-	},
+	}),
 
-	unlike: (sessionId, songId, cb) => {
+	unlike: hooks.loginRequired((sessionId, songId, cb) => {
 		cache.hget('sessions', sessionId, (err, session) => {
 			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
 				db.models.user.findOne({_id: userSession.userId}, (err, user) => {
@@ -176,33 +176,25 @@ module.exports = {
 				});
 			});
 		});
-	},
-
-	getOwnSongRatings: (sessionId, songId, cb) => {
-		cache.hget('sessions', sessionId, (err, session) => {
-			cache.hget('userSessions', session.userSessionId, (err, userSession) => {
-				if (!err && userSession) {
-					db.models.user.findOne({_id: userSession.userId}, (err, user) => {
-						console.log({
-							status: 'success',
-							songId: songId,
-							liked: (user.liked.indexOf(songId) !== -1),
-							disliked: (user.disliked.indexOf(songId) !== -1)
-						})
-						console.log(user.liked)
-						console.log(user.disliked)
-						return cb({
-							status: 'success',
-							songId: songId,
-							liked: (user.liked.indexOf(songId) !== -1),
-							disliked: (user.disliked.indexOf(songId) !== -1)
-						});
-					});
-				} else {
-					return cb({ status: 'failure', message: 'Not logged in.' });
-				}
+	}),
+
+	getOwnSongRatings: hooks.loginRequired(function(sessionId, songId, cb, userId) {
+		db.models.user.findOne({_id: userId}, (err, user) => {
+			console.log({
+				status: 'success',
+				songId: songId,
+				liked: (user.liked.indexOf(songId) !== -1),
+				disliked: (user.disliked.indexOf(songId) !== -1)
+			})
+			console.log(user.liked)
+			console.log(user.disliked)
+			return cb({
+				status: 'success',
+				songId: songId,
+				liked: (user.liked.indexOf(songId) !== -1),
+				disliked: (user.disliked.indexOf(songId) !== -1)
 			});
 		});
-	},
+	})
 
 };

+ 17 - 19
backend/logic/actions/stations.js

@@ -11,6 +11,7 @@ const notifications = require('../notifications');
 const utils = require('../utils');
 const stations = require('../stations');
 const songs = require('../songs');
+const hooks = require('./hooks');
 
 cache.sub('station.locked', stationId => {
 	io.io.to(`station.${stationId}`).emit("event:stations.locked");
@@ -120,7 +121,7 @@ module.exports = {
 	 * @param cb
 	 * @return {{ status: String, skipCount: Integer }}
 	 */
-	skip: (session, stationId, cb) => {
+	/*skip: (session, stationId, cb) => {
 
 		if (!session) return cb({ status: 'failure', message: 'You must be logged in to skip a song!' });
 
@@ -146,9 +147,9 @@ module.exports = {
 				cb({ status: 'failure', message: `That station doesn't exist` });
 			}
 		});
-	},
+	},*/
 
-	forceSkip: (session, stationId, cb) => {
+	forceSkip: hooks.adminRequired((session, stationId, cb) => {
 		//TODO Require admin
 
 		stations.initializeAndReturnStation(stationId, (err, station) => {
@@ -165,7 +166,7 @@ module.exports = {
 				cb({ status: 'failure', message: `That station doesn't exist` });
 			}
 		});
-	},
+	}),
 
 	/**
 	 * Leaves the users current station
@@ -195,8 +196,7 @@ module.exports = {
 		});
 	},
 
-	lock: (sessionId, stationId, cb) => {
-		//TODO Require admin
+	lock: hooks.adminRequired((sessionId, stationId, cb) => {
 		stations.initializeAndReturnStation(stationId, (err, station) => {
 			if (err && err !== true) {
 				return cb({ status: 'error', message: 'An error occurred while locking the station' });
@@ -207,10 +207,9 @@ module.exports = {
 				cb({ status: 'failure', message: `That station doesn't exist, it may have been deleted` });
 			}
 		});
-	},
+	}),
 
-	unlock: (sessionId, stationId, cb) => {
-		//TODO Require admin
+	unlock: hooks.adminRequired((sessionId, stationId, cb) => {
 		stations.initializeAndReturnStation(stationId, (err, station) => {
 			if (err && err !== true) {
 				return cb({ status: 'error', message: 'An error occurred while unlocking the station' });
@@ -221,9 +220,9 @@ module.exports = {
 				cb({ status: 'failure', message: `That station doesn't exist, it may have been deleted` });
 			}
 		});
-	},
+	}),
 
-	pause: (sessionId, stationId, cb) => {
+	pause: hooks.adminRequired((sessionId, stationId, cb) => {
 		//TODO Require admin
 		stations.initializeAndReturnStation(stationId, (err, station) => {
 			if (err && err !== true) {
@@ -251,9 +250,9 @@ module.exports = {
 				cb({ status: 'failure', message: `That station doesn't exist, it may have been deleted` });
 			}
 		});
-	},
+	}),
 
-	resume: (sessionId, stationId, cb) => {
+	resume: hooks.adminRequired((sessionId, stationId, cb) => {
 		//TODO Require admin
 		stations.initializeAndReturnStation(stationId, (err, station) => {
 			if (err && err !== true) {
@@ -280,17 +279,16 @@ module.exports = {
 				cb({ status: 'failure', message: `That station doesn't exist, it may have been deleted` });
 			}
 		});
-	},
+	}),
 
-	remove: (sessionId, _id, cb) => {
+	remove: hooks.adminRequired((sessionId, _id, cb) => {
 		db.models.station.find({ _id }).remove().exec();
 		cache.hdel('stations', _id, () => {
 			return cb({ status: 'success', message: 'Station successfully removed' });
 		});
-	},
+	}),
 
-	create: (sessionId, data, cb) => {
-		//TODO Require admin
+	create: hooks.adminRequired((sessionId, data, cb) => {
 		async.waterfall([
 
 			(next) => {
@@ -327,6 +325,6 @@ module.exports = {
 				return cb(null, { 'status': 'success', 'message': 'Successfully created station.' });
 			});
 		});
-	},
+	}),
 
 };

+ 6 - 5
backend/logic/actions/users.js

@@ -8,6 +8,7 @@ const bcrypt = require('bcrypt');
 const db = require('../db');
 const cache = require('../cache');
 const utils = require('../utils');
+const hooks = require('./hooks');
 
 module.exports = {
 
@@ -211,12 +212,12 @@ module.exports = {
 
 	},
 
-	update: (sessionId, user_id, property, value, cb) => {
-        db.models.user.findOne({ _id: user_id }, (err, user) => {
+	update: hooks.loginRequired((sessionId, user_id, property, value, cb, userId) => {
+        db.models.user.findOne({ _id: userId }, (err, user) => {
             if (err) throw err;
             else if (!user) cb({ status: 'error', message: 'Invalid User ID' });
             else if (user[property] !== undefined && user[property] !== value) {
-                if (property == 'services.password.password') {
+                if (property === 'services.password.password') {
                     bcrypt.compare(user[property], value, (err, res) => {
                         if (err) throw err;
                         bcrypt.genSalt(10, (err, salt) => {
@@ -227,7 +228,7 @@ module.exports = {
                             });
                         });
                     });
-                } else user[property] = value;
+                } else if (property === 'email.address') user[property] = value;
                 user.save(err => {
                     if (err) cb({ status: 'error', message: err.message });
 					else  cb({ status: 'success', message: 'Field saved successfully' });
@@ -236,7 +237,7 @@ module.exports = {
                 cb({ status: 'error', message: 'Field has not changed' });
             }
         });
-    },
+    }),
 
 
 };

+ 3 - 3
frontend/components/User/Settings.vue

@@ -83,7 +83,7 @@
 			changePassword: function () {
 				if (this.currentPassword == "" || this.newPassword == "") return Toast.methods.addToast('Current password field is incorrect', 2000);
 
-				this.socket.emit('users.update', this.user._id, 'services.password.password', this.user.password, res => {
+				this.socket.emit('users.update', 'services.password.password', this.user.password, res => {
 					if (res.status == 'error') Toast.methods.addToast(res.message, 2000);
 					else Toast.methods.addToast('Successfully changed password', 2000);
 				});
@@ -91,7 +91,7 @@
 			changeEmail: function () {
 				if (this.user.email == "") return Toast.methods.addToast('Field cannot be empty', 2000);
 
-				this.socket.emit('users.update', this.user._id, 'email.address', this.user.email, res => {
+				this.socket.emit('users.update', 'email.address', this.user.email, res => {
 					if (res.status == 'error') Toast.methods.addToast(res.message, 2000);
 					else Toast.methods.addToast('Successfully changed email address', 2000);
 				});
@@ -118,7 +118,7 @@
 			changeUsername: function () {
 				if (this.user.username == "") return Toast.methods.addToast('Field cannot be empty', 2000);
 
-				this.socket.emit('users.update', this.user._id, 'username', this.user.username, res => {
+				this.socket.emit('users.update', 'username', this.user.username, res => {
 					if (res.status == 'error') Toast.methods.addToast(res.message, 2000);
 					else Toast.methods.addToast('Successfully changed username', 2000);
 				});

+ 1 - 1
frontend/components/User/Show.vue

@@ -46,7 +46,7 @@
 		},
 		methods: {
 			changeRank(newRank) {
-				this.socket.emit('users.update', this.user._id, 'role', ((newRank == 'admin') ? 'admin' : 'default'), res => {
+				this.socket.emit('users.update', 'role', ((newRank == 'admin') ? 'admin' : 'default'), res => {
 					if (res.status == 'error') Toast.methods.addToast(res.message, 2000);
 					else this.user.role = newRank; Toast.methods.addToast(`User ${this.$route.params.username}'s rank has been changed to: ${newRank}`, 2000);
 				});