mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 08:36:12 +01:00
feat: added PUT/DELETE /api/v1/users/:uid/ban routes
This commit is contained in:
69
openapi.yaml
69
openapi.yaml
@@ -242,19 +242,74 @@ paths:
|
||||
$ref: '#/components/schemas/Status'
|
||||
response:
|
||||
type: object
|
||||
'/{uid}/ban':
|
||||
put:
|
||||
tags:
|
||||
- users
|
||||
summary: bans a user
|
||||
parameters:
|
||||
- in: path
|
||||
name: uid
|
||||
schema:
|
||||
type: integer
|
||||
required: true
|
||||
description: uid of the user to ban
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
until:
|
||||
type: number
|
||||
description: UNIX timestamp of the ban expiry
|
||||
example: 1585775608076
|
||||
reason:
|
||||
type: string
|
||||
example: the reason for the ban
|
||||
responses:
|
||||
'200':
|
||||
description: successfully banned user
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
$ref: '#/components/schemas/Status'
|
||||
response:
|
||||
type: object
|
||||
delete:
|
||||
tags:
|
||||
- users
|
||||
summary: unbans a user
|
||||
parameters:
|
||||
- in: path
|
||||
name: uid
|
||||
schema:
|
||||
type: integer
|
||||
required: true
|
||||
description: uid of the user to unban
|
||||
responses:
|
||||
'200':
|
||||
description: successfully unbanned user
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
$ref: '#/components/schemas/Status'
|
||||
response:
|
||||
type: object
|
||||
components:
|
||||
schemas:
|
||||
Status:
|
||||
type: object
|
||||
properties:
|
||||
code:
|
||||
allOf:
|
||||
- title: Success
|
||||
type: string
|
||||
example: ok
|
||||
- title: Error
|
||||
type: string
|
||||
example: error
|
||||
type: string
|
||||
example: ok
|
||||
message:
|
||||
type: string
|
||||
example: OK
|
||||
|
||||
@@ -44,6 +44,7 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete'], funct
|
||||
$('.users-table [component="user/select/single"]:checked').parents('.user-row').remove();
|
||||
}
|
||||
|
||||
// use onSuccess/onFail instead
|
||||
function done(successMessage, className, flag) {
|
||||
return function (err) {
|
||||
if (err) {
|
||||
@@ -57,6 +58,18 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete'], funct
|
||||
};
|
||||
}
|
||||
|
||||
function onSuccess(successMessage, className, flag) {
|
||||
app.alertSuccess(successMessage);
|
||||
if (className) {
|
||||
update(className, flag);
|
||||
}
|
||||
unselectAll();
|
||||
}
|
||||
|
||||
function onFail(err) {
|
||||
app.alertError(err.message);
|
||||
}
|
||||
|
||||
$('[component="user/select/all"]').on('click', function () {
|
||||
$('.users-table [component="user/select/single"]').prop('checked', $(this).is(':checked'));
|
||||
});
|
||||
@@ -119,7 +132,20 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete'], funct
|
||||
|
||||
bootbox.confirm((uids.length > 1 ? '[[admin/manage/users:alerts.confirm-ban-multi]]' : '[[admin/manage/users:alerts.confirm-ban]]'), function (confirm) {
|
||||
if (confirm) {
|
||||
socket.emit('user.banUsers', { uids: uids, reason: '' }, done('[[admin/manage/users:alerts.ban-success]]', '.ban', true));
|
||||
var requests = uids.map(function (uid) {
|
||||
return $.ajax({
|
||||
url: config.relative_path + '/api/v1/users/' + uid + '/ban',
|
||||
method: 'put',
|
||||
});
|
||||
});
|
||||
|
||||
$.when(requests)
|
||||
.done(function () {
|
||||
onSuccess('[[admin/manage/users:alerts.ban-success]]', '.ban', true);
|
||||
})
|
||||
.fail(function (ev) {
|
||||
onFail(ev.responseJSON.status);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -150,7 +176,24 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete'], funct
|
||||
return data;
|
||||
}, {});
|
||||
var until = formData.length > 0 ? (Date.now() + (formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1))) : 0;
|
||||
socket.emit('user.banUsers', { uids: uids, until: until, reason: formData.reason }, done('[[admin/manage/users:alerts.ban-success]]', '.ban', true));
|
||||
|
||||
var requests = uids.map(function (uid) {
|
||||
return $.ajax({
|
||||
url: config.relative_path + '/api/v1/users/' + uid + '/ban',
|
||||
method: 'put',
|
||||
data: {
|
||||
until: until,
|
||||
reason: formData.reason,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
$.when(requests)
|
||||
.done(function () {
|
||||
onSuccess('[[admin/manage/users:alerts.ban-success]]', '.ban', true);
|
||||
}).fail(function (ev) {
|
||||
onFail(ev.responseJSON.status);
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -165,7 +208,19 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete'], funct
|
||||
return false; // specifically to keep the menu open
|
||||
}
|
||||
|
||||
socket.emit('user.unbanUsers', uids, done('[[admin/manage/users:alerts.unban-success]]', '.ban', false));
|
||||
var requests = uids.map(function (uid) {
|
||||
return $.ajax({
|
||||
url: config.relative_path + '/api/v1/users/' + uid + '/ban',
|
||||
method: 'delete',
|
||||
});
|
||||
});
|
||||
|
||||
$.when(requests)
|
||||
.done(function () {
|
||||
onSuccess('[[admin/manage/users:alerts.unban-success]]', '.ban', false);
|
||||
}).fail(function (ev) {
|
||||
onFail(ev.responseJSON.status);
|
||||
});
|
||||
});
|
||||
|
||||
$('.reset-lockout').on('click', function () {
|
||||
|
||||
@@ -154,20 +154,21 @@ define('forum/account/header', [
|
||||
|
||||
var until = formData.length > 0 ? (Date.now() + (formData.length * 1000 * 60 * 60 * (parseInt(formData.unit, 10) ? 24 : 1))) : 0;
|
||||
|
||||
socket.emit('user.banUsers', {
|
||||
uids: [theirid],
|
||||
until: until,
|
||||
reason: formData.reason || '',
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: config.relative_path + '/api/v1/users/' + theirid + '/ban',
|
||||
method: 'put',
|
||||
data: {
|
||||
until: until,
|
||||
reason: formData.reason || '',
|
||||
},
|
||||
}).done(function () {
|
||||
if (typeof onSuccess === 'function') {
|
||||
return onSuccess();
|
||||
}
|
||||
|
||||
ajaxify.refresh();
|
||||
}).fail(function (ev) {
|
||||
app.alertError(ev.responseJSON.status.message);
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -177,11 +178,13 @@ define('forum/account/header', [
|
||||
}
|
||||
|
||||
function unbanAccount() {
|
||||
socket.emit('user.unbanUsers', [ajaxify.data.theirid], function (err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
$.ajax({
|
||||
url: config.relative_path + '/api/v1/users/' + ajaxify.data.theirid + '/ban',
|
||||
method: 'delete',
|
||||
}).done(function () {
|
||||
ajaxify.refresh();
|
||||
}).fail(function (ev) {
|
||||
app.alertError(ev.responseJSON.status.message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,11 @@ const privileges = require('../../privileges');
|
||||
const notifications = require('../../notifications');
|
||||
const meta = require('../../meta');
|
||||
const events = require('../../events');
|
||||
const translator = require('../../translator');
|
||||
|
||||
const db = require('../../database');
|
||||
const helpers = require('../helpers');
|
||||
const sockets = require('../../socket.io');
|
||||
|
||||
const Users = module.exports;
|
||||
|
||||
@@ -155,3 +159,62 @@ Users.unfollow = async (req, res) => {
|
||||
});
|
||||
helpers.formatApiResponse(200, res);
|
||||
};
|
||||
|
||||
Users.ban = async (req, res) => {
|
||||
if (!await privileges.users.hasBanPrivilege(req.user.uid)) {
|
||||
return helpers.formatApiResponse(403, res, new Error('[[error:no-privileges]]'));
|
||||
} else if (await user.isAdministrator(req.params.uid)) {
|
||||
return helpers.formatApiResponse(403, res, new Error('[[error:cant-ban-other-admins]]'));
|
||||
}
|
||||
|
||||
const banData = await user.bans.ban(req.params.uid, req.body.until, req.body.reason);
|
||||
await db.setObjectField('uid:' + req.params.uid + ':ban:' + banData.timestamp, 'fromUid', req.user.uid);
|
||||
|
||||
if (!req.body.reason) {
|
||||
req.body.reason = await translator.translate('[[user:info.banned-no-reason]]');
|
||||
}
|
||||
|
||||
sockets.in('uid_' + req.params.uid).emit('event:banned', {
|
||||
until: req.body.until,
|
||||
reason: req.body.reason,
|
||||
});
|
||||
|
||||
await events.log({
|
||||
type: 'user-ban',
|
||||
uid: req.user.uid,
|
||||
targetUid: req.params.uid,
|
||||
ip: req.ip,
|
||||
reason: req.body.reason || undefined,
|
||||
});
|
||||
plugins.fireHook('action:user.banned', {
|
||||
callerUid: req.user.uid,
|
||||
ip: req.ip,
|
||||
uid: req.params.uid,
|
||||
until: req.body.until > 0 ? req.body.until : undefined,
|
||||
reason: req.body.reason || undefined,
|
||||
});
|
||||
await user.auth.revokeAllSessions(req.params.uid);
|
||||
|
||||
helpers.formatApiResponse(200, res);
|
||||
};
|
||||
|
||||
Users.unban = async (req, res) => {
|
||||
if (!await privileges.users.hasBanPrivilege(req.user.uid)) {
|
||||
return helpers.formatApiResponse(403, res, new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
await user.bans.unban(req.params.uid);
|
||||
await events.log({
|
||||
type: 'user-unban',
|
||||
uid: req.user.uid,
|
||||
targetUid: req.params.uid,
|
||||
ip: req.ip,
|
||||
});
|
||||
plugins.fireHook('action:user.unbanned', {
|
||||
callerUid: req.user.uid,
|
||||
ip: req.ip,
|
||||
uid: req.params.uid,
|
||||
});
|
||||
|
||||
helpers.formatApiResponse(200, res);
|
||||
};
|
||||
|
||||
@@ -32,59 +32,8 @@ function authenticatedRoutes() {
|
||||
setupApiRoute(router, '/:uid/follow', middleware, [...middlewares], 'post', controllers.write.users.follow);
|
||||
setupApiRoute(router, '/:uid/follow', middleware, [...middlewares], 'delete', controllers.write.users.unfollow);
|
||||
|
||||
// app.put('/:uid/follow', apiMiddleware.requireUser, function(req, res) {
|
||||
// Users.follow(req.user.uid, req.params.uid, function(err) {
|
||||
// return errorHandler.handle(err, res);
|
||||
// });
|
||||
// });
|
||||
|
||||
// app.delete('/:uid/follow', apiMiddleware.requireUser, function(req, res) {
|
||||
// Users.unfollow(req.user.uid, req.params.uid, function(err) {
|
||||
// return errorHandler.handle(err, res);
|
||||
// });
|
||||
// });
|
||||
|
||||
// app.route('/:uid/chats')
|
||||
// .post(apiMiddleware.requireUser, function(req, res) {
|
||||
// if (!utils.checkRequired(['message'], req, res)) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// var timestamp = parseInt(req.body.timestamp, 10) || Date.now();
|
||||
|
||||
// function addMessage(roomId) {
|
||||
// Messaging.addMessage({
|
||||
// uid: req.user.uid,
|
||||
// roomId: roomId,
|
||||
// content: req.body.message,
|
||||
// timestamp: timestamp,
|
||||
// }, function(err, message) {
|
||||
// if (parseInt(req.body.quiet, 10) !== 1) {
|
||||
// Messaging.notifyUsersInRoom(req.user.uid, roomId, message);
|
||||
// }
|
||||
|
||||
// return errorHandler.handle(err, res, message);
|
||||
// });
|
||||
// }
|
||||
|
||||
// Messaging.canMessageUser(req.user.uid, req.params.uid, function(err) {
|
||||
// if (err) {
|
||||
// return errorHandler.handle(err, res);
|
||||
// }
|
||||
|
||||
// if (req.body.roomId) {
|
||||
// addMessage(req.body.roomId);
|
||||
// } else {
|
||||
// Messaging.newRoom(req.user.uid, [req.params.uid], function(err, roomId) {
|
||||
// if (err) {
|
||||
// return errorHandler.handle(err, res);
|
||||
// }
|
||||
|
||||
// addMessage(roomId);
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
setupApiRoute(router, '/:uid/ban', middleware, [...middlewares, middleware.exposePrivileges], 'put', controllers.write.users.ban);
|
||||
setupApiRoute(router, '/:uid/ban', middleware, [...middlewares, middleware.exposePrivileges], 'delete', controllers.write.users.unban);
|
||||
|
||||
// app.route('/:uid/ban')
|
||||
// .put(apiMiddleware.requireUser, apiMiddleware.requireAdmin, function(req, res) {
|
||||
@@ -131,6 +80,52 @@ function authenticatedRoutes() {
|
||||
// errorHandler.handle(err, res);
|
||||
// });
|
||||
// });
|
||||
|
||||
/**
|
||||
* Chat routes were not migrated because chats may get refactored... also the logic is derpy
|
||||
* It also does not take into account multiple chats for a given user.
|
||||
*/
|
||||
// app.route('/:uid/chats')
|
||||
// .post(apiMiddleware.requireUser, function(req, res) {
|
||||
// if (!utils.checkRequired(['message'], req, res)) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// var timestamp = parseInt(req.body.timestamp, 10) || Date.now();
|
||||
|
||||
// function addMessage(roomId) {
|
||||
// Messaging.addMessage({
|
||||
// uid: req.user.uid,
|
||||
// roomId: roomId,
|
||||
// content: req.body.message,
|
||||
// timestamp: timestamp,
|
||||
// }, function(err, message) {
|
||||
// if (parseInt(req.body.quiet, 10) !== 1) {
|
||||
// Messaging.notifyUsersInRoom(req.user.uid, roomId, message);
|
||||
// }
|
||||
|
||||
// return errorHandler.handle(err, res, message);
|
||||
// });
|
||||
// }
|
||||
|
||||
// Messaging.canMessageUser(req.user.uid, req.params.uid, function(err) {
|
||||
// if (err) {
|
||||
// return errorHandler.handle(err, res);
|
||||
// }
|
||||
|
||||
// if (req.body.roomId) {
|
||||
// addMessage(req.body.roomId);
|
||||
// } else {
|
||||
// Messaging.newRoom(req.user.uid, [req.params.uid], function(err, roomId) {
|
||||
// if (err) {
|
||||
// return errorHandler.handle(err, res);
|
||||
// }
|
||||
|
||||
// addMessage(roomId);
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
const winston = require('winston');
|
||||
|
||||
const db = require('../../database');
|
||||
const user = require('../../user');
|
||||
const meta = require('../../meta');
|
||||
const websockets = require('../index');
|
||||
const events = require('../../events');
|
||||
const privileges = require('../../privileges');
|
||||
const plugins = require('../../plugins');
|
||||
const emailer = require('../../emailer');
|
||||
const translator = require('../../translator');
|
||||
const utils = require('../../../public/src/utils');
|
||||
const flags = require('../../flags');
|
||||
|
||||
module.exports = function (SocketUser) {
|
||||
SocketUser.banUsers = async function (socket, data) {
|
||||
websockets.warnDeprecated(socket, 'PUT /api/v1/users/:uid/ban');
|
||||
|
||||
if (!data || !Array.isArray(data.uids)) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
@@ -43,6 +40,8 @@ module.exports = function (SocketUser) {
|
||||
};
|
||||
|
||||
SocketUser.unbanUsers = async function (socket, uids) {
|
||||
websockets.warnDeprecated(socket, 'DELETE /api/v1/users/:uid/ban');
|
||||
|
||||
await toggleBan(socket.uid, uids, async function (uid) {
|
||||
await user.bans.unban(uid);
|
||||
await events.log({
|
||||
@@ -76,19 +75,7 @@ module.exports = function (SocketUser) {
|
||||
if (isAdmin) {
|
||||
throw new Error('[[error:cant-ban-other-admins]]');
|
||||
}
|
||||
const username = await user.getUserField(uid, 'username');
|
||||
const siteTitle = meta.config.title || 'NodeBB';
|
||||
const data = {
|
||||
subject: '[[email:banned.subject, ' + siteTitle + ']]',
|
||||
username: username,
|
||||
until: until ? utils.toISOString(until) : false,
|
||||
reason: reason,
|
||||
};
|
||||
try {
|
||||
await emailer.send('banned', uid, data);
|
||||
} catch (err) {
|
||||
winston.error('[emailer.send] ' + err.message);
|
||||
}
|
||||
|
||||
const banData = await user.bans.ban(uid, until, reason);
|
||||
await db.setObjectField('uid:' + uid + ':ban:' + banData.timestamp, 'fromUid', callerUid);
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const winston = require('winston');
|
||||
|
||||
const meta = require('../meta');
|
||||
const utils = require('../utils');
|
||||
const emailer = require('../emailer');
|
||||
const db = require('../database');
|
||||
|
||||
module.exports = function (User) {
|
||||
@@ -38,6 +43,22 @@ module.exports = function (User) {
|
||||
} else {
|
||||
await db.sortedSetRemove('users:banned:expire', uid);
|
||||
}
|
||||
|
||||
// Email notification of ban
|
||||
const username = await User.getUserField(uid, 'username');
|
||||
const siteTitle = meta.config.title || 'NodeBB';
|
||||
const data = {
|
||||
subject: '[[email:banned.subject, ' + siteTitle + ']]',
|
||||
username: username,
|
||||
until: until ? utils.toISOString(until) : false,
|
||||
reason: reason,
|
||||
};
|
||||
try {
|
||||
await emailer.send('banned', uid, data);
|
||||
} catch (err) {
|
||||
winston.error('[emailer.send] ' + err.message);
|
||||
}
|
||||
|
||||
return banData;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user