diff --git a/src/controllers/groups.js b/src/controllers/groups.js
index b960f1446c..2cbbcc7c76 100644
--- a/src/controllers/groups.js
+++ b/src/controllers/groups.js
@@ -162,14 +162,24 @@ groupsController.members = function (req, res, callback) {
 groupsController.uploadCover = function (req, res, next) {
 	var params = JSON.parse(req.body.params);
 
-	groups.updateCover(req.uid, {
-		file: req.files.files[0].path,
-		groupName: params.groupName
-	}, function (err, image) {
+	async.waterfall([
+		function (next) {
+			groups.ownership.isOwner(req.uid, params.groupName, next);
+		},
+		function (isOwner, next) {
+			if (!isOwner) {
+				return next(new Error('[[error:no-privileges]]'));
+			}
+
+			groups.updateCover(req.uid, {
+				file: req.files.files[0].path,
+				groupName: params.groupName
+			}, next);
+		}
+	], function (err, image) {
 		if (err) {
 			return next(err);
 		}
-
 		res.json([{url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url}]);
 	});
 };
diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js
index 1faf4625d0..488ac6507c 100644
--- a/src/controllers/helpers.js
+++ b/src/controllers/helpers.js
@@ -26,13 +26,15 @@ helpers.notAllowed = function (req, res, error) {
 			if (res.locals.isAPI) {
 				res.status(403).json({
 					path: req.path.replace(/^\/api/, ''),
-					loggedIn: !!req.uid, error: error,
+					loggedIn: !!req.uid,
+					error: error,
 					title: '[[global:403.title]]'
 				});
 			} else {
 				res.status(403).render('403', {
 					path: req.path,
-					loggedIn: !!req.uid, error: error,
+					loggedIn: !!req.uid,
+					error: error,
 					title: '[[global:403.title]]'
 				});
 			}
diff --git a/src/emailer.js b/src/emailer.js
index 1e66ce2c1a..ce90839f12 100644
--- a/src/emailer.js
+++ b/src/emailer.js
@@ -132,7 +132,12 @@ var fallbackTransport;
 		delete data.from_name;
 
 		winston.verbose('[emailer] Sending email to uid ' + data.uid);
-		fallbackTransport.sendMail(data, callback);
+		fallbackTransport.sendMail(data, function (err) {
+			if (err) {
+				winston.error(err);
+			}
+			callback();
+		});
 	};
 
 	function render(tpl, params, next) {
diff --git a/src/groups/cover.js b/src/groups/cover.js
index d7dfc8890c..b3a380d80c 100644
--- a/src/groups/cover.js
+++ b/src/groups/cover.js
@@ -10,7 +10,6 @@ var mime = require('mime');
 var winston = require('winston');
 
 var db = require('../database');
-var file = require('../file');
 var uploadsController = require('../controllers/uploads');
 
 module.exports = function (Groups) {
@@ -25,7 +24,7 @@ module.exports = function (Groups) {
 	Groups.updateCover = function (uid, data, callback) {
 
 		// Position only? That's fine
-		if (!data.imageData && data.position) {
+		if (!data.imageData && !data.file && data.position) {
 			return Groups.updateCoverPosition(data.groupName, data.position, callback);
 		}
 
@@ -66,26 +65,19 @@ module.exports = function (Groups) {
 				Groups.setGroupField(data.groupName, 'cover:thumb:url', uploadData.url, next);
 			},
 			function (next) {
-				fs.unlink(tempPath, next);	// Delete temporary file
+				if (data.position) {
+					Groups.updateCoverPosition(data.groupName, data.position, next);
+				} else {
+					next(null);
+				}
 			}
 		], function (err) {
-			if (err) {
-				return fs.unlink(tempPath, function (unlinkErr) {
-					if (unlinkErr) {
-						winston.error(unlinkErr);
-					}
-
-					callback(err);	// send back original error
-				});
-			}
-
-			if (data.position) {
-				Groups.updateCoverPosition(data.groupName, data.position, function (err) {
-					callback(err, {url: url});
-				});
-			} else {
+			fs.unlink(tempPath, function (unlinkErr) {
+				if (unlinkErr) {
+					winston.error(unlinkErr);
+				}
 				callback(err, {url: url});
-			}
+			});
 		});
 	};
 
diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js
index fb6d6e31ec..964001a3f1 100644
--- a/src/socket.io/admin/user.js
+++ b/src/socket.io/admin/user.js
@@ -220,33 +220,37 @@ User.deleteInvitation = function (socket, data, callback) {
 };
 
 User.acceptRegistration = function (socket, data, callback) {
-	user.acceptRegistration(data.username, function (err, uid) {
-		if (err) {
-			return callback(err);
+	async.waterfall([
+		function (next) {
+			user.acceptRegistration(data.username, next);
+		},
+		function (uid, next) {
+			events.log({
+				type: 'registration-approved',
+				uid: socket.uid,
+				ip: socket.ip,
+				targetUid: uid
+			});
+			next(null, uid);
 		}
-		events.log({
-			type: 'registration-approved',
-			uid: socket.uid,
-			ip: socket.ip,
-			targetUid: uid,
-		});
-		callback();
-	});
+	], callback);
 };
 
 User.rejectRegistration = function (socket, data, callback) {
-	user.rejectRegistration(data.username, function (err) {
-		if (err) {
-			return callback(err);
+	async.waterfall([
+		function (next) {
+			user.rejectRegistration(data.username, next);
+		},
+		function (next) {
+			events.log({
+				type: 'registration-rejected',
+				uid: socket.uid,
+				ip: socket.ip,
+				username: data.username,
+			});
+			next();
 		}
-		events.log({
-			type: 'registration-rejected',
-			uid: socket.uid,
-			ip: socket.ip,
-			username: data.username,
-		});
-		callback();
-	});
+	], callback);
 };
 
 User.restartJobs = function (socket, data, callback) {
diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js
index 858d9bdeff..5bff7ba944 100644
--- a/src/socket.io/groups.js
+++ b/src/socket.io/groups.js
@@ -277,13 +277,18 @@ SocketGroups.cover.update = function (socket, data, callback) {
 		return callback(new Error('[[error:no-privileges]]'));
 	}
 
-	groups.ownership.isOwner(socket.uid, data.groupName, function (err, isOwner) {
-		if (err || !isOwner) {
-			return callback(err || new Error('[[error:no-privileges]]'));
-		}
+	async.waterfall([
+		function (next) {
+			groups.ownership.isOwner(socket.uid, data.groupName, next);
+		},
+		function (isOwner, next) {
+			if (!isOwner) {
+				return next(new Error('[[error:no-privileges]]'));
+			}
 
-		groups.updateCover(socket.uid, data, callback);
-	});
+			groups.updateCover(socket.uid, data, next);
+		}
+	], callback);
 };
 
 SocketGroups.cover.remove = function (socket, data, callback) {
@@ -291,13 +296,18 @@ SocketGroups.cover.remove = function (socket, data, callback) {
 		return callback(new Error('[[error:no-privileges]]'));
 	}
 
-	groups.ownership.isOwner(socket.uid, data.groupName, function (err, isOwner) {
-		if (err || !isOwner) {
-			return callback(err || new Error('[[error:no-privileges]]'));
-		}
+	async.waterfall([
+		function (next) {
+			groups.ownership.isOwner(socket.uid, data.groupName, next);
+		},
+		function (isOwner, next) {
+			if (!isOwner) {
+				return next(new Error('[[error:no-privileges]]'));
+			}
 
-		groups.removeCover(data, callback);
-	});
+			groups.removeCover(data, next);
+		}
+	], callback);
 };
 
 module.exports = SocketGroups;
diff --git a/src/user/approval.js b/src/user/approval.js
index e09e6431ff..c8b78eb75f 100644
--- a/src/user/approval.js
+++ b/src/user/approval.js
@@ -78,6 +78,12 @@ module.exports = function (User) {
 				uid = _uid;
 				User.setUserField(uid, 'password', userData.hashedPassword, next);
 			},
+			function (next) {
+				removeFromQueue(username, next);
+			},
+			function (next) {
+				markNotificationRead(username, next);
+			},
 			function (next) {
 				var title = meta.config.title || meta.config.browserTitle || 'NodeBB';
 				translator.translate('[[email:welcome-to, ' + title + ']]', meta.config.defaultLang, function (subject) {
@@ -92,12 +98,6 @@ module.exports = function (User) {
 					emailer.send('registration_accepted', uid, data, next);
 				});
 			},
-			function (next) {
-				removeFromQueue(username, next);
-			},
-			function (next) {
-				markNotificationRead(username, next);
-			},
 			function (next) {
 				next(null, uid);
 			}
diff --git a/test/groups.js b/test/groups.js
index c2b30b9d94..182910bc32 100644
--- a/test/groups.js
+++ b/test/groups.js
@@ -1,10 +1,12 @@
 'use strict';
-/*global require, before, after*/
 
 var assert = require('assert');
 var async = require('async');
+var path = require('path');
+var nconf = require('nconf');
 
 var db = require('./mocks/databasemock');
+var helpers = require('./helpers');
 var Groups = require('../src/groups');
 var User = require('../src/user');
 
@@ -47,7 +49,8 @@ describe('Groups', function () {
 			function (next) {
 				User.create({
 					username: 'admin',
-					email: 'admin@admin.com'
+					email: 'admin@admin.com',
+					password: '123456'
 				}, next);
 			},
 			function (next) {
@@ -694,6 +697,138 @@ describe('Groups', function () {
 		});
 	});
 
+	describe('groups cover', function () {
+		var socketGroups = require('../src/socket.io/groups');
+		var regularUid;
+		var logoPath = path.join(__dirname, '../public/logo.png');
+		var imagePath = path.join(__dirname, '../public/groupcover.png');
+		before(function (done) {
+			User.create({username: 'regularuser', password: '123456'}, function (err, uid) {
+				assert.ifError(err);
+				regularUid = uid;
+				async.series([
+					function (next) {
+						Groups.join('Test', adminUid, next);
+					},
+					function (next) {
+						Groups.join('Test', regularUid, next);
+					},
+					function (next) {
+						helpers.copyFile(logoPath, imagePath, next);
+					}
+				], done);
+			});
+		});
+
+		it('should fail if user is not logged in or not owner', function (done) {
+			socketGroups.cover.update({uid: 0}, {}, function (err) {
+				assert.equal(err.message, '[[error:no-privileges]]');
+				socketGroups.cover.update({uid: regularUid}, {}, function (err) {
+					assert.equal(err.message, '[[error:no-privileges]]');
+					done();
+				});
+			});
+		});
+
+		it('should upload group cover image from file', function (done) {
+			var data = {
+				groupName: 'Test',
+				file: imagePath
+			};
+			socketGroups.cover.update({uid: adminUid}, data, function (err, data) {
+				assert.ifError(err);
+				Groups.getGroupFields('Test', ['cover:url'], function (err, groupData) {
+					assert.ifError(err);
+					assert.equal(data.url, groupData['cover:url']);
+					done();
+				});
+			});
+		});
+
+
+		it('should upload group cover image from data', function (done) {
+			var data = {
+				groupName: 'Test',
+				imageData: 'data:image/png;base64,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'
+			};
+			socketGroups.cover.update({uid: adminUid}, data, function (err, data) {
+				assert.ifError(err);
+				Groups.getGroupFields('Test', ['cover:url'], function (err, groupData) {
+					assert.ifError(err);
+					assert.equal(data.url, groupData['cover:url']);
+					done();
+				});
+			});
+		});
+
+		it('should update group cover position', function (done) {
+			var data = {
+				groupName: 'Test',
+				position: '50% 50%'
+			};
+			socketGroups.cover.update({uid: adminUid}, data, function (err) {
+				assert.ifError(err);
+				Groups.getGroupFields('Test', ['cover:position'], function (err, groupData) {
+					assert.ifError(err);
+					assert.equal('50% 50%', groupData['cover:position']);
+					done();
+				});
+			});
+		});
+
+		it('should fail to update cover position if group name is missing', function (done) {
+			Groups.updateCoverPosition('', '50% 50%', function (err) {
+				assert.equal(err.message, '[[error:invalid-data]]');
+				done();
+			});
+		});
+
+		it('should error if user is not owner of group', function (done) {
+			helpers.loginUser('regularuser', '123456', function (err, jar, io, csrf_token) {
+				assert.ifError(err);
+				helpers.uploadFile(nconf.get('url') + '/api/groups/uploadpicture', logoPath, {params: JSON.stringify({groupName: 'Test'})}, jar, csrf_token, function (err, res, body) {
+					assert.ifError(err);
+					assert.equal(res.statusCode, 500);
+					assert.equal(body.error, '[[error:no-privileges]]');
+					done();
+				});
+			});
+		});
+
+		it('should upload group cover with api route', function (done) {
+			helpers.loginUser('admin', '123456', function (err, jar, io, csrf_token) {
+				assert.ifError(err);
+				helpers.uploadFile(nconf.get('url') + '/api/groups/uploadpicture', logoPath, {params: JSON.stringify({groupName: 'Test'})}, jar, csrf_token, function (err, res, body) {
+					assert.ifError(err);
+					assert.equal(res.statusCode, 200);
+					Groups.getGroupFields('Test', ['cover:url'], function (err, groupData) {
+						assert.ifError(err);
+						assert.equal(body[0].url, groupData['cover:url']);
+						done();
+					});
+				});
+			});
+		});
+
+		it('should fail to remove cover if not owner', function (done) {
+			socketGroups.cover.remove({uid: regularUid}, {groupName: 'Test'}, function (err) {
+				assert.equal(err.message, '[[error:no-privileges]]');
+				done();
+			});
+		});
+
+		it('should remove cover', function (done) {
+			socketGroups.cover.remove({uid: adminUid}, {groupName: 'Test'}, function (err) {
+				assert.ifError(err);
+				Groups.getGroupFields('Test', ['cover:url'], function (err, groupData) {
+					assert.ifError(err);
+					assert(!groupData['cover:url']);
+					done();
+				});
+			});
+		});
+	});
+
 	after(function (done) {
 		db.emptydb(done);
 	});
diff --git a/test/helpers/index.js b/test/helpers/index.js
index a04a6a855c..edae035b66 100644
--- a/test/helpers/index.js
+++ b/test/helpers/index.js
@@ -112,4 +112,58 @@ helpers.uploadFile = function (uploadEndPoint, filePath, body, jar, csrf_token,
 		}
 		callback(err, res, body);
 	});
+};
+
+helpers.registerUser = function (data, callback) {
+	var jar = request.jar();
+	request({
+		url: nconf.get('url') + '/api/config',
+		json: true,
+		jar: jar
+	}, function (err, response, body) {
+		if (err) {
+			return callback(err);
+		}
+
+		request.post(nconf.get('url') + '/register', {
+			form: data,
+			json: true,
+			jar: jar,
+			headers: {
+				'x-csrf-token': body.csrf_token
+			}
+		}, function (err, res, body) {
+			if (err) {
+				return callback(err);
+			}
+
+			callback(null, jar);
+		});
+	});
+};
+
+//http://stackoverflow.com/a/14387791/583363
+helpers.copyFile = function (source, target, callback) {
+
+	var cbCalled = false;
+
+	var rd = fs.createReadStream(source);
+	rd.on("error", function (err) {
+		done(err);
+	});
+	var wr = fs.createWriteStream(target);
+	wr.on("error", function (err) {
+		done(err);
+	});
+	wr.on("close", function () {
+		done();
+	});
+	rd.pipe(wr);
+
+	function done(err) {
+		if (!cbCalled) {
+			callback(err);
+			cbCalled = true;
+		}
+	}
 };
\ No newline at end of file
diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js
index f1fcb89c5a..44de8c7122 100644
--- a/test/mocks/databasemock.js
+++ b/test/mocks/databasemock.js
@@ -138,7 +138,7 @@
 				nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates'));
 				nconf.set('theme_templates_path', meta.config['theme:templates'] ? path.join(nconf.get('themes_path'), meta.config['theme:id'], meta.config['theme:templates']) : nconf.get('base_templates_path'));
 				nconf.set('theme_config', path.join(nconf.get('themes_path'), 'nodebb-theme-persona', 'theme.json'));
-				nconf.set('bcrypt_rounds', 4);
+				nconf.set('bcrypt_rounds', 1);
 
 				require('../../build').buildTargets(['js', 'clientCSS', 'acpCSS', 'tpl'], next);
 			},
diff --git a/test/user.js b/test/user.js
index c2f67ef709..3567906601 100644
--- a/test/user.js
+++ b/test/user.js
@@ -661,6 +661,80 @@ describe('User', function () {
 		});
 	});
 
+	describe('approval queue', function () {
+		var socketAdmin = require('../src/socket.io/admin');
+
+		var oldRegistrationType;
+		var adminUid;
+		before(function (done) {
+			oldRegistrationType = Meta.config.registrationType;
+			Meta.config.registrationType = 'admin-approval';
+			User.create({username: 'admin', password: '123456'}, function (err, uid) {
+				assert.ifError(err);
+				adminUid = uid;
+				groups.join('administrators', uid, done);
+			});
+		});
+
+		after(function (done) {
+			Meta.config.registrationType = oldRegistrationType;
+			done();
+		});
+
+		it('should add user to approval queue', function (done) {
+			helpers.registerUser({
+				username: 'rejectme',
+				password: '123456',
+				email: 'reject@me.com'
+			}, function (err) {
+				assert.ifError(err);
+				helpers.loginUser('admin', '123456', function (err, jar) {
+					assert.ifError(err);
+					request(nconf.get('url') + '/api/admin/manage/registration', {jar: jar, json: true}, function (err, res, body) {
+						assert.ifError(err);
+						assert.equal(body.users[0].username, 'rejectme');
+						assert.equal(body.users[0].email, 'reject@me.com');
+						done();
+					});
+				});
+			});
+		});
+
+		it('should reject user registration', function (done) {
+			socketAdmin.user.rejectRegistration({uid: adminUid}, {username: 'rejectme'}, function (err) {
+				assert.ifError(err);
+				User.getRegistrationQueue(0, -1, function (err, users) {
+					assert.ifError(err);
+					assert.equal(users.length, 0);
+					done();
+				});
+			});
+		});
+
+		it('should accept user registration', function (done) {
+			helpers.registerUser({
+				username: 'acceptme',
+				password: '123456',
+				email: 'accept@me.com'
+			}, function (err) {
+				assert.ifError(err);
+				socketAdmin.user.acceptRegistration({uid: adminUid}, {username: 'acceptme'}, function (err, uid) {
+					assert.ifError(err);
+					User.exists(uid, function (err, exists) {
+						assert.ifError(err);
+						assert(exists);
+						User.getRegistrationQueue(0, -1, function (err, users) {
+							assert.ifError(err);
+							assert.equal(users.length, 0);
+							done();
+						});
+					});
+				});
+			});
+		});
+
+	});
+
 
 	after(function (done) {
 		db.emptydb(done);