Merge branch 'disco-improve-modlogs' into 'develop'

Draft: Add modlog on unban, and split concept of private/public modlog entries

See merge request fatchan/jschan!345
Thomas Lynch 2 weeks ago
commit 9c7550d09f
  1. 6
      CHANGELOG.md
  2. 4
      controllers/forms.js
  3. 28
      controllers/forms/editbans.js
  4. 9
      db/bans.js
  5. 8
      db/modlogs.js
  6. 5
      lib/input/modlogactions.js
  7. 3
      lib/post/filteractions.js
  8. 3
      locales/en-GB.json
  9. 3
      locales/it-IT.json
  10. 3
      locales/pt-BR.json
  11. 3
      locales/pt-PT.json
  12. 3
      locales/ru-RU.json
  13. 12
      migrations/1.4.2.js
  14. 14
      migrations/1.5.0.js
  15. 5
      models/forms/actionhandler.js
  16. 7
      models/forms/banposter.js
  17. 19
      models/forms/changeboardsettings.js
  18. 18
      models/forms/create.js
  19. 1
      models/forms/editpost.js
  20. 8
      views/mixins/ban.pug
  21. 6
      views/mixins/postlink.pug
  22. 11
      views/pages/globalmanagelogs.pug
  23. 2
      views/pages/managelogs.pug
  24. 2
      views/pages/modlog.pug

@ -1,5 +1,11 @@
### 1.6.0
- Filters now have a "replace" mode, by @disco.
- Global bans now show the board it originated from as "Global (<board>)".
- Modlogs now have private entries for staff auditing
- So far covers creating boards, changing board settings, and ban editing/unban/upgrade.
- Modlog entries for e.g. board creation are global and not linked to the board and will not be deleted along with the board (duh).
- Private modlogs for edited global bans will also be available from the originating board.
- Added a global setting that toggles whether board ban lists include global bans that originated from that board.
- Global account management now has an option delete all boards owned by an account when deleting it.
- Bugfix moving posts to non existing board not correctly returning an error sometimes.

@ -57,7 +57,7 @@ router.post('/board/:board/transfer', useSession, sessionRefresh, csrf, Boards.e
hasPerms.any(Permissions.MANAGE_BOARD_OWNER, Permissions.MANAGE_GLOBAL_BOARDS), transferController.paramConverter, transferController.controller);
router.post('/board/:board/settings', geoIp, processIp, useSession, sessionRefresh, csrf, Boards.exists, setBoardLanguage, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_SETTINGS), boardSettingsController.paramConverter, boardSettingsController.controller);
router.post('/board/:board/editbans', useSession, sessionRefresh, csrf, Boards.exists, setBoardLanguage, calcPerms, isLoggedIn,
router.post('/board/:board/editbans', geoIp, processIp, useSession, sessionRefresh, csrf, Boards.exists, setBoardLanguage, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_BANS), editBansController.paramConverter, editBansController.controller); //edit bans
router.post('/board/:board/addfilter', useSession, sessionRefresh, csrf, Boards.exists, setBoardLanguage, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_SETTINGS), addFilterController.paramConverter, addFilterController.controller); //add new filter
@ -95,7 +95,7 @@ router.post('/board/:board/deletestaff', useSession, sessionRefresh, csrf, Board
hasPerms.one(Permissions.MANAGE_BOARD_STAFF), deleteStaffController.paramConverter, deleteStaffController.controller); //delete board staff
//global management forms
router.post('/global/editbans', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn,
router.post('/global/editbans', geoIp, processIp, useSession, sessionRefresh, csrf, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_GLOBAL_BANS), editBansController.paramConverter, editBansController.controller); //remove bans
router.post('/global/deleteboard', useSession, sessionRefresh, csrf, deleteBoardController.paramConverter, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_GLOBAL_BOARDS), deleteBoardController.controller); //delete board from global management panel

