mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	
							
								
								
									
										43
									
								
								public/src/admin/advanced/events.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								public/src/admin/advanced/events.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| "use strict"; | ||||
| /* global define, socket, app, templates */ | ||||
|  | ||||
|  | ||||
| define('admin/advanced/events', ['forum/infinitescroll'], function(infinitescroll) { | ||||
| 	var	Events = {}; | ||||
|  | ||||
| 	Events.init = function() { | ||||
|  | ||||
| 		$('[data-action="clear"]').on('click', function() { | ||||
| 			socket.emit('admin.deleteAllEvents', function(err) { | ||||
| 				if (err) { | ||||
| 					return app.alertError(err.message); | ||||
| 				} | ||||
| 				$('.events-list').empty(); | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		infinitescroll.init(function(direction) { | ||||
| 			if (direction < 0 || !$('.events').length) { | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			infinitescroll.loadMore('admin.getMoreEvents', $('[data-next]').attr('data-next'), function(data, done) { | ||||
| 				console.log(data.events); | ||||
| 				if (data.events && data.events.length) { | ||||
| 					templates.parse('admin/advanced/events', 'events', {events: data.events}, function(html) { | ||||
| 						console.log(html); | ||||
| 						$('.events-list').append(html); | ||||
| 						done(); | ||||
| 					}); | ||||
|  | ||||
| 					$('[data-next]').attr('data-next', data.next); | ||||
| 				} else { | ||||
| 					done(); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 	}; | ||||
|  | ||||
| 	return Events; | ||||
| }); | ||||
| @@ -174,14 +174,14 @@ adminController.database.get = function(req, res, next) { | ||||
| }; | ||||
|  | ||||
| adminController.events.get = function(req, res, next) { | ||||
| 	events.getLog(-1, 5000, function(err, data) { | ||||
| 		if(err || !data) { | ||||
| 	events.getEvents(0, 19, function(err, events) { | ||||
| 		if(err || !events) { | ||||
| 			return next(err); | ||||
| 		} | ||||
|  | ||||
| 		res.render('admin/advanced/events', { | ||||
| 			eventdata: data.data, | ||||
| 			next: data.next | ||||
| 			events: events, | ||||
| 			next: 20 | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										232
									
								
								src/events.js
									
									
									
									
									
								
							
							
						
						
									
										232
									
								
								src/events.js
									
									
									
									
									
								
							| @@ -1,154 +1,128 @@ | ||||
|  | ||||
| 'use strict'; | ||||
|  | ||||
| var fs = require('fs'), | ||||
| 	winston = require('winston'), | ||||
| 	path = require('path'), | ||||
| 	nconf = require('nconf'), | ||||
| 	user = require('./user'); | ||||
| var async = require('async'), | ||||
|  | ||||
| 	db =  require('./database'), | ||||
| 	batch = require('./batch'), | ||||
| 	user = require('./user'), | ||||
| 	utils = require('../public/src/utils'); | ||||
|  | ||||
|  | ||||
| (function(events) { | ||||
| 	var logFileName = 'logs/events.log'; | ||||
| 	events.log = function(data, callback) { | ||||
| 		callback = callback || function() {}; | ||||
|  | ||||
| 	events.logPasswordChange = function(uid) { | ||||
| 		events.logWithUser(uid, 'changed password'); | ||||
| 	}; | ||||
| 		async.waterfall([ | ||||
| 			function(next) { | ||||
| 				db.incrObjectField('global', 'nextEid', next); | ||||
| 			}, | ||||
| 			function(eid, next) { | ||||
| 				data.timestamp = Date.now(); | ||||
| 				data.eid = eid; | ||||
|  | ||||
| 	events.logAdminChangeUserPassword = function(adminUid, theirUid, callback) { | ||||
| 		logAdminEvent(adminUid, theirUid, 'changed password of', callback); | ||||
| 	}; | ||||
|  | ||||
| 	events.logAdminUserDelete = function(adminUid, theirUid, callback) { | ||||
| 		logAdminEvent(adminUid, theirUid, 'deleted', callback); | ||||
| 	}; | ||||
|  | ||||
| 	function logAdminEvent(adminUid, theirUid, message, callback) { | ||||
| 		user.getMultipleUserFields([adminUid, theirUid], ['username'], function(err, userData) { | ||||
| 			if(err) { | ||||
| 				return winston.error('Error logging event. ' + err.message); | ||||
| 				async.parallel([ | ||||
| 					function(next) { | ||||
| 						db.sortedSetAdd('events:time', data.timestamp, eid, next); | ||||
| 					}, | ||||
| 					function(next) { | ||||
| 						db.setObject('event:' + eid, data, next); | ||||
| 					} | ||||
|  | ||||
| 			var msg = userData[0].username + '(uid ' + adminUid + ') ' + message + ' ' +  userData[1].username + '(uid ' + theirUid + ')'; | ||||
| 			events.log(msg, callback); | ||||
| 		}); | ||||
| 				], next); | ||||
| 			} | ||||
|  | ||||
| 	events.logPasswordReset = function(uid) { | ||||
| 		events.logWithUser(uid, 'reset password'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logEmailChange = function(uid, oldEmail, newEmail) { | ||||
| 		events.logWithUser(uid,'changed email from "' + oldEmail + '" to "' + newEmail +'"'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logUsernameChange = function(uid, oldUsername, newUsername) { | ||||
| 		events.logWithUser(uid,'changed username from "' + oldUsername + '" to "' + newUsername +'"'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logAdminLogin = function(uid) { | ||||
| 		events.logWithUser(uid, 'logged into admin panel'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logPostEdit = function(uid, pid) { | ||||
| 		events.logWithUser(uid, 'edited post (pid ' + pid + ')'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logPostDelete = function(uid, pid) { | ||||
| 		events.logWithUser(uid, 'deleted post (pid ' + pid + ')'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logPostRestore = function(uid, pid) { | ||||
| 		events.logWithUser(uid, 'restored post (pid ' + pid + ')'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logPostPurge = function(uid, pid) { | ||||
| 		events.logWithUser(uid, 'purged post (pid ' + pid + ')'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logTopicMove = function(uid, tid) { | ||||
| 		events.logWithUser(uid, 'moved topic (tid ' + tid + ')'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logTopicDelete = function(uid, tid) { | ||||
| 		events.logWithUser(uid, 'deleted topic (tid ' + tid + ')'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logTopicRestore = function(uid, tid) { | ||||
| 		events.logWithUser(uid, 'restored topic (tid ' + tid + ')'); | ||||
| 	}; | ||||
|  | ||||
| 	events.logAccountLock = function(uid, until) { | ||||
| 		var date = new Date(); | ||||
| 		date.setTime(date.getTime() + until); | ||||
|  | ||||
| 		events.logWithUser(uid, 'locked out until ' + date.toString()); | ||||
| 	}; | ||||
|  | ||||
| 	events.logWithUser = function(uid, string) { | ||||
| 		user.getUserField(uid, 'username', function(err, username) { | ||||
| 			if(err) { | ||||
| 				return winston.error('Error logging event. ' + err.message); | ||||
| 			} | ||||
|  | ||||
| 			var msg = username + '(uid ' + uid + ') ' + string; | ||||
| 			events.log(msg); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	events.log = function(msg, callback) { | ||||
| 		var logFile = path.join(nconf.get('base_dir'), logFileName); | ||||
|  | ||||
| 		msg = '[' + new Date().toUTCString() + '] - ' + msg; | ||||
|  | ||||
| 		fs.appendFile(logFile, msg + '\n', function(err) { | ||||
| 			if(err) { | ||||
| 				winston.error('Error logging event. ' + err.message); | ||||
| 				if (typeof callback === 'function') { | ||||
| 		], function(err, result) { | ||||
| 			callback(err); | ||||
| 				} | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			if (typeof callback === 'function') { | ||||
| 				callback(); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	events.getLog = function(end, len, callback) { | ||||
| 		var logFile = path.join(nconf.get('base_dir'), logFileName); | ||||
| 	events.getEvents = function(start, stop, callback) { | ||||
| 		async.waterfall([ | ||||
| 			function(next) { | ||||
| 				db.getSortedSetRevRange('events:time', start, stop, next); | ||||
| 			}, | ||||
| 			function(eids, next) { | ||||
| 				var keys = eids.map(function(eid) { | ||||
| 					return 'event:' + eid; | ||||
| 				}); | ||||
| 				db.getObjects(keys, next); | ||||
| 			}, | ||||
| 			function(eventsData, next) { | ||||
| 				eventsData.forEach(function(event) { | ||||
| 					var e = utils.merge(event); | ||||
| 					e.eid = e.uid = e.type = e.ip = undefined; | ||||
| 					event.jsonString = JSON.stringify(e, null, 4); | ||||
| 					event.timestampISO = new Date(parseInt(event.timestamp, 10)).toUTCString(); | ||||
| 				}); | ||||
| 				addUserData(eventsData, 'uid', 'user', next); | ||||
| 			}, | ||||
| 			function(eventsData, next) { | ||||
| 				addUserData(eventsData, 'targetUid', 'targetUser', next); | ||||
| 			} | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 		fs.stat(logFile, function(err, stat) { | ||||
| 	function addUserData(eventsData, field, objectName, callback) { | ||||
| 		var uids = eventsData.map(function(event) { | ||||
| 			return event && event[field]; | ||||
| 		}).filter(function(uid, index, array) { | ||||
| 			return uid && array.indexOf(uid) === index; | ||||
| 		}); | ||||
|  | ||||
| 		if (!uids.length) { | ||||
| 			return callback(null, eventsData); | ||||
| 		} | ||||
|  | ||||
| 		async.parallel({ | ||||
| 			isAdmin: function(next) { | ||||
| 				user.isAdministrator(uids, next); | ||||
| 			}, | ||||
| 			userData: function(next) { | ||||
| 				user.getMultipleUserFields(uids, ['username', 'userslug', 'picture'], next); | ||||
| 			} | ||||
| 		}, function(err, results) { | ||||
| 			if (err) { | ||||
| 				return callback(null, 'No logs found!'); | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			var buffer = ''; | ||||
| 			var size = stat.size; | ||||
| 			if (end === -1) { | ||||
| 				end = size; | ||||
| 			var userData = results.userData; | ||||
|  | ||||
| 			var map = {}; | ||||
| 			userData.forEach(function(user, index) { | ||||
| 				user.isAdmin = results.isAdmin[index]; | ||||
| 				map[user.uid] = user; | ||||
| 			}); | ||||
|  | ||||
| 			eventsData.forEach(function(event) { | ||||
| 				if (map[event[field]]) { | ||||
| 					event[objectName] = map[event[field]]; | ||||
| 				} | ||||
| 			}); | ||||
| 			callback(null, eventsData); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 			end = parseInt(end, 10); | ||||
| 			var start = Math.max(0, end - len); | ||||
|  | ||||
| 			var rs = fs.createReadStream(logFile, {start: start, end: end}); | ||||
| 			rs.addListener('data', function(lines) { | ||||
| 				buffer += lines.toString(); | ||||
| 	events.deleteEvents = function(eids, callback) { | ||||
| 		callback = callback || function() {}; | ||||
| 		async.parallel([ | ||||
| 			function(next) { | ||||
| 				var keys = eids.map(function(eid) { | ||||
| 					return 'event:' + eid; | ||||
| 				}); | ||||
|  | ||||
| 			rs.addListener('end', function() { | ||||
| 				var firstNewline = buffer.indexOf('\n'); | ||||
| 				if (firstNewline !== -1) { | ||||
| 					buffer = buffer.slice(firstNewline); | ||||
| 					buffer = buffer.split('\n').reverse().join('\n'); | ||||
| 				db.deleteAll(keys, next); | ||||
| 			}, | ||||
| 			function(next) { | ||||
| 				db.sortedSetRemove('events:time', eids, next); | ||||
| 			} | ||||
|  | ||||
| 				callback(null, {data: buffer, next: end - buffer.length}); | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		], callback); | ||||
| 	}; | ||||
|  | ||||
| 	events.deleteAll = function(callback) { | ||||
| 		callback = callback || function() {}; | ||||
|  | ||||
| 		batch.processSortedSet('events:time', function(eids, next) { | ||||
| 			events.deleteEvents(eids, callback); | ||||
| 		}, {alwaysStartAt: 0}, callback); | ||||
| 	}; | ||||
|  | ||||
|  | ||||
| }(module.exports)); | ||||
|   | ||||
| @@ -43,10 +43,6 @@ var async = require('async'), | ||||
|  | ||||
| 				db.sortedSetAdd('users:reputation', newreputation, postData.uid); | ||||
|  | ||||
| 				if (type === 'downvote') { | ||||
| 					banUserForLowReputation(postData.uid, newreputation); | ||||
| 				} | ||||
|  | ||||
| 				adjustPostVotes(pid, uid, type, unvote, function(err, votes) { | ||||
| 					postData.votes = votes; | ||||
| 					callback(err, { | ||||
| @@ -62,23 +58,6 @@ var async = require('async'), | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	function banUserForLowReputation(uid, newreputation) { | ||||
| 		if (parseInt(meta.config['autoban:downvote'], 10) === 1 && newreputation < parseInt(meta.config['autoban:downvote:threshold'], 10)) { | ||||
| 			user.getUserField(uid, 'banned', function(err, banned) { | ||||
| 				if (err || parseInt(banned, 10) === 1) { | ||||
| 					return; | ||||
| 				} | ||||
| 				var adminUser = require('./socket.io/admin/user'); | ||||
| 				adminUser.banUser(uid, function(err) { | ||||
| 					if (err) { | ||||
| 						return winston.error(err.message); | ||||
| 					} | ||||
| 					winston.info('uid ' + uid + ' was banned for reaching ' + newreputation + ' reputation'); | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	function adjustPostVotes(pid, uid, type, unvote, callback) { | ||||
| 		var notType = (type === 'upvote' ? 'downvote' : 'upvote'); | ||||
|  | ||||
|   | ||||
| @@ -311,8 +311,6 @@ var async = require('async'), | ||||
| 				if (process.env.NODE_ENV === 'development') { | ||||
| 					winston.info('[notifications.prune] Notification pruning completed. ' + numPruned + ' expired notification' + (numPruned !== 1 ? 's' : '') + ' removed.'); | ||||
| 				} | ||||
| 				var diff = process.hrtime(start); | ||||
| 				events.log('Pruning '+ numPruned + ' notifications took : ' + (diff[0] * 1e3 + diff[1] / 1e6) + ' ms'); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
|   | ||||
| @@ -106,7 +106,7 @@ var winston = require('winston'), | ||||
| 					return callback(err); | ||||
| 				} | ||||
| 				results.content = results.postData.content; | ||||
| 				//events.logPostEdit(uid, pid); | ||||
|  | ||||
| 				plugins.fireHook('action:post.edit', postData); | ||||
| 				callback(null, results); | ||||
| 			}); | ||||
| @@ -146,7 +146,6 @@ var winston = require('winston'), | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			events[isDelete ? 'logPostDelete' : 'logPostRestore'](uid, pid); | ||||
| 			if (isDelete) { | ||||
| 				posts.delete(pid, callback); | ||||
| 			} else { | ||||
| @@ -165,7 +164,7 @@ var winston = require('winston'), | ||||
| 			if (err || !canEdit) { | ||||
| 				return callback(err || new Error('[[error:no-privileges]]')); | ||||
| 			} | ||||
| 			events.logPostPurge(uid, pid); | ||||
|  | ||||
| 			posts.purge(pid, callback); | ||||
| 		}); | ||||
| 	}; | ||||
|   | ||||
| @@ -68,7 +68,7 @@ | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	Auth.login = function(username, password, next) { | ||||
| 	Auth.login = function(req, username, password, next) { | ||||
| 		if (!username || !password) { | ||||
| 			return next(new Error('[[error:invalid-password]]')); | ||||
| 		} | ||||
| @@ -85,7 +85,7 @@ | ||||
| 					return next(new Error('[[error:no-user]]')); | ||||
| 				} | ||||
| 				uid = _uid; | ||||
| 				user.auth.logAttempt(uid, next); | ||||
| 				user.auth.logAttempt(uid, req.ip, next); | ||||
| 			}, | ||||
| 			function(next) { | ||||
| 				db.getObjectFields('user:' + uid, ['password', 'banned'], next); | ||||
| @@ -109,7 +109,7 @@ | ||||
| 		], next); | ||||
| 	}; | ||||
|  | ||||
| 	passport.use(new passportLocal(Auth.login)); | ||||
| 	passport.use(new passportLocal({passReqToCallback: true}, Auth.login)); | ||||
|  | ||||
| 	passport.serializeUser(function(user, done) { | ||||
| 		done(null, user.uid); | ||||
|   | ||||
| @@ -49,7 +49,11 @@ SocketAdmin.before = function(socket, method, next) { | ||||
| }; | ||||
|  | ||||
| SocketAdmin.reload = function(socket, data, callback) { | ||||
| 	events.logWithUser(socket.uid, ' is reloading NodeBB'); | ||||
| 	events.log({ | ||||
| 		type: 'reload', | ||||
| 		uid: socket.uid, | ||||
| 		ip: socket.ip | ||||
| 	}); | ||||
| 	if (process.send) { | ||||
| 		process.send({ | ||||
| 			action: 'reload' | ||||
| @@ -60,7 +64,11 @@ SocketAdmin.reload = function(socket, data, callback) { | ||||
| }; | ||||
|  | ||||
| SocketAdmin.restart = function(socket, data, callback) { | ||||
| 	events.logWithUser(socket.uid, ' is restarting NodeBB'); | ||||
| 	events.log({ | ||||
| 		type: 'restart', | ||||
| 		uid: socket.uid, | ||||
| 		ip: socket.ip | ||||
| 	}); | ||||
| 	meta.restart(); | ||||
| }; | ||||
|  | ||||
| @@ -274,10 +282,21 @@ function getMonthlyPageViews(callback) { | ||||
| } | ||||
|  | ||||
| SocketAdmin.getMoreEvents = function(socket, next, callback) { | ||||
| 	if (parseInt(next, 10) < 0) { | ||||
| 	var start = parseInt(next, 10); | ||||
| 	if (start < 0) { | ||||
| 		return callback(null, {data: [], next: next}); | ||||
| 	} | ||||
| 	events.getLog(next, 5000, callback); | ||||
| 	var end = next + 10; | ||||
| 	events.getEvents(start, end, function(err, events) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
| 		callback(null, {events: events, next: end + 1}); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| SocketAdmin.deleteAllEvents = function(socket, data, callback) { | ||||
| 	events.deleteAll(callback); | ||||
| }; | ||||
|  | ||||
| SocketAdmin.dismissFlag = function(socket, pid, callback) { | ||||
|   | ||||
| @@ -157,7 +157,7 @@ User.deleteUsers = function(socket, uids, callback) { | ||||
| 	async.each(uids, function(uid, next) { | ||||
| 		user.isAdministrator(uid, function(err, isAdmin) { | ||||
| 			if (err || isAdmin) { | ||||
| 				return callback(err || new Error('[[error:cant-ban-other-admins]]')); | ||||
| 				return callback(err || new Error('[[error:cant-delete-other-admins]]')); | ||||
| 			} | ||||
|  | ||||
| 			user.delete(uid, function(err) { | ||||
| @@ -165,7 +165,12 @@ User.deleteUsers = function(socket, uids, callback) { | ||||
| 					return next(err); | ||||
| 				} | ||||
|  | ||||
| 				events.logAdminUserDelete(socket.uid, uid); | ||||
| 				events.log({ | ||||
| 					type: 'user-delete', | ||||
| 					uid: socket.uid, | ||||
| 					targetUid: uid, | ||||
| 					ip: socket.ip | ||||
| 				}); | ||||
|  | ||||
| 				websockets.logoutUser(uid); | ||||
| 				next(); | ||||
|   | ||||
| @@ -16,6 +16,7 @@ var	async = require('async'), | ||||
| 	groups = require('../groups'), | ||||
| 	user = require('../user'), | ||||
| 	websockets = require('./index'), | ||||
| 	events = require('../events'), | ||||
| 	utils = require('../../public/src/utils'), | ||||
|  | ||||
| 	SocketPosts = {}; | ||||
| @@ -138,7 +139,38 @@ SocketPosts.upvote = function(socket, data, callback) { | ||||
| }; | ||||
|  | ||||
| SocketPosts.downvote = function(socket, data, callback) { | ||||
| 	favouriteCommand(socket, 'downvote', 'voted', '', data, callback); | ||||
| 	function banUserForLowReputation(uid, callback) { | ||||
| 		if (parseInt(meta.config['autoban:downvote'], 10) === 1) { | ||||
| 			user.getUserFields(uid, ['reputation', 'banned'], function(err, userData) { | ||||
| 				if (err || parseInt(userData.banned, 10) === 1 || parseInt(userData.reputation) >= parseInt(meta.config['autoban:downvote:threshold'], 10)) { | ||||
| 					return callback(err); | ||||
| 				} | ||||
|  | ||||
| 				var adminUser = require('./admin/user'); | ||||
| 				adminUser.banUser(uid, function(err) { | ||||
| 					if (err) { | ||||
| 						return callback(err); | ||||
| 					} | ||||
| 					events.log({ | ||||
| 						type: 'banned', | ||||
| 						reason: 'low-reputation', | ||||
| 						uid: socket.uid, | ||||
| 						ip: socket.ip, | ||||
| 						targetUid: data.uid, | ||||
| 						reputation: userData.reputation | ||||
| 					}); | ||||
| 					callback(); | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	favouriteCommand(socket, 'downvote', 'voted', '', data, function(err) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
| 		banUserForLowReputation(data.uid, callback); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| SocketPosts.unvote = function(socket, data, callback) { | ||||
| @@ -309,9 +341,16 @@ function deleteOrRestore(command, socket, data, callback) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
|  | ||||
| 		var eventName = command === 'restore' ? 'event:post_restored' : 'event:post_deleted'; | ||||
| 		var eventName = command === 'delete' ? 'event:post_deleted' : 'event:post_restored'; | ||||
| 		websockets.in('topic_' + data.tid).emit(eventName, postData); | ||||
|  | ||||
| 		events.log({ | ||||
| 			type: command === 'delete' ? 'post-delete' : 'post-restore', | ||||
| 			uid: socket.uid, | ||||
| 			pid: data.pid, | ||||
| 			ip: socket.ip | ||||
| 		}); | ||||
|  | ||||
| 		callback(); | ||||
| 	}); | ||||
| } | ||||
| @@ -327,6 +366,13 @@ SocketPosts.purge = function(socket, data, callback) { | ||||
|  | ||||
| 		websockets.in('topic_' + data.tid).emit('event:post_purged', data.pid); | ||||
|  | ||||
| 		events.log({ | ||||
| 			type: 'post-purge', | ||||
| 			uid: socket.uid, | ||||
| 			pid: data.pid, | ||||
| 			ip: socket.ip | ||||
| 		}); | ||||
|  | ||||
| 		callback(); | ||||
| 	}); | ||||
| }; | ||||
|   | ||||
| @@ -15,6 +15,7 @@ var nconf = require('nconf'), | ||||
| 	user = require('../user'), | ||||
| 	db = require('../database'), | ||||
| 	meta = require('../meta'), | ||||
| 	events = require('../events'), | ||||
| 	utils = require('../../public/src/utils'), | ||||
| 	SocketPosts = require('./posts'), | ||||
|  | ||||
| @@ -259,7 +260,21 @@ function doTopicAction(action, socket, data, callback) { | ||||
| 			} | ||||
|  | ||||
| 			if(typeof threadTools[action] === 'function') { | ||||
| 				threadTools[action](tid, socket.uid, next); | ||||
| 				threadTools[action](tid, socket.uid, function(err) { | ||||
| 					if (err) { | ||||
| 						return next(err); | ||||
| 					} | ||||
|  | ||||
| 					if (action === 'delete' || action === 'restore' || action === 'purge') { | ||||
| 						events.log({ | ||||
| 							type: 'topic-' + action, | ||||
| 							uid: socket.uid, | ||||
| 							ip: socket.ip, | ||||
| 							tid: tid | ||||
| 						}); | ||||
| 					} | ||||
| 					next(); | ||||
| 				}); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, callback); | ||||
|   | ||||
| @@ -12,6 +12,7 @@ var	async = require('async'), | ||||
| 	utils = require('../../public/src/utils'), | ||||
| 	websockets = require('./index'), | ||||
| 	meta = require('../meta'), | ||||
| 	events = require('../events'), | ||||
| 	SocketUser = {}; | ||||
|  | ||||
| SocketUser.exists = function(socket, data, callback) { | ||||
| @@ -56,7 +57,7 @@ SocketUser.emailConfirm = function(socket, data, callback) { | ||||
|  | ||||
| SocketUser.search = function(socket, data, callback) { | ||||
| 	if (!data) { | ||||
| 		return callback(new Error('[[error:invalid-data]]')) | ||||
| 		return callback(new Error('[[error:invalid-data]]')); | ||||
| 	} | ||||
| 	if (!socket.uid) { | ||||
| 		return callback(new Error('[[error:not-logged-in]]')); | ||||
| @@ -87,7 +88,16 @@ SocketUser.reset.valid = function(socket, code, callback) { | ||||
|  | ||||
| SocketUser.reset.commit = function(socket, data, callback) { | ||||
| 	if(data && data.code && data.password) { | ||||
| 		user.reset.commit(data.code, data.password, callback); | ||||
| 		user.reset.commit(data.code, data.password, function(err) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
| 			events.log({ | ||||
| 				type: 'password-reset', | ||||
| 				uid: socket.uid, | ||||
| 				ip: socket.ip | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| @@ -109,22 +119,59 @@ SocketUser.checkStatus = function(socket, uid, callback) { | ||||
| }; | ||||
|  | ||||
| SocketUser.changePassword = function(socket, data, callback) { | ||||
| 	if (data && socket.uid) { | ||||
| 		user.changePassword(socket.uid, data, callback); | ||||
| 	if (!data || !data.uid) { | ||||
| 		return callback(new Error('[[error:invalid-data]]')); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| SocketUser.updateProfile = function(socket, data, callback) { | ||||
| 	if (!socket.uid) { | ||||
| 		return callback('[[error:invalid-uid]]'); | ||||
| 	} | ||||
|  | ||||
| 	if (!data || !data.uid) { | ||||
| 		return callback(new Error('[[error:invalid-data]]')); | ||||
| 	user.changePassword(socket.uid, data, function(err) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
|  | ||||
| 		events.log({ | ||||
| 			type: 'password-change', | ||||
| 			uid: socket.uid, | ||||
| 			targetUid: data.uid, | ||||
| 			ip: socket.ip | ||||
| 		}); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| SocketUser.updateProfile = function(socket, data, callback) { | ||||
| 	function update(oldUserData) { | ||||
| 		function done(err, userData) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
|  | ||||
| 			if (userData.email !== oldUserData.email) { | ||||
| 				events.log({ | ||||
| 					type: 'email-change', | ||||
| 					uid: socket.uid, | ||||
| 					targetUid: data.uid, | ||||
| 					ip: socket.ip, | ||||
| 					oldEmail: oldUserData.email, | ||||
| 					newEmail: userData.email | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			if (userData.username !== oldUserData.username) { | ||||
| 				events.log({ | ||||
| 					type: 'username-change', | ||||
| 					uid: socket.uid, | ||||
| 					targetUid: data.uid, | ||||
| 					ip: socket.ip, | ||||
| 					oldUsername: oldUserData.username, | ||||
| 					newUsername: userData.username | ||||
| 				}); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (socket.uid === parseInt(data.uid, 10)) { | ||||
| 		return user.updateProfile(socket.uid, data, callback); | ||||
| 			return user.updateProfile(socket.uid, data, done); | ||||
| 		} | ||||
|  | ||||
| 		user.isAdministrator(socket.uid, function(err, isAdmin) { | ||||
| @@ -136,7 +183,24 @@ SocketUser.updateProfile = function(socket, data, callback) { | ||||
| 				return callback(new Error('[[error:no-privileges]]')); | ||||
| 			} | ||||
|  | ||||
| 		user.updateProfile(data.uid, data, callback); | ||||
| 			user.updateProfile(data.uid, data, done); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	if (!socket.uid) { | ||||
| 		return callback('[[error:invalid-uid]]'); | ||||
| 	} | ||||
|  | ||||
| 	if (!data || !data.uid) { | ||||
| 		return callback(new Error('[[error:invalid-data]]')); | ||||
| 	} | ||||
|  | ||||
| 	user.getUserFields(data.uid, ['email', 'username'], function(err, oldUserData) { | ||||
| 		if (err) { | ||||
| 			return callback(err); | ||||
| 		} | ||||
|  | ||||
| 		update(oldUserData, callback); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -56,8 +56,6 @@ var winston = require('winston'), | ||||
| 					plugins.fireHook('action:topic.restore', topicData); | ||||
| 				} | ||||
|  | ||||
| 				events[isDelete ? 'logTopicDelete' : 'logTopicRestore'](uid, tid); | ||||
|  | ||||
| 				emitTo('topic_' + tid); | ||||
| 				emitTo('category_' + topicData.cid); | ||||
|  | ||||
| @@ -214,8 +212,6 @@ var winston = require('winston'), | ||||
|  | ||||
| 			topics.setTopicField(tid, 'cid', cid, callback); | ||||
|  | ||||
| 			events.logTopicMove(uid, tid); | ||||
|  | ||||
| 			plugins.fireHook('action:topic.move', { | ||||
| 				tid: tid, | ||||
| 				fromCid: oldCid, | ||||
|   | ||||
| @@ -8,7 +8,7 @@ var async = require('async'), | ||||
| module.exports = function(User) { | ||||
| 	User.auth = {}; | ||||
|  | ||||
| 	User.auth.logAttempt = function(uid, callback) { | ||||
| 	User.auth.logAttempt = function(uid, ip, callback) { | ||||
| 		db.exists('lockout:' + uid, function(err, exists) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| @@ -33,9 +33,12 @@ module.exports = function(User) { | ||||
|  | ||||
| 						db.delete('loginAttempts:' + uid); | ||||
| 						db.pexpire('lockout:' + uid, duration); | ||||
|  | ||||
| 						events.logAccountLock(uid, duration); | ||||
| 						callback(new Error('account-locked')); | ||||
| 						events.log({ | ||||
| 							type: 'account-locked', | ||||
| 							uid: uid, | ||||
| 							ip: ip | ||||
| 						}); | ||||
| 						callback(new Error('[[error:account-locked]]')); | ||||
| 					}); | ||||
| 				} else { | ||||
| 					db.pexpire('loginAttempts:' + uid, 1000 * 60 * 60); | ||||
|   | ||||
| @@ -148,8 +148,6 @@ module.exports = function(User) { | ||||
| 					return callback(err); | ||||
| 				} | ||||
|  | ||||
| 				events.logEmailChange(uid, userData.email, newEmail); | ||||
|  | ||||
| 				var gravatarpicture = User.createGravatarURLFromEmail(newEmail); | ||||
| 				async.parallel([ | ||||
| 					function(next) { | ||||
| @@ -183,9 +181,13 @@ module.exports = function(User) { | ||||
| 		if (!newUsername) { | ||||
| 			return callback(); | ||||
| 		} | ||||
|  | ||||
| 		User.getUserFields(uid, ['username', 'userslug'], function(err, userData) { | ||||
| 			function update(field, object, value, callback) { | ||||
| 				async.parallel([ | ||||
| 				async.series([ | ||||
| 					function(next) { | ||||
| 						db.deleteObjectField(field + ':uid', userData[field], next); | ||||
| 					}, | ||||
| 					function(next) { | ||||
| 						User.setUserField(uid, field, value, next); | ||||
| 					}, | ||||
| @@ -198,7 +200,6 @@ module.exports = function(User) { | ||||
| 			if (err) { | ||||
| 				return callback(err); | ||||
| 			} | ||||
| 			var userslug = utils.slugify(newUsername); | ||||
|  | ||||
| 			async.parallel([ | ||||
| 				function(next) { | ||||
| @@ -206,25 +207,15 @@ module.exports = function(User) { | ||||
| 						return next(); | ||||
| 					} | ||||
|  | ||||
| 					db.deleteObjectField('username:uid', userData.username, function(err) { | ||||
| 						if (err) { | ||||
| 							return next(err); | ||||
| 						} | ||||
| 						events.logUsernameChange(uid, userData.username, newUsername); | ||||
| 					update('username', 'username:uid', newUsername, next); | ||||
| 					}); | ||||
| 				}, | ||||
| 				function(next) { | ||||
| 					if (userslug === userData.userslug) { | ||||
| 					var newUserslug = utils.slugify(newUsername); | ||||
| 					if (newUserslug === userData.userslug) { | ||||
| 						return next(); | ||||
| 					} | ||||
|  | ||||
| 					db.deleteObjectField('userslug:uid', userData.userslug, function(err) { | ||||
| 						if (err) { | ||||
| 							return next(err); | ||||
| 						} | ||||
| 						update('userslug', 'userslug:uid', userslug, next); | ||||
| 					}); | ||||
| 					update('userslug', 'userslug:uid', newUserslug, next); | ||||
| 				} | ||||
| 			], callback); | ||||
| 		}); | ||||
| @@ -261,23 +252,11 @@ module.exports = function(User) { | ||||
|  | ||||
| 		function hashAndSetPassword(callback) { | ||||
| 			User.hashPassword(data.newPassword, function(err, hash) { | ||||
| 				if(err) { | ||||
| 				if (err) { | ||||
| 					return callback(err); | ||||
| 				} | ||||
|  | ||||
| 				User.setUserField(data.uid, 'password', hash, function(err) { | ||||
| 					if(err) { | ||||
| 						return callback(err); | ||||
| 					} | ||||
|  | ||||
| 					if(parseInt(uid, 10) === parseInt(data.uid, 10)) { | ||||
| 						events.logPasswordChange(data.uid); | ||||
| 					} else { | ||||
| 						events.logAdminChangeUserPassword(uid, data.uid); | ||||
| 					} | ||||
|  | ||||
| 					callback(); | ||||
| 				}); | ||||
| 				User.setUserField(data.uid, 'password', hash, callback); | ||||
| 			}); | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -83,7 +83,6 @@ var async = require('async'), | ||||
| 						return callback(err); | ||||
| 					} | ||||
| 					user.setUserField(uid, 'password', hash); | ||||
| 					events.logPasswordReset(uid); | ||||
|  | ||||
| 					db.deleteObjectField('reset:uid', code); | ||||
| 					db.deleteObjectField('reset:expiry', code); | ||||
|   | ||||
| @@ -1,31 +1,32 @@ | ||||
| <div class="events"> | ||||
| 	<div class="col-sm-9"> | ||||
| 	<div class="col-lg-9"> | ||||
| 		<div class="panel panel-default"> | ||||
| 			<div class="panel-heading"><i class="fa fa-calendar-o"></i> Events</div> | ||||
| 			<div class="panel-body" data-next="{next}"> | ||||
| 				<pre>{eventdata}</pre> | ||||
| 				<!-- IF !events.length --> | ||||
| 				<div class="alert alert-info">There are no events</div> | ||||
| 				<!-- ENDIF !events.length --> | ||||
| 				<div class="events-list"> | ||||
| 				<!-- BEGIN events --> | ||||
| 				<div> | ||||
| 					<span>#{events.eid} </span><span class="label label-info">{events.type}</span> | ||||
| 					<a href="{config.relative_path}/user/{events.user.userslug}"><img class="user-img" src="{events.user.picture}"/></a> <a href="{config.relative_path}/user/{events.user.userslug}">{events.user.username}</a> (uid {events.user.uid}) (IP {events.ip}) | ||||
| 					<span class="pull-right">{events.timestampISO}</span> | ||||
| 					<br/><br/> | ||||
| 					<pre>{events.jsonString}</pre> | ||||
| 				</div> | ||||
| 				<!-- END events --> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<div class="col-lg-3"> | ||||
| 		<div class="panel panel-default affix"> | ||||
| 			<div class="panel-heading">Events Control Panel</div> | ||||
| 			<div class="panel-body"> | ||||
| 				<button class="btn btn-warning" data-action="clear"><i class="fa fa-eraser"></i> Delete Events</button> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  | ||||
|  | ||||
| <script> | ||||
| require(['forum/infinitescroll'], function(infinitescroll) { | ||||
|  | ||||
| 	infinitescroll.init(function(direction) { | ||||
| 		if (direction < 0 || !$('.events').length) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		infinitescroll.loadMore('admin.getMoreEvents', $('[data-next]').attr('data-next'), function(events, done) { | ||||
| 			if (events.data && events.data.length) { | ||||
| 				$('.panel-body pre').append(events.data); | ||||
| 				$('[data-next]').attr('data-next', events.next); | ||||
| 			} | ||||
| 			done(); | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| </script> | ||||
		Reference in New Issue
	
	Block a user