diff --git a/app.js b/app.js
index d4cc855de9..85abee4ab2 100644
--- a/app.js
+++ b/app.js
@@ -37,7 +37,7 @@ winston.add(winston.transports.Console, {
 	colorize: true,
 	timestamp: function () {
 		var date = new Date();
-		return (!!nconf.get('json-logging')) ? date.toJSON() :	date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0,5) + ' [' + global.process.pid + ']';
+		return (!!nconf.get('json-logging')) ? date.toJSON() :	date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0,8) + ' [' + global.process.pid + ']';
 	},
 	level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose'),
 	json: (!!nconf.get('json-logging')),
@@ -180,21 +180,26 @@ function start() {
 	});
 
 	async.waterfall([
-		async.apply(db.init),
-		async.apply(db.checkCompatibility),
-		function (next) {
-			require('./src/meta').configs.init(next);
-		},
+		async.apply(db.init),		
 		function (next) {
-			if (nconf.get('dep-check') === undefined || nconf.get('dep-check') !== false) {
-				require('./src/meta').dependencies.check(next);
-			} else {
-				winston.warn('[init] Dependency checking skipped!');
-				setImmediate(next);
-			}
-		},
-		function (next) {
-			require('./src/upgrade').check(next);
+			var meta = require('./src/meta');
+			async.parallel([
+				async.apply(db.checkCompatibility),
+				async.apply(meta.configs.init),	
+				function (next) {
+					if (nconf.get('dep-check') === undefined || nconf.get('dep-check') !== false) {
+						meta.dependencies.check(next);
+					} else {
+						winston.warn('[init] Dependency checking skipped!');
+						setImmediate(next);
+					}
+				},
+				function (next) {
+					require('./src/upgrade').check(next);	
+				}				
+			], function (err) {
+				next(err);
+			});
 		},
 		function (next) {
 			var webserver = require('./src/webserver');
diff --git a/package.json b/package.json
index a897b57966..bea679984d 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
     "morgan": "^1.3.2",
     "mousetrap": "^1.5.3",
     "nconf": "~0.8.2",
-    "nodebb-plugin-composer-default": "4.3.2",
+    "nodebb-plugin-composer-default": "4.3.3",
     "nodebb-plugin-dbsearch": "1.0.4",
     "nodebb-plugin-emoji-extended": "1.1.1",
     "nodebb-plugin-emoji-one": "1.1.5",
diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js
index bc0b1652b5..866bc4bb9d 100644
--- a/public/src/admin/manage/category.js
+++ b/public/src/admin/manage/category.js
@@ -8,44 +8,9 @@ define('admin/manage/category', [
 	'autocomplete'
 ], function (uploader, iconSelect, colorpicker, autocomplete) {
 	var	Category = {};
+	var modified_categories = {};
 
 	Category.init = function () {
-		var modified_categories = {};
-
-		function modified(el) {
-			var cid = $(el).parents('form').attr('data-cid');
-
-			if (cid) {
-				modified_categories[cid] = modified_categories[cid] || {};
-				modified_categories[cid][$(el).attr('data-name')] = $(el).val();
-
-				app.flags = app.flags || {};
-				app.flags._unsaved = true;
-			}
-		}
-
-		function save(e) {
-			e.preventDefault();
-
-			if(Object.keys(modified_categories).length) {
-				socket.emit('admin.categories.update', modified_categories, function (err, result) {
-					if (err) {
-						return app.alertError(err.message);
-					}
-
-					if (result && result.length) {
-						app.flags._unsaved = false;
-						app.alert({
-							title: 'Updated Categories',
-							message: 'Category IDs ' + result.join(', ') + ' was successfully updated.',
-							type: 'success',
-							timeout: 2000
-						});
-					}
-				});
-				modified_categories = {};
-			}
-		}
 
 		$('.blockclass, form.category select').each(function () {
 			var $this = $(this);
@@ -85,7 +50,28 @@ define('admin/manage/category', [
 
 		$('[data-name="bgColor"], [data-name="color"]').each(enableColorPicker);
 
-		$('#save').on('click', save);
+		$('#save').on('click', function () {
+			if (Object.keys(modified_categories).length) {
+				socket.emit('admin.categories.update', modified_categories, function (err, result) {
+					if (err) {
+						return app.alertError(err.message);
+					}
+
+					if (result && result.length) {
+						app.flags._unsaved = false;
+						app.alert({
+							title: 'Updated Categories',
+							message: 'Category IDs ' + result.join(', ') + ' was successfully updated.',
+							type: 'success',
+							timeout: 2000
+						});
+					}
+				});
+				modified_categories = {};
+			}
+			return false;
+		});
+
 		$('.purge').on('click', function (e) {
 			e.preventDefault();
 
@@ -171,8 +157,37 @@ define('admin/manage/category', [
 		});
 
 		Category.setupPrivilegeTable();
+
+		handleTags();
 	};
 
+	function modified(el) {
+		var cid = ajaxify.data.category.cid;
+
+		if (cid) {
+			modified_categories[cid] = modified_categories[cid] || {};
+			modified_categories[cid][$(el).attr('data-name')] = $(el).val();
+
+			app.flags = app.flags || {};
+			app.flags._unsaved = true;
+		}
+	}
+
+	function handleTags() {
+		var tagEl = $('#tag-whitelist');
+		tagEl.tagsinput({
+			confirmKeys: [13, 44],
+			trimValue: true
+		});
+
+		ajaxify.data.category.tagWhitelist.forEach(function (tag) {
+			tagEl.tagsinput('add', tag);
+		});
+		tagEl.on('itemAdded itemRemoved', function (event) {
+			modified(tagEl);
+		});
+	}
+
 	Category.setupPrivilegeTable = function () {
 		$('.privilege-table-container').on('change', 'input[type="checkbox"]', function () {
 			var checkboxEl = $(this),
diff --git a/public/src/utils.js b/public/src/utils.js
index 0b78b34030..42d4b6f3ad 100644
--- a/public/src/utils.js
+++ b/public/src/utils.js
@@ -101,7 +101,7 @@
 		},
 
 		cleanUpTag: function (tag, maxLength) {
-			if (typeof tag !== 'string' || !tag.length ) {
+			if (typeof tag !== 'string' || !tag.length) {
 				return '';
 			}
 
diff --git a/src/categories.js b/src/categories.js
index 28d99a7d7f..4458e09481 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -141,6 +141,9 @@ var privileges = require('./privileges');
 			parents: function (next) {
 				Categories.getParents(cids, next);
 			},
+			tagWhitelist: function (next) {
+				Categories.getTagWhitelist(cids, next);
+			},
 			hasRead: function (next) {
 				Categories.hasReadCategories(cids, uid, next);
 			}
@@ -149,20 +152,26 @@ var privileges = require('./privileges');
 				return callback(err);
 			}
 
-			var categories = results.categories;
-			var hasRead = results.hasRead;
 			uid = parseInt(uid, 10);
-			for(var i = 0; i < results.categories.length; ++i) {
-				if (categories[i]) {
-					categories[i]['unread-class'] = (parseInt(categories[i].topic_count, 10) === 0 || (hasRead[i] && uid !== 0)) ? '' : 'unread';
-					categories[i].children = results.children[i];
-					categories[i].parent = results.parents[i] || undefined;
-					calculateTopicPostCount(categories[i]);
+			results.categories.forEach(function (category, i) {
+				if (category) {
+					category['unread-class'] = (parseInt(category.topic_count, 10) === 0 || (results.hasRead[i] && uid !== 0)) ? '' : 'unread';
+					category.children = results.children[i];
+					category.parent = results.parents[i] || undefined;
+					category.tagWhitelist = results.tagWhitelist[i];
+					calculateTopicPostCount(category);
 				}
-			}
+			});
+
+			callback(null, results.categories);
+		});
+	};
 
-			callback(null, categories);
+	Categories.getTagWhitelist = function (cids, callback) {
+		var keys = cids.map(function (cid) {
+			return 'cid:' + cid + ':tag:whitelist';
 		});
+		db.getSortedSetsMembers(keys, callback);
 	};
 
 	function calculateTopicPostCount(category) {
diff --git a/src/categories/delete.js b/src/categories/delete.js
index f91842e487..06d4931132 100644
--- a/src/categories/delete.js
+++ b/src/categories/delete.js
@@ -54,6 +54,7 @@ module.exports = function (Categories) {
 					'cid:' + cid + ':read_by_uid',
 					'cid:' + cid + ':ignorers',
 					'cid:' + cid + ':children',
+					'cid:' + cid + ':tag:whitelist',
 					'category:' + cid
 				], next);
 			},
diff --git a/src/categories/update.js b/src/categories/update.js
index 485d3e3834..afaad6d974 100644
--- a/src/categories/update.js
+++ b/src/categories/update.js
@@ -2,7 +2,9 @@
 'use strict';
 
 var async = require('async');
+
 var db = require('../database');
+var meta = require('../meta');
 var utils = require('../../public/src/utils');
 var translator = require('../../public/src/modules/translator');
 var plugins = require('../plugins');
@@ -66,6 +68,8 @@ module.exports = function (Categories) {
 	function updateCategoryField(cid, key, value, callback) {
 		if (key === 'parentCid') {
 			return updateParent(cid, value, callback);
+		} else if (key === 'tagWhitelist') {
+			return updateTagWhitelist(cid, value, callback);
 		}
 
 		async.waterfall([
@@ -112,6 +116,25 @@ module.exports = function (Categories) {
 		});
 	}
 
+	function updateTagWhitelist(cid, tags, callback) {
+		tags = tags.split(',');
+		tags = tags.map(function (tag) {
+			return utils.cleanUpTag(tag, meta.config.maximumTagLength);
+		}).filter(Boolean);
+
+		async.waterfall([
+			function (next) {
+				db.delete('cid:' + cid + ':tag:whitelist', next);
+			},
+			function (next) {
+				var scores = tags.map(function (tag, index) {
+					return index;
+				});
+				db.sortedSetAdd('cid:' + cid + ':tag:whitelist', scores, tags, next);
+			}
+		], callback);
+	}
+
 	function updateOrder(cid, order, callback) {
 		async.waterfall([
 			function (next) {
diff --git a/src/database/mongo.js b/src/database/mongo.js
index 1528671770..2e09047da1 100644
--- a/src/database/mongo.js
+++ b/src/database/mongo.js
@@ -111,13 +111,11 @@
 					if (err) {
 						return callback(err);
 					}
-					createSessionStore();
-					createIndices();
+					createSessionStore();					
 				});
 			} else {
 				winston.warn('You have no mongo password setup!');
 				createSessionStore();
-				createIndices();
 			}
 
 			function createSessionStore() {
@@ -137,37 +135,44 @@
 						db: db
 					});
 				}
-			}
+				callback();
+			}			
+		});
+	};
 
-			function createIndices() {
-				winston.info('[database] Checking database indices.');
-				async.series([
-					async.apply(createIndex, 'objects', {_key: 1, score: -1}, {background: true}),
-					async.apply(createIndex, 'objects', {_key: 1, value: -1}, {background: true, unique: true, sparse: true}),
-					async.apply(createIndex, 'objects', {expireAt: 1}, {expireAfterSeconds: 0, background: true})
-				], function (err) {
-					if (err) {
-						winston.error('Error creating index ' + err.message);
-					}
-					winston.info('[database] Checking database indices done!');
-					callback(err);
-				});
-			}
+	module.createIndices = function (callback) {
+		function createIndex(collection, index, options, callback) {
+			module.client.collection(collection).createIndex(index, options, callback);				
+		}
 
-			function createIndex(collection, index, options, callback) {
-				db.collection(collection).createIndex(index, options, callback);
+		if (!module.client) {
+			winston.warn('[database/createIndices] database not initialized');
+			return callback();
+		}
+
+		winston.info('[database] Checking database indices.');
+		async.series([
+			async.apply(createIndex, 'objects', {_key: 1, score: -1}, {background: true}),
+			async.apply(createIndex, 'objects', {_key: 1, value: -1}, {background: true, unique: true, sparse: true}),
+			async.apply(createIndex, 'objects', {expireAt: 1}, {expireAfterSeconds: 0, background: true})
+		], function (err) {
+			if (err) {
+				winston.error('Error creating index ' + err.message);
+				return callback(err);
 			}
-		});
+			winston.info('[database] Checking database indices done!');
+			callback();
+		});			
 	};
 
 	module.checkCompatibility = function (callback) {
-		var mongoPkg = require.main.require('./node_modules/mongodb/package.json'),
-			err = semver.lt(mongoPkg.version, '2.0.0') ? new Error('The `mongodb` package is out-of-date, please run `./nodebb setup` again.') : null;
-
-		if (err) {
-			err.stacktrace = false;
+		var mongoPkg = require.main.require('./node_modules/mongodb/package.json');
+		
+		if (semver.lt(mongoPkg.version, '2.0.0')) {
+			return callback(new Error('The `mongodb` package is out-of-date, please run `./nodebb setup` again.'));
 		}
-		callback(err);
+		
+		callback();
 	};
 
 	module.info = function (db, callback) {
diff --git a/src/database/redis.js b/src/database/redis.js
index e583aff4ff..f1c00a2316 100644
--- a/src/database/redis.js
+++ b/src/database/redis.js
@@ -2,13 +2,13 @@
 
 (function (module) {
 
-	var winston = require('winston'),
-		nconf = require('nconf'),
-		semver = require('semver'),
-		session = require('express-session'),
-		redis,
-		connectRedis,
-		redisClient;
+	var winston = require('winston');
+	var nconf = require('nconf');
+	var semver = require('semver');
+	var session = require('express-session');
+	var redis;
+	var connectRedis;
+	var redisClient;
 
 	module.questions = [
 		{
@@ -107,6 +107,10 @@
 		return cxn;
 	};
 
+	module.createIndices = function (callback) {
+		setImmediate(callback);
+	};
+
 	module.checkCompatibility = function (callback) {
 		module.info(module.client, function (err, info) {
 			if (err) {
@@ -114,11 +118,10 @@
 			}
 
 			if (semver.lt(info.redis_version, '2.8.9')) {
-				err = new Error('Your Redis version is not new enough to support NodeBB, please upgrade Redis to v2.8.9 or higher.');
-				err.stacktrace = false;
+				return callback(new Error('Your Redis version is not new enough to support NodeBB, please upgrade Redis to v2.8.9 or higher.'));
 			}
 
-			callback(err);
+			callback();
 		});
 	};
 
diff --git a/src/install.js b/src/install.js
index 711932bb00..acf7aadc97 100644
--- a/src/install.js
+++ b/src/install.js
@@ -1,16 +1,15 @@
 'use strict';
 
-var async = require('async'),
-	fs = require('fs'),
-	path = require('path'),
-	prompt = require('prompt'),
-	winston = require('winston'),
-	nconf = require('nconf'),
-	utils = require('../public/src/utils.js');
+var async = require('async');
+var fs = require('fs');
+var path = require('path');
+var prompt = require('prompt');
+var winston = require('winston');
+var nconf = require('nconf');
+var utils = require('../public/src/utils.js');
 
-
-var install = {},
-	questions = {};
+var install = {};
+var questions = {};
 
 questions.main = [
 	{
@@ -124,40 +123,33 @@ function setupConfig(next) {
 	prompt.delimiter = '';
 	prompt.colors = false;
 
-	if (!install.values) {
-		prompt.get(questions.main, function (err, config) {
-			if (err) {
-				process.stdout.write('\n\n');
-				winston.warn('NodeBB setup ' + err.message);
-				process.exit();
+	async.waterfall([
+		function (next) {
+			if (install.values) {
+				// Use provided values, fall back to defaults
+				var config = {};
+				var redisQuestions = require('./database/redis').questions;
+				var mongoQuestions = require('./database/mongo').questions;
+				var allQuestions = questions.main.concat(questions.optional).concat(redisQuestions).concat(mongoQuestions);
+
+				allQuestions.forEach(function (question) {
+					config[question.name] = install.values[question.name] || question['default'] || undefined;
+				});
+				setImmediate(next, null, config);
+			} else {
+				prompt.get(questions.main, next);
 			}
-
-			configureDatabases(config, function (err, config) {
-				completeConfigSetup(err, config, next);
-			});
-		});
-	} else {
-		// Use provided values, fall back to defaults
-		var	config = {},
-			redisQuestions = require('./database/redis').questions,
-			mongoQuestions = require('./database/mongo').questions,
-			allQuestions = questions.main.concat(questions.optional).concat(redisQuestions).concat(mongoQuestions);
-
-		allQuestions.forEach(function (question) {
-			config[question.name] = install.values[question.name] || question['default'] || undefined;
-		});
-
-		configureDatabases(config, function (err, config) {
-			completeConfigSetup(err, config, next);
-		});
-	}
+		},
+		function (config, next) {
+			configureDatabases(config, next);
+		},
+		function (config, next) {
+			completeConfigSetup(config, next);
+		}
+	], next);
 }
 
-function completeConfigSetup(err, config, next) {
-	if (err) {
-		return next(err);
-	}
-
+function completeConfigSetup(config, next) {
 	// Add CI object
 	if (install.ciVals) {
 		config.test_database = {};
@@ -168,13 +160,17 @@ function completeConfigSetup(err, config, next) {
 		}
 	}
 
-	install.save(config, function (err) {
-		if (err) {
-			return next(err);
+	async.waterfall([
+		function (next) {
+			install.save(config, next);
+		},
+		function (next) {
+			require('./database').init(next);
+		},
+		function (next) {
+			require('./database').createIndices(next);
 		}
-
-		require('./database').init(next);
-	});
+	], next);
 }
 
 function setupDefaultConfigs(next) {
@@ -491,7 +487,6 @@ function setCopyrightWidget(next) {
 
 install.setup = function (callback) {
 
-
 	async.series([
 		checkSetupFlag,
 		checkCIFlag,
diff --git a/src/meta/css.js b/src/meta/css.js
index 583927e04a..b9bc1787b6 100644
--- a/src/meta/css.js
+++ b/src/meta/css.js
@@ -78,6 +78,7 @@ module.exports = function (Meta) {
 					acpSource += '\n@import "..' + path.sep + 'public/less/generics.less";\n';
 					acpSource += '\n@import (inline) "..' + path.sep + 'public/vendor/colorpicker/colorpicker.css";\n';
 					acpSource += '\n@import (inline) "..' + path.sep + 'public/vendor/jquery/css/smoothness/jquery-ui.css";';
+					acpSource += '\n@import (inline) "..' + path.sep + 'public/vendor/jquery/bootstrap-tagsinput/bootstrap-tagsinput.css";';
 
 					minify(acpSource, paths, 'acpCache', callback);
 				}
diff --git a/src/socket.io/topics/tags.js b/src/socket.io/topics/tags.js
index 67e48fcce8..8829229ca8 100644
--- a/src/socket.io/topics/tags.js
+++ b/src/socket.io/topics/tags.js
@@ -1,10 +1,29 @@
 'use strict';
 
 var async = require('async');
+var db = require('../../database');
 var topics = require('../../topics');
 var utils = require('../../../public/src/utils');
 
 module.exports = function (SocketTopics) {
+
+	SocketTopics.isTagAllowed = function (socket, data, callback) {
+		if (!data || !data.cid || !data.tag) {
+			return callback(new Error('[[error:invalid-data]]'));
+		}
+		async.waterfall([
+			function (next) {
+				db.getSortedSetRange('cid:' + data.cid + ':tag:whitelist', 0, -1, next);
+			},
+			function (tagWhitelist, next) {
+				if (!tagWhitelist.length) {
+					return next(null, true);
+				}
+				next(null, tagWhitelist.indexOf(data.tag) !== -1);
+			}
+		], callback);
+	};
+
 	SocketTopics.autocompleteTags = function (socket, data, callback) {
 		topics.autocompleteTags(data, callback);
 	};
diff --git a/src/topics/tags.js b/src/topics/tags.js
index a88c5119d5..3f6bfbab36 100644
--- a/src/topics/tags.js
+++ b/src/topics/tags.js
@@ -31,6 +31,10 @@ module.exports = function (Topics) {
 					return tag && tag.length >= (meta.config.minimumTagLength || 3) && array.indexOf(tag) === index;
 				});
 
+				filterCategoryTags(tags, tid, next);
+			},
+			function (_tags, next) {
+				tags = _tags;
 				var keys = tags.map(function (tag) {
 					return 'tag:' + tag + ':topics';
 				});
@@ -39,15 +43,35 @@ module.exports = function (Topics) {
 					async.apply(db.setAdd, 'topic:' + tid + ':tags', tags),
 					async.apply(db.sortedSetsAdd, keys, timestamp, tid)
 				], function (err) {
-					if (err) {
-						return next(err);
-					}
-					async.each(tags, updateTagCount, next);
+					next(err);
 				});
+			},
+			function (next) {
+				async.each(tags, updateTagCount, next);
 			}
 		], callback);
 	};
 
+	function filterCategoryTags(tags, tid, callback) {
+		async.waterfall([
+			function (next) {
+				Topics.getTopicField(tid, 'cid', next);
+			},
+			function (cid, next) {
+				db.getSortedSetRange('cid:' + cid + ':tag:whitelist', 0, -1, next);
+			},
+			function (tagWhitelist, next) {
+				if (!tagWhitelist.length) {
+					return next(null, tags);
+				}
+				tags = tags.filter(function (tag) {
+					return tagWhitelist.indexOf(tag) !== -1;
+				});
+				next(null, tags);
+			}
+		], callback);
+	}
+
 	Topics.createEmptyTag = function (tag, callback) {
 		if (!tag) {
 			return callback(new Error('[[error:invalid-tag]]'));
@@ -273,7 +297,7 @@ module.exports = function (Topics) {
 				if (plugins.hasListeners('filter:topics.searchTags')) {
 					plugins.fireHook('filter:topics.searchTags', {data: data}, next);
 				} else {
-					findMatches(data.query, next);
+					findMatches(data.query, 0, next);
 				}
 			},
 			function (result, next) {
@@ -295,7 +319,7 @@ module.exports = function (Topics) {
 				if (plugins.hasListeners('filter:topics.autocompleteTags')) {
 					plugins.fireHook('filter:topics.autocompleteTags', {data: data}, next);
 				} else {
-					findMatches(data.query, next);
+					findMatches(data.query, data.cid, next);
 				}
 			},
 			function (result, next) {
@@ -304,10 +328,21 @@ module.exports = function (Topics) {
 		], callback);
 	};
 
-	function findMatches(query, callback) {
+	function findMatches(query, cid, callback) {
 		async.waterfall([
 			function (next) {
-				db.getSortedSetRevRange('tags:topic:count', 0, -1, next);
+				if (parseInt(cid, 10)) {
+					db.getSortedSetRange('cid:' + cid + ':tag:whitelist', 0, -1, next);
+				} else {
+					setImmediate(next, null, []);
+				}
+			},
+			function (tagWhitelist, next) {
+				if (tagWhitelist.length) {
+					setImmediate(next, null, tagWhitelist);
+				} else {
+					db.getSortedSetRevRange('tags:topic:count', 0, -1, next);
+				}
 			},
 			function (tags, next) {
 				query = query.toLowerCase();
diff --git a/src/views/admin/manage/category.tpl b/src/views/admin/manage/category.tpl
index 814b319913..7b2638b584 100644
--- a/src/views/admin/manage/category.tpl
+++ b/src/views/admin/manage/category.tpl
@@ -59,6 +59,10 @@
 								</div>
 							</div>
 						</fieldset>
+						<fieldset>
+							<label for="tag-whitelist">Tag Whitelist</label><br />
+							<input id="tag-whitelist" type="text" class="form-control" placeholder="Enter category tags here" data-name="tagWhitelist" value="" />
+						</fieldset>
 					</div>
 				</div>
 
diff --git a/test/categories.js b/test/categories.js
index 1a95846264..e0e035410b 100644
--- a/test/categories.js
+++ b/test/categories.js
@@ -541,7 +541,80 @@ describe('Categories', function () {
 				});
 			});
 		});
+	});
+
+	describe('tag whitelist', function () {
+		var cid;
+		var socketTopics = require('../src/socket.io/topics');
+		before(function (done) {
+			Categories.create({
+				name: 'test'
+			}, function (err, category) {
+				assert.ifError(err);
+				cid = category.cid;
+				done();
+			});
+		});
+
+		it('should error if data is invalid', function (done) {
+			socketTopics.isTagAllowed({uid: posterUid}, null, function (err) {
+				assert.equal(err.message, '[[error:invalid-data]]');
+				done();
+			});
+		});
+
+		it('should return true if category whitelist is empty', function (done) {
+			socketTopics.isTagAllowed({uid: posterUid}, {tag: 'notallowed', cid: cid}, function (err, allowed) {
+				assert.ifError(err);
+				assert(allowed);
+				done();
+			});
+		});
 
+		it('should add tags to category whitelist', function (done) {
+			var data = {};
+			data[cid] = {
+				tagWhitelist: 'nodebb,jquery,javascript'
+			};
+			Categories.update(data, function (err) {
+				assert.ifError(err);
+				db.getSortedSetRange('cid:' + cid + ':tag:whitelist', 0, -1, function (err, tagWhitelist) {
+					assert.ifError(err);
+					assert.deepEqual(['nodebb', 'jquery', 'javascript'], tagWhitelist);
+					done();
+				});
+			});
+		});
+
+		it('should return false if category whitelist does not have tag', function (done) {
+			socketTopics.isTagAllowed({uid: posterUid}, {tag: 'notallowed', cid: cid}, function (err, allowed) {
+				assert.ifError(err);
+				assert(!allowed);
+				done();
+			});
+		});
+
+		it('should return true if category whitelist has tag', function (done) {
+			socketTopics.isTagAllowed({uid: posterUid}, {tag: 'nodebb', cid: cid}, function (err, allowed) {
+				assert.ifError(err);
+				assert(allowed);
+				done();
+			});
+		});
+
+		it('should post a topic with only allowed tags', function (done) {
+			Topics.post({
+				uid: posterUid,
+				cid: cid,
+				title: 'Test Topic Title',
+				content: 'The content of test topic',
+				tags: ['nodebb', 'jquery', 'notallowed']
+			}, function (err, data) {
+				assert.ifError(err);
+				assert.equal(data.topicData.tags.length, 2);
+				done();
+			});
+		});
 	});