@ -8,6 +8,8 @@ const config = require(__dirname+'/../../lib/misc/config.js')
, editBanNote = require(__dirname+'/../../models/forms/editbannote.js')
, upgradeBans = require(__dirname+'/../../models/forms/upgradebans.js')
, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
, ModlogActions = require(__dirname+'/../../lib/input/modlogactions.js')
, { Bans, Modlogs } = require(__dirname+'/../../db/')
, { checkSchema, lengthBody, numberBody, inArrayBody } = require(__dirname+'/../../lib/input/schema.js');
module.exports = {
@ -44,6 +46,13 @@ module.exports = {
});
}
let bans = [];
try {
bans = await Bans.get(req.body.checkedbans, req.params.board ? req.params.board : null);
} catch (e) {
return next(e);
}
let amount = 0;
let message;
try {
@ -71,6 +80,25 @@ module.exports = {
default:
throw __('Invalid ban action'); //should never happen anyway
}
// inserting these into non-public modlogs
const modlogs = bans.map(b => ({
board: Array.isArray(b.board) ? b.board.find(bx => bx != null) : b.board, //TODO: if in future multiple are allowed, update this to use an array
showLinks: true,
postLinks: [],
actions: [ModlogActions.EDIT_BAN],
public: false,
date: new Date(),
showUser: true,
message: message,
user: req.session.user,
ip: {
cloak: res.locals.ip.cloak,
raw: res.locals.ip.raw,
}
}));
await Modlogs.insertMany(modlogs);
} catch (err) {
return next(err);
}

