diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 5a0cc50e52..b5b90c04e5 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -36,7 +36,7 @@ "password-too-long": "Password too long", "user-banned": "User banned", - "user-banned-reason": "User banned (Reason: %1)", + "user-banned-reason": "Sorry, this account has been banned (Reason: %1)", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "ban-expiry-missing": "Please provide an end date for this ban", diff --git a/public/src/app.js b/public/src/app.js index 6b14d9e489..1cd6e49571 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -10,6 +10,7 @@ app.cacheBuster = null; (function () { var showWelcomeMessage = !!utils.params().loggedin; + var showBannedMessage = !!utils.params().banned; templates.setGlobal('config', config); @@ -246,22 +247,59 @@ app.cacheBuster = null; window.scrollTo(0, 0); }; - app.showLoginMessage = function () { - function showAlert() { - app.alert({ - type: 'success', + app.showMessages = function () { + var messages = { + login: { + format: 'alert', title: '[[global:welcome_back]] ' + app.user.username + '!', - message: '[[global:you_have_successfully_logged_in]]', - timeout: 5000 - }); + message: '[[global:you_have_successfully_logged_in]]' + }, + banned: { + format: 'modal', + title: '[[error:user-banned]]', + message: '[[error:user-banned-reason, ' + utils.params().banned + ']]' + } + }; + + function showAlert(type) { + switch (messages[type].format) { + case 'alert': + app.alert({ + type: 'success', + title: messages[type].title, + message: messages[type].message, + timeout: 5000 + }); + break; + + case 'modal': + require(['translator'], function (translator) { + translator.translate(messages[type].message, function (translated) { + bootbox.alert({ + title: messages[type].title, + message: translated + }); + }); + }); + break; + } } if (showWelcomeMessage) { showWelcomeMessage = false; if (document.readyState !== 'complete') { - $(document).ready(showAlert); + $(document).ready(showAlert.bind(null, 'login')); + } else { + showAlert('login'); + } + } + + if (showBannedMessage) { + showBannedMessage = false; + if (document.readyState !== 'complete') { + $(document).ready(showAlert.bind(null, 'banned')); } else { - showAlert(); + showAlert('banned'); } } }; diff --git a/public/src/sockets.js b/public/src/sockets.js index a8c33be1e8..cd88c3e5f7 100644 --- a/public/src/sockets.js +++ b/public/src/sockets.js @@ -42,7 +42,7 @@ app.isConnected = false; app.isConnected = true; if (!reconnecting) { - app.showLoginMessage(); + app.showMessages(); $(window).trigger('action:connected'); } diff --git a/src/middleware/header.js b/src/middleware/header.js index 5ad07d25dd..f0b494d9da 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -98,7 +98,8 @@ module.exports = function (middleware) { db.get('uid:' + req.uid + ':confirm:email:sent', next); }, navigation: async.apply(navigation.get), - tags: async.apply(meta.tags.parse, res.locals.metaTags, res.locals.linkTags) + tags: async.apply(meta.tags.parse, res.locals.metaTags, res.locals.linkTags), + banReason: async.apply(user.getBannedReason, req.uid) }, function (err, results) { if (err) { return callback(err); @@ -106,7 +107,7 @@ module.exports = function (middleware) { if (results.user && parseInt(results.user.banned, 10) === 1) { req.logout(); - return res.redirect('/'); + return res.redirect('/?banned=' + (results.banReason || '')); } results.user.isAdmin = results.isAdmin; diff --git a/src/user.js b/src/user.js index 7cc01c89f3..31dda0d991 100644 --- a/src/user.js +++ b/src/user.js @@ -19,6 +19,7 @@ var meta = require('./meta'); require('./user/data')(User); require('./user/auth')(User); + require('./user/bans')(User); require('./user/create')(User); require('./user/posts')(User); require('./user/topics')(User); diff --git a/src/user/admin.js b/src/user/admin.js index 3bd9ca1d46..44aa72e059 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -56,96 +56,6 @@ module.exports = function (User) { ], callback); }; - User.ban = function (uid, until, reason, callback) { - // "until" (optional) is unix timestamp in milliseconds - // "reason" (optional) is a string - if (!callback && typeof until === 'function') { - callback = until; - until = 0; - reason = ''; - } else if (!callback && typeof reason === 'function') { - callback = reason; - reason = ''; - } - - var now = Date.now(); - - until = parseInt(until, 10); - if (isNaN(until)) { - return callback(new Error('[[error:ban-expiry-missing]]')); - } - - var tasks = [ - async.apply(User.setUserField, uid, 'banned', 1), - async.apply(db.sortedSetAdd, 'users:banned', now, uid), - async.apply(db.sortedSetAdd, 'uid:' + uid + ':bans', now, until) - ]; - - if (until > 0 && now < until) { - tasks.push(async.apply(db.sortedSetAdd, 'users:banned:expire', until, uid)); - tasks.push(async.apply(User.setUserField, uid, 'banned:expire', until)); - } else { - until = 0; - } - - if (reason) { - tasks.push(async.apply(db.sortedSetAdd, 'banned:' + uid + ':reasons', now, reason)); - } - - async.series(tasks, function (err) { - if (err) { - return callback(err); - } - - plugins.fireHook('action:user.banned', { - uid: uid, - until: until > 0 ? until : undefined - }); - callback(); - }); - }; - - User.unban = function (uid, callback) { - async.waterfall([ - function (next) { - User.setUserFields(uid, {banned: 0, 'banned:expire': 0}, next); - }, - function (next) { - db.sortedSetsRemove(['users:banned', 'users:banned:expire'], uid, next); - }, - function (next) { - plugins.fireHook('action:user.unbanned', {uid: uid}); - next(); - } - ], callback); - }; - - User.isBanned = function (uid, callback) { - async.waterfall([ - async.apply(User.getUserFields, uid, ['banned', 'banned:expire']), - function (userData, next) { - var banned = parseInt(userData.banned, 10) === 1; - if (!banned) { - return next(null, banned); - } - - // If they are banned, see if the ban has expired - var stillBanned = !userData['banned:expire'] || Date.now() < userData['banned:expire']; - - if (stillBanned) { - return next(null, true); - } - async.parallel([ - async.apply(db.sortedSetRemove.bind(db), 'users:banned:expire', uid), - async.apply(db.sortedSetRemove.bind(db), 'users:banned', uid), - async.apply(User.setUserFields, uid, {banned:0, 'banned:expire': 0}) - ], function (err) { - next(err, false); - }); - } - ], callback); - }; - User.resetFlags = function (uids, callback) { if (!Array.isArray(uids) || !uids.length) { return callback(); diff --git a/src/user/bans.js b/src/user/bans.js new file mode 100644 index 0000000000..574f9d587b --- /dev/null +++ b/src/user/bans.js @@ -0,0 +1,108 @@ +'use strict'; + +var async = require('async'); +var db = require('../database'); +var plugins = require('../plugins'); + +module.exports = function (User) { + User.ban = function (uid, until, reason, callback) { + // "until" (optional) is unix timestamp in milliseconds + // "reason" (optional) is a string + if (!callback && typeof until === 'function') { + callback = until; + until = 0; + reason = ''; + } else if (!callback && typeof reason === 'function') { + callback = reason; + reason = ''; + } + + var now = Date.now(); + + until = parseInt(until, 10); + if (isNaN(until)) { + return callback(new Error('[[error:ban-expiry-missing]]')); + } + + var tasks = [ + async.apply(User.setUserField, uid, 'banned', 1), + async.apply(db.sortedSetAdd, 'users:banned', now, uid), + async.apply(db.sortedSetAdd, 'uid:' + uid + ':bans', now, until) + ]; + + if (until > 0 && now < until) { + tasks.push(async.apply(db.sortedSetAdd, 'users:banned:expire', until, uid)); + tasks.push(async.apply(User.setUserField, uid, 'banned:expire', until)); + } else { + until = 0; + } + + if (reason) { + tasks.push(async.apply(db.sortedSetAdd, 'banned:' + uid + ':reasons', now, reason)); + } + + async.series(tasks, function (err) { + if (err) { + return callback(err); + } + + plugins.fireHook('action:user.banned', { + uid: uid, + until: until > 0 ? until : undefined + }); + callback(); + }); + }; + + User.unban = function (uid, callback) { + async.waterfall([ + function (next) { + User.setUserFields(uid, {banned: 0, 'banned:expire': 0}, next); + }, + function (next) { + db.sortedSetsRemove(['users:banned', 'users:banned:expire'], uid, next); + }, + function (next) { + plugins.fireHook('action:user.unbanned', {uid: uid}); + next(); + } + ], callback); + }; + + User.isBanned = function (uid, callback) { + async.waterfall([ + async.apply(User.getUserFields, uid, ['banned', 'banned:expire']), + function (userData, next) { + var banned = parseInt(userData.banned, 10) === 1; + if (!banned) { + return next(null, banned); + } + + // If they are banned, see if the ban has expired + var stillBanned = !userData['banned:expire'] || Date.now() < userData['banned:expire']; + + if (stillBanned) { + return next(null, true); + } + async.parallel([ + async.apply(db.sortedSetRemove.bind(db), 'users:banned:expire', uid), + async.apply(db.sortedSetRemove.bind(db), 'users:banned', uid), + async.apply(User.setUserFields, uid, {banned:0, 'banned:expire': 0}) + ], function (err) { + next(err, false); + }); + } + ], callback); + }; + + User.getBannedReason = function (uid, callback) { + // Grabs the latest ban reason + db.getSortedSetRevRange('banned:' + uid + ':reasons', 0, 1, function (err, reasons) { + if (err) { + return callback(err); + } + + callback(null, reasons.length ? reasons[0] : ''); + }); + }; +};