@ -29,6 +29,15 @@ module.exports = {
}).toArray();
},
get: (ids, board) => {
return db.find({
'board': board,
'_id': {
'$in': ids
},
}).toArray();
},
upgrade: async (board, ids, upgradeType) => {
const substrProjection = upgradeType === 1
? ['$ip.cloak', 0, 16]

@ -7,10 +7,11 @@ module.exports = {
db,
getDates: (board) => {
getDates: (board, publicOnly=true) => {
return db.aggregate([
{
'$match': {
...(publicOnly ? { 'public': true } : {}),
'board': board
}
},
@ -70,7 +71,7 @@ module.exports = {
return db.countDocuments(filter);
},
findBetweenDate: (board, start, end) => {
findBetweenDate: (board, start, end, publicOnly=true) => {
const startDate = Mongo.ObjectId.createFromTime(Math.floor(start.getTime()/1000));
const endDate = Mongo.ObjectId.createFromTime(Math.floor(end.getTime()/1000));
return db.find({
@ -78,7 +79,8 @@ module.exports = {
'$gte': startDate,
'$lte': endDate
},
'board': board._id
'board': board._id,
...(publicOnly ? { 'public': true } : {}),
}, {
projection: {
'ip': 0,

@ -27,4 +27,9 @@ module.exports = Object.seal(Object.freeze(Object.preventExtensions({
STICKY: 'Sticky',
CYCLE: 'Cycle',
EDIT_BAN: 'Edit Ban',
SETTINGS: 'Settings',
CREATE_BOARD: 'Create Board',
DELETE_BOARD: 'Delete Board',
})));

@ -18,10 +18,11 @@ module.exports = async (req, res, globalFilter, hitFilter, filterMode,
'redirect': redirect
});
} else {
const banBoard = globalFilter ? null : res.locals.board._id;
const banBoard = globalFilter ? [null, res.locals.board._id] : res.locals.board._id;
const banDate = new Date();
const banExpiry = new Date(filterBanDuration + banDate.getTime());
const ban = {
'global': globalFilter ? true : false,
'ip': {
'cloak': res.locals.ip.cloak,
'raw': res.locals.ip.raw,

@ -1427,5 +1427,6 @@
"Deleted %n records.": {
"one": "Deleted %s record.",
"other": "Deleted %n records."
}
},
"Created board /%s/": "Created board /%s/"
}

@ -1426,5 +1426,6 @@
"Deleted %n records.": {
"one": "Deleted %s record.",
"other": "Deleted %n records."
}
},
"Created board /%s/": "Created board /%s/"
}

@ -1430,5 +1430,6 @@
"Deleted %n records.": {
"one": "Deleted %s record.",
"other": "Deleted %n records."
}
},
"Created board /%s/": "Created board /%s/"
}

@ -1430,5 +1430,6 @@
"Deleted %n records.": {
"one": "Deleted %s record.",
"other": "Deleted %n records."
}
},
"Created board /%s/": "Created board /%s/"
}

@ -1501,5 +1501,6 @@
"Deleted %n records.": {
"one": "Deleted %s record.",
"other": "Deleted %n records."
}
},
"Created board /%s/": "Created board /%s/"
}

@ -0,0 +1,12 @@
'use strict';
module.exports = async(db) => {
console.log('Updating modlogs to add public flag');
await db.collection('modlog').updateMany({}, {
'$set': {
'public': true,
},
});
};

@ -0,0 +1,14 @@
'use strict';
module.exports = async(db) => {
console.log('Updating all bans with board: null to be global true');
await db.collection('bans').updateMany({
board: null,
}, {
'$set': {
'global': true,
},
});
};

@ -110,7 +110,7 @@ module.exports = async (req, res, next) => {
if (deleting) {
//OP delete protection. for old OPs or with a lot of replies
if (!isStaffOrGlobal) {
if (!isStaffOrGlobal) { //TODO: make this use a permission bit
const { deleteProtectionAge, deleteProtectionCount } = res.locals.board.settings;
if (deleteProtectionAge > 0 || deleteProtectionCount > 0) {
const protectedThread = res.locals.posts.some(p => {
@ -330,8 +330,9 @@ module.exports = async (req, res, next) => {
//per board actions, all actions combined to one event
modlog[post.board] = {
showLinks: !deleting,
postLinks: [],
postLinks: [], //TODO: rename this to just "links"
actions: modlogActions,
public: true,
date: logDate,
showUser: !req.body.hide_name || logUser === null ? true : false,
message: message,

@ -16,7 +16,7 @@ module.exports = async (req, res) => {
const bans = [];
if (req.body.ban || req.body.global_ban) {
const banBoard = req.body.global_ban ? null : req.params.board;
const banBoard = req.body.global_ban ? [null, req.params.board] : req.params.board;
const ipPosts = res.locals.posts.reduce((acc, post) => {
if (!acc[post.ip.cloak]) {
acc[post.ip.cloak] = [];
@ -55,6 +55,7 @@ module.exports = async (req, res) => {
.join('.');
}
bans.push({
'global': req.body.global_ban != null,
'range': banRange,
'ip': banIp,
'reason': banReason,
@ -73,7 +74,7 @@ module.exports = async (req, res) => {
}
if (req.body.report_ban || req.body.global_report_ban) {
const banBoard = req.body.global_report_ban ? null : req.params.board;
const banBoard = req.body.global_report_ban ? [null, req.params.board] : req.params.board;
res.locals.posts.forEach(post => {
let ips = [];
if (req.body.report_ban) {
@ -95,6 +96,7 @@ module.exports = async (req, res) => {
ips = ips.filter(n => n);
[...new Set(ips)].forEach(ip => {
bans.push({
'global': req.body.global_report_ban != null,
'range': 0,
'ip': ip,
'reason': banReason,
@ -106,6 +108,7 @@ module.exports = async (req, res) => {
allowAppeal,
'appeal': null,
'showUser': !req.body.hide_name,
'note': null,
'seen': false
});
});

@ -1,6 +1,7 @@
'use strict';
const { Boards, Posts } = require(__dirname+'/../../db/')
const { Boards, Posts, Modlogs } = require(__dirname+'/../../db/')
, ModlogActions = require(__dirname+'/../../lib/input/modlogactions.js')
, { debugLogs } = require(__dirname+'/../../configs/secrets.js')
, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
, config = require(__dirname+'/../../lib/misc/config.js')
@ -223,6 +224,22 @@ module.exports = async (req, res) => {
}
});
promises.push(Modlogs.insertOne({
board: req.params.board,
showLinks: true,
postLinks: [],
actions: [ModlogActions.SETTINGS],
public: false,
date: new Date(),
showUser: true,
message: __('Updated settings.'),
user: req.session.user,
ip: {
cloak: res.locals.ip.cloak,
raw: res.locals.ip.raw,
}
}));
//finish the promises in parallel e.g. removing files
if (promises.length > 0) {
await Promise.all(promises);

@ -1,6 +1,7 @@
'use strict';
const { Boards, Accounts } = require(__dirname+'/../../db/')
const { Boards, Accounts, Modlogs } = require(__dirname+'/../../db/')
, ModlogActions = require(__dirname+'/../../lib/input/modlogactions.js')
, { Binary } = require(__dirname+'/../../db/db.js')
, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
, roleManager = require(__dirname+'/../../lib/permission/rolemanager.js')
@ -65,6 +66,21 @@ module.exports = async (req, res) => {
};
await Promise.all([
Modlogs.insertOne({
board: null,
showLinks: true,
postLinks: [{ board: uri }],
actions: [ModlogActions.CREATE_BOARD],
public: false,
date: new Date(),
showUser: true,
message: __('Created board /%s/', uri),
user: req.session.user,
ip: {
cloak: res.locals.ip.cloak,
raw: res.locals.ip.raw,
}
}),
Boards.insertOne(newBoard),
Accounts.addOwnedBoard(owner, uri),
ensureDir(`${uploadDirectory}/html/${uri}`),

@ -168,6 +168,7 @@ todo: handle some more situations
thread: post.thread,
}],
actions: [ModlogActions.EDIT],
public: true, //TODO: take an optional checkbox also controlled by a BO/global delegated perm
date: new Date(),
showUser: req.body.hide_name ? false : true,
message: req.body.log_message || null,

@ -3,13 +3,13 @@ include ./post.pug
mixin ban(ban, banpage)
tr
td
if !banpage || (ban.appeal == null && ban.allowAppeal === true)
if (!board || !ban.global) && (!banpage || (ban.appeal == null && ban.allowAppeal === true))
input.post-check(type='checkbox', name='checkedbans' value=ban._id)
td
if ban.board
a(href=`/${ban.board}/`) /#{ban.board}/
if ban.global === true
| #{__('Global')} (#{ban.board.filter(n => n).join(', ')})
else
| #{__('Global')}
a(href=`/${ban.board}/`) /#{ban.board}/
td= ban.reason
- const ip = viewRawIp === true ? ban.ip.raw : ban.ip.cloak;
if viewRawIp === true

@ -1,2 +1,6 @@
mixin postlink(log, postLink, manageLink=false)
a.quote(href=`/${postLink.board || log.board}/${manageLink ? 'manage/' : ''}thread/${postLink.thread || postLink.postId}.html#${postLink.postId}`) &gt;&gt;#{postLink.postId}
if postLink.thread || postLink.postId
a.quote(href=`/${postLink.board || log.board}/${manageLink ? 'manage/' : ''}thread/${postLink.thread || postLink.postId}.html#${postLink.postId}`) &gt;&gt;#{postLink.postId}
else
a.quote(href=`/${postLink.board || log.board}/${manageLink ? 'manage/' : ''}index.html`) &gt;&gt;&gt;/#{postLink.board}/

@ -35,16 +35,19 @@ block content
th #{__('User')}
th #{__('IP')}
th #{__('Actions')}
th #{__('Posts')}
th #{__('Links')}
th #{__('Log Message')}
for log in logs
tr
- const logDate = new Date(log.date);
td: time.reltime(datetime=logDate.toISOString()) #{logDate.toLocaleString(pageLanguage, {hourCycle:'h23'})}
td
a(href=`/${log.board}/index.html`) /#{log.board}/
|
a(href=`?uri=${log.board}`) [+]
if log.board
a(href=`/${log.board}/index.html`) /#{log.board}/
|
a(href=`?uri=${log.board}`) [+]
else
| -
td
if log.user !== __('Unregistered User')
a(href=`accounts.html?username=${log.user}`) #{log.user}

@ -30,7 +30,7 @@ block content
th #{__('User')}
th #{__('IP')}
th #{__('Actions')}
th #{__('Posts')}
th #{__('Links')}
th #{__('Log Message')}
for log in logs
tr

@ -19,7 +19,7 @@ block content
th #{__('Date')}
th #{__('User')}
th #{__('Actions')}
th #{__('Posts')}
th #{__('Links')}
th #{__('Log Message')}
for log in logs
tr

Loading…
Cancel
Save