Merge branch 'permissions-update' into develop

indiachan-spamvector
Thomas Lynch 2 years ago
commit 475c0df8fc
  1. 11
      CHANGELOG.md
  2. 2
      configs/nginx/snippets/jschan_common_routes.conf
  3. 36
      configs/template.js.example
  4. 93
      controllers/forms.js
  5. 15
      controllers/forms/actions.js
  6. 48
      controllers/forms/addstaff.js
  7. 8
      controllers/forms/boardsettings.js
  8. 7
      controllers/forms/create.js
  9. 4
      controllers/forms/deleteaccount.js
  10. 7
      controllers/forms/deleteaccounts.js
  11. 4
      controllers/forms/deleteboard.js
  12. 42
      controllers/forms/deletestaff.js
  13. 52
      controllers/forms/editaccount.js
  14. 2
      controllers/forms/editcustompage.js
  15. 3
      controllers/forms/editpost.js
  16. 42
      controllers/forms/editrole.js
  17. 45
      controllers/forms/editstaff.js
  18. 6
      controllers/forms/globalactions.js
  19. 23
      controllers/forms/globalsettings.js
  20. 7
      controllers/forms/index.js
  21. 7
      controllers/forms/register.js
  22. 5
      controllers/forms/transfer.js
  23. 91
      controllers/pages.js
  24. 72
      db/accounts.js
  25. 86
      db/boards.js
  26. 4
      db/db.js
  27. 1
      db/index.js
  28. 13
      db/posts.js
  29. 46
      db/roles.js
  30. 6
      gulp/res/js/forms.js
  31. 5
      gulp/res/js/live.js
  32. 54
      gulpfile.js
  33. 56
      helpers/checks/actionchecker.js
  34. 42
      helpers/checks/bancheck.js
  35. 48
      helpers/checks/calcperms.js
  36. 4
      helpers/checks/calcpermsmiddleware.js
  37. 20
      helpers/checks/hasperms.js
  38. 61
      helpers/checks/haspermsmiddleware.js
  39. 7
      helpers/checks/spamcheck.js
  40. 12
      helpers/decodequeryip.js
  41. 2
      helpers/imagehash.js
  42. 43
      helpers/permission.js
  43. 44
      helpers/permissions.js
  44. 42
      helpers/permissiontext.js
  45. 2
      helpers/posting/diceroll.js
  46. 6
      helpers/posting/linkmatch.js
  47. 46
      helpers/posting/markdown.js
  48. 12
      helpers/posting/message.js
  49. 46
      helpers/posting/name.js
  50. 6
      helpers/processip.js
  51. 17
      helpers/render.js
  52. 39
      helpers/rolemanager.js
  53. 9
      helpers/schema.js
  54. 4
      helpers/sessionrefresh.js
  55. 22
      migrations/0.0.23.js
  56. 136
      migrations/0.4.0.js
  57. 11
      models/forms/actionhandler.js
  58. 2
      models/forms/addcustompage.js
  59. 2
      models/forms/addnews.js
  60. 20
      models/forms/addstaff.js
  61. 28
      models/forms/changeboardsettings.js
  62. 15
      models/forms/changeglobalsettings.js
  63. 9
      models/forms/create.js
  64. 66
      models/forms/deleteaccounts.js
  65. 4
      models/forms/deleteboard.js
  66. 24
      models/forms/deletestaff.js
  67. 74
      models/forms/editaccount.js
  68. 68
      models/forms/editaccounts.js
  69. 2
      models/forms/editcustompage.js
  70. 2
      models/forms/editnews.js
  71. 9
      models/forms/editpost.js
  72. 85
      models/forms/editrole.js
  73. 40
      models/forms/editstaff.js
  74. 45
      models/forms/makepost.js
  75. 5
      models/forms/register.js
  76. 9
      models/forms/resign.js
  77. 44
      models/forms/transferboard.js
  78. 31
      models/pages/account.js
  79. 6
      models/pages/globalmanage/accounts.js
  80. 5
      models/pages/globalmanage/bans.js
  81. 1
      models/pages/globalmanage/boards.js
  82. 29
      models/pages/globalmanage/editaccount.js
  83. 1
      models/pages/globalmanage/editnews.js
  84. 25
      models/pages/globalmanage/editrole.js
  85. 3
      models/pages/globalmanage/index.js
  86. 6
      models/pages/globalmanage/logs.js
  87. 1
      models/pages/globalmanage/news.js
  88. 9
      models/pages/globalmanage/recent.js
  89. 7
      models/pages/globalmanage/reports.js
  90. 19
      models/pages/globalmanage/roles.js
  91. 1
      models/pages/globalmanage/settings.js
  92. 1
      models/pages/index.js
  93. 5
      models/pages/manage/bans.js
  94. 24
      models/pages/manage/editstaff.js
  95. 3
      models/pages/manage/index.js
  96. 3
      models/pages/manage/logs.js
  97. 13
      models/pages/manage/mypermissions.js
  98. 11
      models/pages/manage/permissions.js
  99. 11
      models/pages/manage/recent.js
  100. 7
      models/pages/manage/reports.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,5 +1,16 @@
### 0.4.0
- Hashed IPs now use an irc-style "cloaking".
- New permission system. No more "levels" shit.
- Boards no longer have "moderators" but "staff".
- New "staff" page in board management, to add/remove/edit staff.
- The global "accounts" and board "staff" pages allow editing individual permissions.
- There is now more fine permission/access control system for global and board-level stuff.
- Users with permission to edit accounts can edit accounts on a global level and grant them permissions.
- Users with permission to edit board staff can do the same on a per-board level.
- Better continuity between pages like "news", "accounts", "custompages", "staff" and the associated editieng page. The manage/globalmaange navbar stays in place with the appropriate section highlighted.
- More linking between moderation interfaces e.g. globals board list will now show direct link to view the owners account.
- Made some board and page titles more consistent.
- Bugfixes.
### 0.3.3
- Minor bugfix to filenames of expanded images being incorrectly truncated in some circumstances, when "image loading bars" is enabled.

@ -29,7 +29,7 @@ location /captcha {
}
# authed, no cache pages
location ~* ^/((\w+/manage/.*|globalmanage/(reports|bans|recent|boards|globallogs|news|accounts|settings))|account|create|csrf)\.(html|json)$ {
location ~* ^/((\w+/manage/.*|globalmanage/(reports|bans|recent|boards|globallogs|news|editnews/.*|accounts|editaccount/.*|roles|editrole/.*|settings))|mypermissions|account|create|csrf)\.(html|json)$ {
expires 0;
try_files /dev/null @backend-private;
}

@ -67,39 +67,13 @@ module.exports = {
//block bypasses
blockBypass: {
enabled: true,
enabled: false,
forceAnonymizers: true,
expireAfterUses: 50, //however many (attempted) posts per block bypass captcha
expireAfterTime: 86400000, //expiry in ms regardless if the limit was reached, default 1 day
bypassDnsbl: false,
},
// permission level required to see UNHASHED ips. -1 for ips to be hashed even for root user. not recommended to change after installation
ipHashPermLevel: 0,
// permission level required to delete boards
deleteBoardPermLevel: 2,
//todo: migrate the above 2 into this
permLevels: {
markdown: {
pink: 4,
green: 4,
bold: 4,
underline: 4,
strike: 4,
italic: 4,
title: 4,
spoiler: 4,
mono: 4,
code: 4,
link: 4,
detected: 4,
dice: 4,
fortune: 0,
},
},
/* delete files immediately rather than pruning later. usually disabled to prevent re-thumbnailing and processing commonly
uploaded files, but deleting immediately is better if you are concerned about "deleted" content not being immediately removed */
pruneImmediately: true,
@ -136,14 +110,12 @@ module.exports = {
//option to prune ips on posts older than x days
pruneIps: 0,
//dont store raw ips at all (in jschan), not counting webservers or whatever else.
dontStoreRawIps: false,
//enable the webring (also copy configs/webring.json.example -> configs/webring.json and edit)
enableWebring: false,
//let all users create new boards
enableUserBoardCreation: false,
//let all users register new accounts
enableUserAccountCreation: true,
//extension for thumbnails that do not contain transparency (png will be used)
thumbExtension: '.jpg',
//.gif images > thumbnail size will have animated .gif thumbnails, overriding thumbExtension

@ -9,6 +9,7 @@ const express = require('express')
, geoAndTor = require(__dirname+'/../helpers/geoip.js')
, processIp = require(__dirname+'/../helpers/processip.js')
, calcPerms = require(__dirname+'/../helpers/checks/calcpermsmiddleware.js')
, Permissions = require(__dirname+'/../helpers/permissions.js')
, hasPerms = require(__dirname+'/../helpers/checks/haspermsmiddleware.js')
, numFiles = require(__dirname+'/../helpers/numfiles.js')
, imageHashes = require(__dirname+'/../helpers/imagehash.js')
@ -27,50 +28,86 @@ const express = require('express')
editNewsController, deleteNewsController, uploadBannersController, deleteBannersController, addFlagsController,
deleteFlagsController, boardSettingsController, transferController, addAssetsController, deleteAssetsController,
resignController, deleteAccountController, loginController, registerController, changePasswordController,
editAccountsController, globalSettingsController, createBoardController, makePostController,
editCustomPageController, editPostController, newCaptcha, blockBypass, logout } = require(__dirname+'/forms/index.js');
deleteAccountsController, editAccountController, globalSettingsController, createBoardController, makePostController,
addStaffController, deleteStaffController, editStaffController, editCustomPageController, editPostController,
editRoleController, newCaptcha, blockBypass, logout } = require(__dirname+'/forms/index.js');
//make new post
router.post('/board/:board/post', geoAndTor, fileMiddlewares.postsEarly, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, fileMiddlewares.posts,
makePostController.paramConverter, verifyCaptcha, numFiles, blockBypassCheck, dnsblCheck, imageHashes, makePostController.controller);
router.post('/board/:board/modpost', geoAndTor, fileMiddlewares.postsEarly, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), fileMiddlewares.posts,
makePostController.paramConverter, csrf, numFiles, blockBypassCheck, dnsblCheck, makePostController.controller); //mod post has token instead of captcha
makePostController.paramConverter, verifyCaptcha, numFiles, blockBypassCheck, dnsblCheck, imageHashes, makePostController.controller);
router.post('/board/:board/modpost', geoAndTor, fileMiddlewares.postsEarly, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), fileMiddlewares.posts, makePostController.paramConverter, csrf, numFiles, blockBypassCheck, dnsblCheck, makePostController.controller); //mod post has token instead of captcha
//post actions
router.post('/board/:board/actions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, Boards.exists, calcPerms, banCheck, actionController.paramConverter, verifyCaptcha, actionController.controller); //public, with captcha
router.post('/board/:board/modactions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, Boards.exists, calcPerms, banCheck, isLoggedIn, hasPerms(3), actionController.paramConverter, actionController.controller); //board manage page
router.post('/global/actions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(1), globalActionController.paramConverter, globalActionController.controller); //global manage page
router.post('/board/:board/modactions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, Boards.exists, calcPerms, banCheck, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), actionController.paramConverter, actionController.controller); //board manage page
router.post('/global/actions', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_GLOBAL_GENERAL), globalActionController.paramConverter, globalActionController.controller); //global manage page
//appeal ban
router.post('/appeal', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, appealController.paramConverter, verifyCaptcha, appealController.controller);
//edit post
router.post('/editpost', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, editPostController.paramConverter, Boards.bodyExists, calcPerms, hasPerms(3), editPostController.controller);
router.post('/editpost', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, editPostController.paramConverter, Boards.bodyExists, calcPerms,
hasPerms.any(Permissions.MANAGE_GLOBAL_GENERAL, Permissions.MANAGE_BOARD_GENERAL), editPostController.controller);
//board management forms
router.post('/board/:board/transfer', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), transferController.paramConverter, transferController.controller);
router.post('/board/:board/settings', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), boardSettingsController.paramConverter, boardSettingsController.controller);
router.post('/board/:board/editbans', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(3), editBansController.paramConverter, editBansController.controller); //edit bans
router.post('/board/:board/deleteboard', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(config.get.deleteBoardPermLevel), deleteBoardController.controller); //delete board
router.post('/board/:board/transfer', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.any(Permissions.MANAGE_BOARD_OWNER, Permissions.MANAGE_GLOBAL_BOARDS), transferController.paramConverter, transferController.controller);
router.post('/board/:board/settings', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_SETTINGS), boardSettingsController.paramConverter, boardSettingsController.controller);
router.post('/board/:board/editbans', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_BANS), editBansController.paramConverter, editBansController.controller); //edit bans
router.post('/board/:board/deleteboard', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.any(Permissions.MANAGE_BOARD_OWNER, Permissions.MANAGE_GLOBAL_BOARDS), deleteBoardController.controller); //delete board
//board crud banners, flags, assets, custompages
router.post('/board/:board/addbanners', useSession, sessionRefresh, fileMiddlewares.banner, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), numFiles, uploadBannersController.controller); //add banners
router.post('/board/:board/deletebanners', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), deleteBannersController.paramConverter, deleteBannersController.controller); //delete banners
router.post('/board/:board/addassets', useSession, sessionRefresh, fileMiddlewares.asset, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), numFiles, addAssetsController.controller); //add assets
router.post('/board/:board/deleteassets', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), deleteAssetsController.paramConverter, deleteAssetsController.controller); //delete assets
router.post('/board/:board/addflags', useSession, sessionRefresh, fileMiddlewares.flag, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), numFiles, addFlagsController.controller); //add flags
router.post('/board/:board/deleteflags', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), deleteFlagsController.paramConverter, deleteFlagsController.controller); //delete flags
router.post('/board/:board/addcustompages', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), addCustomPageController.paramConverter, addCustomPageController.controller); //add custom pages
router.post('/board/:board/deletecustompages', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), deleteCustomPageController.paramConverter, deleteCustomPageController.controller); //delete custom pages
router.post('/board/:board/editcustompage', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), editCustomPageController.paramConverter, editCustomPageController.controller); //edit custom page
router.post('/board/:board/addbanners', useSession, sessionRefresh, fileMiddlewares.banner, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), numFiles, uploadBannersController.controller); //add banners
router.post('/board/:board/deletebanners', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), deleteBannersController.paramConverter, deleteBannersController.controller); //delete banners
router.post('/board/:board/addassets', useSession, sessionRefresh, fileMiddlewares.asset, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), numFiles, addAssetsController.controller); //add assets
router.post('/board/:board/deleteassets', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), deleteAssetsController.paramConverter, deleteAssetsController.controller); //delete assets
router.post('/board/:board/addflags', useSession, sessionRefresh, fileMiddlewares.flag, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), numFiles, addFlagsController.controller); //add flags
router.post('/board/:board/deleteflags', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), deleteFlagsController.paramConverter, deleteFlagsController.controller); //delete flags
router.post('/board/:board/addcustompages', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), addCustomPageController.paramConverter, addCustomPageController.controller); //add custom pages
router.post('/board/:board/deletecustompages', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), deleteCustomPageController.paramConverter, deleteCustomPageController.controller); //delete custom pages
router.post('/board/:board/editcustompage', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), editCustomPageController.paramConverter, editCustomPageController.controller); //edit custom page
router.post('/board/:board/addstaff', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_STAFF), addStaffController.paramConverter, addStaffController.controller); //add board staff
router.post('/board/:board/editstaff', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_BOARD_STAFF), editStaffController.paramConverter, editStaffController.controller); //edit staff permission
router.post('/board/:board/deletestaff', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn,
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, hasPerms(1), editBansController.paramConverter, editBansController.controller); //remove bans
router.post('/global/deleteboard', useSession, sessionRefresh, csrf, deleteBoardController.paramConverter, calcPerms, isLoggedIn, hasPerms(Math.min(config.get.deleteBoardPermLevel, 1)), deleteBoardController.controller); //delete board from global management panel
router.post('/global/addnews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), addNewsController.paramConverter, addNewsController.controller); //add new newspost
router.post('/global/editnews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), editNewsController.paramConverter, editNewsController.controller); //add new newspost
router.post('/global/deletenews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), deleteNewsController.paramConverter, deleteNewsController.controller); //delete news
router.post('/global/editaccounts', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), editAccountsController.paramConverter, editAccountsController.controller); //account editing
router.post('/global/settings', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(0), globalSettingsController.paramConverter, globalSettingsController.controller); //global settings
router.post('/global/editbans', 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
router.post('/global/addnews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_GLOBAL_NEWS), addNewsController.paramConverter, addNewsController.controller); //add new newspost
router.post('/global/editnews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_GLOBAL_NEWS), editNewsController.paramConverter, editNewsController.controller); //add new newspost
router.post('/global/deletenews', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_GLOBAL_NEWS), deleteNewsController.paramConverter, deleteNewsController.controller); //delete news
router.post('/global/deleteaccounts', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_GLOBAL_ACCOUNTS), deleteAccountsController.paramConverter, deleteAccountsController.controller); //account deleting
router.post('/global/editaccount', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_GLOBAL_ACCOUNTS), editAccountController.paramConverter, editAccountController.controller); //account editing
router.post('/global/editrole', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_GLOBAL_ROLES), editRoleController.paramConverter, editRoleController.controller); //role editing
router.post('/global/settings', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn,
hasPerms.one(Permissions.MANAGE_GLOBAL_SETTINGS), globalSettingsController.paramConverter, globalSettingsController.controller); //global settings
//create board
router.post('/create', geoAndTor, torPreBypassCheck, processIp, useSession, sessionRefresh, isLoggedIn, verifyCaptcha, calcPerms, createBoardController.paramConverter, createBoardController.controller);

@ -1,6 +1,7 @@
'use strict';
const { Posts } = require(__dirname+'/../../db/')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, config = require(__dirname+'/../../config.js')
, actionHandler = require(__dirname+'/../../models/forms/actionhandler.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
@ -23,21 +24,21 @@ module.exports = {
const { globalLimits } = config.get;
res.locals.actions = actionChecker(req);
res.locals.actions = actionChecker(req, res);
const errors = await checkSchema([
{ result: lengthBody(req.body.checkedposts, 1), expected: false, blocking: true, error: 'Must select at least one post' },
{ result: lengthBody(res.locals.actions.validActions, 1), expected: false, blocking: true, error: 'No actions selected' },
{ result: lengthBody(req.body.checkedposts, 1, globalLimits.multiInputs.posts.anon), permLevel: 3, expected: false, error: `Must not select >${globalLimits.multiInputs.posts.anon} posts per request` },
{ result: lengthBody(req.body.checkedposts, 1, globalLimits.multiInputs.posts.anon), permission: Permissions.MANAGE_BOARD_GENERAL, expected: false, error: `Must not select >${globalLimits.multiInputs.posts.anon} posts per request` },
{ result: lengthBody(req.body.checkedposts, 1, globalLimits.multiInputs.posts.staff), expected: false, error: `Must not select >${globalLimits.multiInputs.posts.staff} posts per request` },
{ result: (existsBody(req.body.report_ban) && !req.body.checkedreports), expected: false, error: 'Must select post and reports to ban reporter' },
{ result: (existsBody(req.body.checkedreports) && !req.body.report_ban), expected: false, error: 'Must select a report action if checked reports' },
{ result: (existsBody(req.body.checkedreports) && !req.body.checkedposts), expected: false, error: 'Must check parent post if checking reports for report action' },
{ result: (existsBody(req.body.checkedreports) && existsBody(req.body.checkedposts) && lengthBody(req.body.checkedreports, 1, req.body.checkedposts.length*5)), expected: false, error: 'Invalid number of reports checked' },
{ result: (res.locals.permLevel > res.locals.actions.authRequired), expected: false, blocking: true, error: 'No permission' },
{ result: (existsBody(req.body.delete) && !res.locals.board.settings.userPostDelete), permLevel: 3, expected: false, error: 'User post deletion is disabled on this board' },
{ result: (existsBody(req.body.spoiler) && !res.locals.board.settings.userPostSpoiler), permLevel: 3, expected: false, error: 'User file spoiling is disabled on this board' },
{ result: (existsBody(req.body.unlink_file) && !res.locals.board.settings.userPostUnlink), permLevel: 3, expected: false, error: 'User file unlinking is disabled on this board' },
{ result: res.locals.actions.hasPermission, expected: true, blocking: true, error: 'No permission' },
{ result: (existsBody(req.body.delete) && !res.locals.board.settings.userPostDelete), permission: Permissions.MANAGE_BOARD_GENERAL, expected: false, error: 'User post deletion is disabled on this board' },
{ result: (existsBody(req.body.spoiler) && !res.locals.board.settings.userPostSpoiler), permission: Permissions.MANAGE_BOARD_GENERAL, expected: false, error: 'User file spoiling is disabled on this board' },
{ result: (existsBody(req.body.unlink_file) && !res.locals.board.settings.userPostUnlink), permission: Permissions.MANAGE_BOARD_GENERAL, expected: false, error: 'User file unlinking is disabled on this board' },
{ result: (existsBody(req.body.edit) && lengthBody(req.body.checkedposts, 1, 1)), expected: false, error: 'Must select only 1 post for edit action' },
{ result: lengthBody(req.body.postpassword, 0, globalLimits.fieldLength.postpassword), expected: false, error: `Password must be ${globalLimits.fieldLength.postpassword} characters or less` },
{ result: lengthBody(req.body.report_reason, 0, globalLimits.fieldLength.report_reason), expected: false, error: `Report must be ${globalLimits.fieldLength.report_reason} characters or less` },
@ -52,7 +53,7 @@ module.exports = {
}
return true;
}, expected: true, error: 'Destination thread for move does not exist' },
], res.locals.permLevel);
], res.locals.permissions);
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {

@ -0,0 +1,48 @@
'use strict';
const addStaff = require(__dirname+'/../../models/forms/addstaff.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, config = require(__dirname+'/../../config.js')
, { Accounts } = require(__dirname+'/../../db/')
, paramConverter = require(__dirname+'/../../helpers/paramconverter.js')
, { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable,
inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js');
module.exports = {
paramConverter: paramConverter({
trimFields: ['username'],
}),
controller: async (req, res, next) => {
const { globalLimits } = config.get;
const errors = await checkSchema([
{ result: existsBody(req.body.username), expected: true, error: 'Missing staff username' },
{ result: lengthBody(req.body.username, 0, 50), expected: false, error: 'Username must be 50 characters or less' },
{ result: (res.locals.board.owner === req.body.username), expected: false, blocking: true, error: 'User is already board owner' },
{ result: (res.locals.board.staff[req.body.username] != null), expected: false, blocking: true, error: 'User is already staff' },
{ result: async () => {
const numAccounts = await Accounts.countUsers([req.body.username]);
return numAccounts > 0;
}, expected: true, error: 'User does not exist' },
]);
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'errors': errors,
'redirect': req.headers.referer || `/${req.params.board}/manage/staff.html`,
});
}
try {
await addStaff(req, res, next);
} catch (err) {
return next(err);
}
}
}

@ -1,7 +1,7 @@
'use strict';
const changeBoardSettings = require(__dirname+'/../../models/forms/changeboardsettings.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, { themes, codeThemes } = require(__dirname+'/../../helpers/themes.js')
, { Ratelimits } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
@ -33,7 +33,7 @@ module.exports = {
{ result: lengthBody(req.body.tags, 0, 2000), expected: false, error: 'Tags length must be 2000 characters or less' },
{ result: lengthBody(req.body.filters, 0, 20000), expected: false, error: 'Filters length must be 20000 characters or less' },
{ result: lengthBody(req.body.custom_css, 0, globalLimits.customCss.max), expected: false, error: `Custom CSS must be ${globalLimits.customCss.max} characters or less` },
{ result: arrayInBody(globalLimits.customCss.filters, req.body.custom_css), permLevel: 1, expected: false, error: `Custom CSS strict mode is enabled and does not allow the following: "${globalLimits.customCss.filters.join('", "')}"` },
{ result: arrayInBody(globalLimits.customCss.filters, req.body.custom_css), permission: Permissions.ROOT, expected: false, error: `Custom CSS strict mode is enabled and does not allow the following: "${globalLimits.customCss.filters.join('", "')}"` },
{ result: lengthBody(req.body.moderators, 0, 500), expected: false, error: 'Moderators length must be 500 characters orless' },
{ result: lengthBody(req.body.name, 1, globalLimits.fieldLength.boardname), expected: false, error: `Board name must be 1-${globalLimits.fieldLength.boardname} characters` },
{ result: lengthBody(req.body.default_name, 0, 50), expected: false, error: 'Anon name must be 50 characters or less' },
@ -73,7 +73,7 @@ module.exports = {
{ result: numberBody(req.body.delete_protection_count, 0), expected: true, error: 'Invalid OP thread reply count delete protection' },
{ result: inArrayBody(req.body.theme, themes), expected: true, error: 'Invalid theme' },
{ result: inArrayBody(req.body.code_theme, codeThemes), expected: true, error: 'Invalid code theme' },
], res.locals.permLevel);
], res.locals.permissions);
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {
@ -83,7 +83,7 @@ module.exports = {
});
}
if (res.locals.permLevel > 1) { //if not global staff or above
if (!res.locals.permissions.get(Permissions.BYPASS_RATELIMITS)) {
const ratelimitBoard = await Ratelimits.incrmentQuota(req.params.board, 'settings', rateLimitCost.boardSettings); //2 changes a minute
const ratelimitIp = res.locals.anonymizer ? 0 : (await Ratelimits.incrmentQuota(res.locals.ip.cloak, 'settings', rateLimitCost.boardSettings));
if (ratelimitBoard > 100 || ratelimitIp > 100) {

@ -1,6 +1,7 @@
'use strict';
const createBoard = require(__dirname+'/../../models/forms/create.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, config = require(__dirname+'/../../config.js')
, alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js')
@ -16,17 +17,17 @@ module.exports = {
controller: async (req, res, next) => {
const { enableUserBoardCreation, globalLimits } = config.get;
const { globalLimits } = config.get;
const errors = await checkSchema([
{ result: (enableUserBoardCreation === true), blocking: true, permLevel: 1, expected: true, error: 'User board creation is currently disabled' },
{ result: res.locals.permissions.get(Permissions.CREATE_BOARD), blocking: true, expected: true, error: 'No permission' },
{ result: existsBody(req.body.uri), expected: true, error: 'Missing URI' },
{ result: lengthBody(req.body.uri, 0, globalLimits.fieldLength.uri), expected: false, error: `URI must be ${globalLimits.fieldLength.uri} characters or less` },
{ result: existsBody(req.body.name), expected: true, error: 'Missing name' },
{ result: lengthBody(req.body.name, 0, globalLimits.fieldLength.boardname), expected: false, error: `Name must be ${globalLimits.fieldLength.boardname} characters or less` },
{ result: alphaNumericRegex.test(req.body.uri), expected: true, error: 'URI must contain a-z 0-9 only' },
{ result: lengthBody(req.body.name, 0, globalLimits.fieldLength.description), expected: false, error: `Description must be ${globalLimits.fieldLength.description} characters or less` },
], res.locals.permLevel);
], res.locals.permissions);
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {

@ -12,11 +12,11 @@ module.exports = {
controller: async (req, res, next) => {
const { modBoards, ownedBoards } = res.locals.user;
const { staffBoards, ownedBoards } = res.locals.user;
const errors = await checkSchema([
{ result: existsBody(req.body.confirm), expected: true, error: 'Missing confirmation' },
{ result: (numberBody(ownedBoards.length, 0, 0) && numberBody(modBoards.length, 0, 0)), expected: true, error: 'You cannot delete your account while you hold staff position on any board' },
{ result: (numberBody(ownedBoards.length, 0, 0) && numberBody(staffBoards.length, 0, 0)), expected: true, error: 'You cannot delete your account while you hold staff position on any board' },
]);
if (errors.length > 0) {

@ -1,6 +1,6 @@
'use strict';
const editAccounts = require(__dirname+'/../../models/forms/editaccounts.js')
const deleteAccounts = require(__dirname+'/../../models/forms/deleteaccounts.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, paramConverter = require(__dirname+'/../../helpers/paramconverter.js')
, { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable,
@ -10,15 +10,12 @@ module.exports = {
paramConverter: paramConverter({
allowedArrays: ['checkedaccounts'],
numberFields: ['auth_level'],
}),
controller: async (req, res, next) => {
const errors = await checkSchema([
{ result: lengthBody(req.body.checkedaccounts, 1), expected: false, error: 'Must select at least one account' },
{ result: !existsBody(req.body.auth_level) || numberBody(req.body.auth_level, 0, 4), expected: true, error: 'Invalid account type' },
{ result: existsBody(req.body.auth_level) || existsBody(req.body.delete_account), expected: true, error: 'Missing account type or delete action' }
]);
if (errors.length > 0) {
@ -30,7 +27,7 @@ module.exports = {
}
try {
await editAccounts(req, res, next);
await deleteAccounts(req, res, next);
} catch (err) {
return next(err);
}

@ -21,12 +21,12 @@ module.exports = {
{ result: existsBody(req.body.confirm), expected: true, error: 'Missing confirmation' },
{ result: existsBody(req.body.uri), expected: true, error: 'Missing URI' },
{ result: alphaNumericRegex.test(req.body.uri), blocking: true, expected: true, error: 'URI must contain a-z 0-9 only'},
{ result: (req.params.board === req.body.uri), expected: true, error: 'URI does not match current board' },
{ result: req.params.board == null || (req.params.board === req.body.uri), expected: true, error: 'URI does not match current board' },
{ result: async () => {
board = await Boards.findOne(req.body.uri);
return board != null;
}, expected: true, error: `Board /${req.body.uri}/ does not exist` }
], res.locals.permLevel);
]);
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {

@ -0,0 +1,42 @@
'use strict';
const deleteStaff = require(__dirname+'/../../models/forms/deletestaff.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, paramConverter = require(__dirname+'/../../helpers/paramconverter.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable,
inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js');
module.exports = {
paramConverter: paramConverter({
allowedArrays: ['checkedstaff'],
}),
controller: async (req, res, next) => {
const errors = await checkSchema([
{ result: lengthBody(req.body.checkedstaff, 1), expected: false, error: 'Must select at least one staff to delete' },
{ result: existsBody(req.body.checkedstaff) && req.body.checkedstaff.some(s => !res.locals.board.staff[s]), expected: false, error: 'Invalid staff selection' },
{ result: existsBody(req.body.checkedstaff) && req.body.checkedstaff.some(s => s === res.locals.board.owner), expected: false, permission: Permissions.ROOT, error: "You can't delete the board owner" },
//not really necessary, but its a bit retarded to "delete yourself" as staff this way
{ result: existsBody(req.body.checkedstaff) && req.body.checkedstaff.some(s => s === res.locals.user.username), expected: false, error: 'Resign from the accounts page instead' },
]);
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'errors': errors,
'redirect': req.headers.referer || `/${req.params.board}/manage/staff.html`,
})
}
try {
await deleteStaff(req, res, next);
} catch (err) {
return next(err);
}
}
}

@ -0,0 +1,52 @@
'use strict';
const editAccount = require(__dirname+'/../../models/forms/editaccount.js')
, { Accounts } = require(__dirname+'/../../db/')
, alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, paramConverter = require(__dirname+'/../../helpers/paramconverter.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, roleManager = require(__dirname+'/../../helpers/rolemanager.js')
, { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable,
inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js');
module.exports = {
paramConverter: paramConverter({
trimFields: ['username'],
}),
controller: async (req, res, next) => {
const errors = await checkSchema([
{ result: existsBody(req.body.username), expected: true, error: 'Missing username' },
{ result: lengthBody(req.body.username, 1, 50), expected: false, error: 'Username must be 50 characters or less' },
{ result: alphaNumericRegex.test(req.body.username), expected: true, error: 'Username must contain a-z 0-9 only' },
{ result: async () => {
res.locals.editingAccount = await Accounts.findOne(req.body.username);
return res.locals.editingAccount != null;
}, expected: true, error: 'Invalid account username' },
{ result: (res.locals.user.username === req.body.username), expected: false, error: "You can't edit your own permissions" },
{ result: !existsBody(req.body.template) //no template, OR the template is a valid one
|| inArrayBody(req.body.template, [roleManager.roles.ANON.base64, roleManager.roles.GLOBAL_STAFF.base64,
roleManager.roles.ADMIN.base64, roleManager.roles.BOARD_STAFF.base64, roleManager.roles.BOARD_OWNER.base64]),
expected: true, error: "Invalid template selection" },
]);
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'errors': errors,
'redirect': req.headers.referer || `/${req.params.board}/manage/staff.html`,
});
}
try {
await editAccount(req, res, next);
} catch (err) {
return next(err);
}
}
}

@ -47,7 +47,7 @@ module.exports = {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'errors': errors,
'redirect': req.headers.referer || '/${req.params.board}/manage/custompages.html'
'redirect': req.headers.referer || `/${req.params.board}/manage/custompages.html`,
});
}

@ -1,6 +1,7 @@
'use strict';
const editPost = require(__dirname+'/../../models/forms/editpost.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, config = require(__dirname+'/../../config.js')
, { Ratelimits, Posts, Boards } = require(__dirname+'/../../db/')
@ -41,7 +42,7 @@ module.exports = {
});
}
if (res.locals.permLevel > 1) { //if not global staff or above
if (!res.locals.permissions.get(Permissions.BYPASS_RATELIMITS)) {
const ratelimitUser = await Ratelimits.incrmentQuota(req.session.user, 'edit', rateLimitCost.editPost);
const ratelimitIp = res.locals.anonymizer ? 0 : (await Ratelimits.incrmentQuota(res.locals.ip.cloak, 'edit', rateLimitCost.editPost));
if (ratelimitUser > 100 || ratelimitIp > 100) {

@ -0,0 +1,42 @@
'use strict';
const editRole = require(__dirname+'/../../models/forms/editrole.js')
, { Roles } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, paramConverter = require(__dirname+'/../../helpers/paramconverter.js')
, { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable,
inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js');
module.exports = {
paramConverter: paramConverter({
objectIdFields: ['roleid'],
}),
controller: async (req, res, next) => {
const errors = await checkSchema([
{ result: existsBody(req.body.roleid), expected: true, error: 'Missing role id' },
{ result: async () => {
res.locals.editingRole = await Roles.findOne(req.body.roleid);
return res.locals.editingRole != null && res.locals.editingRole.name !== 'ROOT';
}, blocking: true, expected: true, error: "You can't edit this role" },
]);
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'errors': errors,
'redirect': req.headers.referer || `/${req.params.board}/manage/roles.html`,
});
}
try {
await editRole(req, res, next);
} catch (err) {
return next(err);
}
}
}

@ -0,0 +1,45 @@
'use strict';
const editStaff = require(__dirname+'/../../models/forms/editstaff.js')
, { Accounts } = require(__dirname+'/../../db/')
, alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, paramConverter = require(__dirname+'/../../helpers/paramconverter.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable,
inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js');
module.exports = {
paramConverter: paramConverter({
trimFields: ['username'],
}),
controller: async (req, res, next) => {
const errors = await checkSchema([
{ result: existsBody(req.body.username), expected: true, error: 'Missing username' },
{ result: lengthBody(req.body.username, 1, 50), expected: false, error: 'Username must be 50 characters or less' },
{ result: alphaNumericRegex.test(req.body.username), expected: true, error: 'Username must contain a-z 0-9 only' },
{ result: (res.locals.board.staff[req.body.username] != null), expected: true, error: 'Invalid staff username' },
{ result: (req.body.username === res.locals.board.owner), expected: false, error: "You can't edit the permissions of the board owner" },
{ result: (res.locals.user.username === req.body.username), expected: false, error: "You can't edit your own permissions" },
]);
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'errors': errors,
'redirect': req.headers.referer || `/${req.params.board}/manage/staff.html`,
});
}
try {
await editStaff(req, res, next);
} catch (err) {
return next(err);
}
}
}

@ -23,7 +23,7 @@ module.exports = {
const { globalLimits } = config.get;
res.locals.actions = actionChecker(req);
res.locals.actions = actionChecker(req, res);
const errors = await checkSchema([
{ result: lengthBody(req.body.globalcheckedposts, 1), expected: false, blocking: true, error: 'Must select at least one post' },
@ -35,12 +35,12 @@ module.exports = {
{ result: (existsBody(req.body.checkedreports) && req.body.globalcheckedposts
&& lengthBody(req.body.checkedreports, 1, req.body.globalcheckedposts.length*5)), expected: false, error: 'Invalid number of reports checked' },
{ result: (res.locals.actions.numGlobal > 0 && res.locals.actions.validActions.length <= res.locals.actions.numGlobal), expected: true, blocking: true, error: 'Invalid actions selected' },
{ result: (res.locals.permLevel > res.locals.actions.authRequired), expected: false, blocking: true, error: 'No permission' },
{ result: res.locals.actions.hasPermission, expected: true, blocking: true, error: 'No permission' },
{ result: (existsBody(req.body.edit) && lengthBody(req.body.globalcheckedposts, 1, 1)), expected: false, error: 'Must select only 1 post for edit action' },
{ result: lengthBody(req.body.postpassword, 0, globalLimits.fieldLength.postpassword), expected: false, error: `Password must be ${globalLimits.fieldLength.postpassword} characters or less` },
{ result: lengthBody(req.body.ban_reason, 0, globalLimits.fieldLength.ban_reason), expected: false, error: `Ban reason must be ${globalLimits.fieldLength.ban_reason} characters or less` },
{ result: lengthBody(req.body.log_message, 0, globalLimits.fieldLength.log_message), expected: false, error: `Modlog message must be ${globalLimits.fieldLength.log_message} characters or less` },
], res.locals.permLevel);
]);
//return the errors
if (errors.length > 0) {

@ -16,7 +16,7 @@ module.exports = {
numberFields: ['filter_mode', 'auth_level',
'captcha_options_generate_limit', 'captcha_options_grid_size', 'captcha_options_image_size', 'captcha_options_num_distorts_min', 'captcha_options_num_distorts_max',
'captcha_options_distortion', 'captcha_options_grid_icon_y_offset', 'flood_timers_same_content_same_ip', 'flood_timers_same_content_any_ip', 'flood_timers_any_content_same_ip',
'block_bypass_expire_after_uses', 'ip_hash_perm_level', 'delete_board_perm_level', 'rate_limit_cost_captcha', 'rate_limit_cost_board_settings', 'rate_limit_cost_edit_post',
'block_bypass_expire_after_uses', 'rate_limit_cost_captcha', 'rate_limit_cost_board_settings', 'rate_limit_cost_edit_post',
'overboard_limit', 'overboard_catalog_limit', 'lock_wait', 'prune_modlogs', 'prune_ips', 'thumb_size', 'video_thumb_percentage', 'quote_limit', 'preview_replies',
'sticky_preview_replies', 'early_404_fraction', 'early_404_replies', 'max_recent_news', 'highlight_options_threshold', 'global_limits_thread_limit_min',
'global_limits_thread_limit_max', 'global_limits_reply_limit_min', 'global_limits_reply_limit_max', 'global_limits_bump_limit_min', 'global_limits_bump_limit_max',
@ -30,10 +30,7 @@ module.exports = {
'board_defaults_lock_mode', 'board_defaults_file_r9k_mode', 'board_defaults_message_r9k_mode', 'board_defaults_captcha_mode', 'board_defaults_tph_trigger',
'board_defaults_pph_trigger', 'board_defaults_tph_trigger_action', 'board_defaults_pph_trigger_action', 'board_defaults_captcha_reset', 'board_defaults_lock_reset',
'board_defaults_thread_limit', 'board_defaults_reply_limit', 'board_defaults_bump_limit', 'board_defaults_max_files', 'board_defaults_min_thread_message_length',
'board_defaults_min_reply_message_length', 'board_defaults_max_thread_message_length', 'board_defaults_max_reply_message_length', 'board_defaults_filter_mode', 'board_defaults_delete_protection_count',
'perm_levels_markdown_pink', 'perm_levels_markdown_green', 'perm_levels_markdown_bold', 'perm_levels_markdown_underline', 'perm_levels_markdown_strike',
'perm_levels_markdown_italic', 'perm_levels_markdown_title', 'perm_levels_markdown_spoiler', 'perm_levels_markdown_mono', 'perm_levels_markdown_code',
'perm_levels_markdown_link', 'perm_levels_markdown_detected', 'perm_levels_markdown_dice', 'perm_levels_markdown_fortune'], //damn, this has a lot of numbers lol
'board_defaults_min_reply_message_length', 'board_defaults_max_thread_message_length', 'board_defaults_max_reply_message_length', 'board_defaults_filter_mode', 'board_defaults_delete_protection_count']
}),
controller: async (req, res, next) => {
@ -94,22 +91,6 @@ module.exports = {
{ result: numberBody(req.body.flood_timers_any_content_same_ip), expected: true, error: 'Invalid flood time any content same ip' },
{ result: numberBody(req.body.block_bypass_expire_after_uses), expected: true, error: 'Block bypass expire after uses must be a number > 0' },
{ result: numberBody(req.body.block_bypass_expire_after_time), expected: true, error: 'Invalid block bypass expire after time' },
{ result: numberBody(req.body.ip_hash_perm_level, -1), expected: true, error: 'Invalid ip hash perm level' },
{ result: numberBody(req.body.delete_board_perm_level, 0, 4), expected: true, error: 'Invalid delete board perm level' },
{ result: numberBody(req.body.perm_levels_markdown_green, 0, 4), expected: true, error: 'Invalid greentext markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_pink, 0, 4), expected: true, error: 'Invalid pinktext markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_title, 0, 4), expected: true, error: 'Invalid title markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_bold, 0, 4), expected: true, error: 'Invalid bold markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_underline, 0, 4), expected: true, error: 'Invalid underline markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_strike, 0, 4), expected: true, error: 'Invalid strike markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_italic, 0, 4), expected: true, error: 'Invalid italicmarkdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_mono, 0, 4), expected: true, error: 'Invalid mono markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_code, 0, 4), expected: true, error: 'Invalid code block markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_spoiler, 0, 4), expected: true, error: 'Invalid spoiler markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_detected, 0, 4), expected: true, error: 'Invalid detected markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_link, 0, 4), expected: true, error: 'Invalid link markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_dice, 0, 4), expected: true, error: 'Invalid dice markdown perm level' },
{ result: numberBody(req.body.perm_levels_markdown_fortune, 0, 4), expected: true, error: 'Invalid fortune markdown perm level' },
{ result: numberBody(req.body.rate_limit_cost_captcha, 1, 100), expected: true, error: 'Rate limit cost captcha must be a number from 1-100' },
{ result: numberBody(req.body.rate_limit_cost_board_settings, 1, 100), expected: true, error: 'Rate limit cost board settings must be a number from 1-100' },
{ result: numberBody(req.body.rate_limit_cost_edit_post, 1, 100), expected: true, error: 'Rate limit cost edit post must be a number from 1-100' },

@ -23,14 +23,19 @@ module.exports = {
transferController: require(__dirname+'/transfer.js'),
resignController: require(__dirname+'/resign.js'),
deleteAccountController: require(__dirname+'/deleteaccount.js'),
editAccountController: require(__dirname+'/editaccount.js'),
loginController: require(__dirname+'/login.js'),
registerController: require(__dirname+'/register.js'),
changePasswordController: require(__dirname+'/changepassword.js'),
editAccountsController: require(__dirname+'/editaccounts.js'),
deleteAccountsController: require(__dirname+'/deleteaccounts.js'),
globalSettingsController: require(__dirname+'/globalsettings.js'),
createBoardController: require(__dirname+'/create.js'),
makePostController: require(__dirname+'/makepost.js'),
editPostController: require(__dirname+'/editpost.js'),
addStaffController: require(__dirname+'/addstaff.js'),
deleteStaffController: require(__dirname+'/deletestaff.js'),
editStaffController: require(__dirname+'/editstaff.js'),
editRoleController: require(__dirname+'/editrole.js'),
//these dont have a "real" controller
newCaptcha: require(__dirname+'/../../models/forms/newcaptcha.js'),

@ -1,6 +1,7 @@
'use strict';
const alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, config = require(__dirname+'/../../config.js')
, registerAccount = require(__dirname+'/../../models/forms/register.js')
@ -16,10 +17,8 @@ module.exports = {
controller: async (req, res, next) => {
const { enableUserAccountCreation } = config.get;
const errors = await checkSchema([
{ result: (enableUserAccountCreation === true), blocking: true, permLevel: 1, expected: true, error: 'Account creation is currently disabled' },
{ result: res.locals.permissions.get(Permissions.CREATE_ACCOUNT), blocking: true, expected: true, error: 'No permission' },
{ result: existsBody(req.body.username), expected: true, error: 'Missing username' },
{ result: lengthBody(req.body.username, 0, 50), expected: false, error: 'Username must be 50 characters or less' },
{ result: alphaNumericRegex.test(req.body.username), expected: true, error: 'Username must contain a-z 0-9 only'},
@ -28,7 +27,7 @@ module.exports = {
{ result: existsBody(req.body.passwordconfirm), expected: true, error: 'Missing password confirmation' },
{ result: lengthBody(req.body.passwordconfirm, 0, 100), expected: false, error: 'Password confirmation must be 100 characters or less' },
{ result: (req.body.password === req.body.passwordconfirm), expected: true, error: 'Password and password confirmation must match' },
], res.locals.permLevel);
], res.locals.permissions);
if (errors.length > 0) {
return dynamicResponse(req, res, 400, 'message', {

@ -1,6 +1,7 @@
'use strict';
const transferBoard = require(__dirname+'/../../models/forms/transferboard.js')
, { Accounts } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, alphaNumericRegex = require(__dirname+'/../../helpers/checks/alphanumregex.js')
, paramConverter = require(__dirname+'/../../helpers/paramconverter.js')
@ -20,6 +21,10 @@ module.exports = {
{ result: lengthBody(req.body.username, 1, 50), expected: false, error: 'New owner username must be 50 characters or less' },
{ result: (req.body.username === res.locals.board.owner), expected: false, error: 'New owner must be different from current owner' },
{ result: alphaNumericRegex.test(req.body.username), expected: true, error: 'New owner username must contain a-z 0-9 only' },
{ result: async () => {
res.locals.newOwner = await Accounts.findOne(req.body.username.toLowerCase());
return res.locals.newOwner != null;
}, expected: true, error: 'Cannot transfer to account that does not exist' },
]);
if (errors.length > 0) {

@ -8,6 +8,7 @@ const express = require('express')
, processIp = require(__dirname+'/../helpers/processip.js')
, geoAndTor = require(__dirname+'/../helpers/geoip.js')
, calcPerms = require(__dirname+'/../helpers/checks/calcpermsmiddleware.js')
, Permissions = require(__dirname+'/../helpers/permissions.js')
, hasPerms = require(__dirname+'/../helpers/checks/haspermsmiddleware.js')
, isLoggedIn = require(__dirname+'/../helpers/checks/isloggedin.js')
, paramConverter = require(__dirname+'/../helpers/paramconverter.js')
@ -16,16 +17,17 @@ const express = require('express')
, csrf = require(__dirname+'/../helpers/checks/csrfmiddleware.js')
, setMinimal = require(__dirname+'/../helpers/setminimal.js')
//page models
, { manageRecent, manageReports, manageAssets, manageSettings, manageBans, editCustomPage,
manageBoard, manageThread, manageLogs, manageCatalog, manageCustomPages } = require(__dirname+'/../models/pages/manage/')
, { globalManageSettings, globalManageReports, globalManageBans, globalManageBoards, editNews,
globalManageRecent, globalManageAccounts, globalManageNews, globalManageLogs } = require(__dirname+'/../models/pages/globalmanage/')
, { changePassword, blockBypass, home, register, login, create,
, { manageRecent, manageReports, manageAssets, manageSettings, manageBans, editCustomPage, manageMyPermissions,
manageBoard, manageThread, manageLogs, manageCatalog, manageCustomPages, manageStaff, editStaff } = require(__dirname+'/../models/pages/manage/')
, { globalManageSettings, globalManageReports, globalManageBans, globalManageBoards, editNews, editAccount, editRole,
globalManageRecent, globalManageAccounts, globalManageNews, globalManageLogs, globalManageRoles } = require(__dirname+'/../models/pages/globalmanage/')
, { changePassword, blockBypass, home, register, login, create, myPermissions,
board, catalog, banners, randombanner, news, captchaPage, overboard, overboardCatalog,
captcha, thread, modlog, modloglist, account, boardlist, customPage, csrfPage } = require(__dirname+'/../models/pages/')
, threadParamConverter = paramConverter({ processThreadIdParam: true })
, logParamConverter = paramConverter({ processDateParam: true })
, newsParamConverter = paramConverter({ objectIdParams: ['newsid'] })
, roleParamConverter = paramConverter({ objectIdParams: ['roleid'] })
, custompageParamConverter = paramConverter({ objectIdParams: ['custompageid'] });
//homepage
@ -52,31 +54,61 @@ router.get('/:board/banners.html', Boards.exists, banners); //banners
router.get('/randombanner', randombanner); //random banner
//board manage pages
router.get('/:board/manage/reports.(html|json)', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageReports);
router.get('/:board/manage/recent.(html|json)', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageRecent);
router.get('/:board/manage/bans.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageBans);
router.get('/:board/manage/logs.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageLogs);
router.get('/:board/manage/settings.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(2), csrf, manageSettings);
router.get('/:board/manage/assets.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(2), csrf, manageAssets);
router.get('/:board/manage/custompages.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(2), csrf, manageCustomPages);
router.get('/:board/manage/catalog.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageCatalog);
router.get('/:board/manage/:page(1[0-9]{1,}|[2-9][0-9]{0,}|index).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageBoard);
router.get('/:board/manage/thread/:id([1-9][0-9]{0,}).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, threadParamConverter, calcPerms, hasPerms(3), csrf, Posts.exists, manageThread);
router.get('/:board/manage/catalog.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), csrf, manageCatalog);
router.get('/:board/manage/:page(1[0-9]{1,}|[2-9][0-9]{0,}|index).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), csrf, manageBoard);
router.get('/:board/manage/thread/:id([1-9][0-9]{0,}).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, threadParamConverter, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), csrf, Posts.exists, manageThread);
router.get('/:board/manage/reports.(html|json)', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), csrf, manageReports);
router.get('/:board/manage/recent.(html|json)', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), csrf, manageRecent);
router.get('/:board/manage/mypermissions.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), csrf, manageMyPermissions);
router.get('/:board/manage/logs.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_LOGS), csrf, manageLogs);
router.get('/:board/manage/bans.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_BANS), csrf, manageBans);
router.get('/:board/manage/settings.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_SETTINGS), csrf, manageSettings);
router.get('/:board/manage/assets.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), csrf, manageAssets);
router.get('/:board/manage/custompages.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), csrf, manageCustomPages);
router.get('/:board/manage/editcustompage/:custompageid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), csrf, custompageParamConverter, editCustomPage);
router.get('/:board/manage/staff.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_STAFF), csrf, manageStaff);
router.get('/:board/manage/editstaff/:staffusername([a-zA-Z0-9]{1,50}).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms,
hasPerms.one(Permissions.MANAGE_BOARD_STAFF), csrf, editStaff);
//global manage pages
router.get('/globalmanage/reports.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageReports);
router.get('/globalmanage/bans.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageBans);
router.get('/globalmanage/recent.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageRecent);
router.get('/globalmanage/boards.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), globalManageBoards);
router.get('/globalmanage/globallogs.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(1), csrf, globalManageLogs);
router.get('/globalmanage/news.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, globalManageNews);
router.get('/globalmanage/accounts.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, globalManageAccounts);
router.get('/globalmanage/settings.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, globalManageSettings);
//edit pages
router.get('/globalmanage/editnews/:newsid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, newsParamConverter, editNews);
router.get('/:board/manage/editcustompage/:custompageid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(2), csrf, custompageParamConverter, editCustomPage);
//TODO: edit post get endpoint
router.get('/globalmanage/reports.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_GENERAL), csrf, globalManageReports);
router.get('/globalmanage/recent.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_GENERAL), csrf, globalManageRecent);
router.get('/globalmanage/globallogs.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_LOGS), csrf, globalManageLogs);
router.get('/globalmanage/bans.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_BANS), csrf, globalManageBans);
router.get('/globalmanage/boards.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_BOARDS), globalManageBoards);
router.get('/globalmanage/news.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_NEWS), csrf, globalManageNews);
router.get('/globalmanage/accounts.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_ACCOUNTS), csrf, globalManageAccounts);
router.get('/globalmanage/roles.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_ROLES), csrf, globalManageRoles);
router.get('/globalmanage/settings.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_SETTINGS), csrf, globalManageSettings);
router.get('/globalmanage/editnews/:newsid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_NEWS), csrf, newsParamConverter, editNews);
router.get('/globalmanage/editaccount/:accountusername([a-zA-Z0-9]{1,50}).html', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_ACCOUNTS), csrf, editAccount);
router.get('/globalmanage/editrole/:roleid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, calcPerms,
hasPerms.one(Permissions.MANAGE_GLOBAL_ROLES), csrf, roleParamConverter, editRole);
//TODO: edit post edit page form, like editnews/editaccount/editrole endpoint
//captcha
router.get('/captcha', geoAndTor, processIp, captcha); //get captcha image and cookie
@ -85,7 +117,8 @@ router.get('/bypass.html', blockBypass); //block bypass page
router.get('/bypass_minimal.html', setMinimal, blockBypass); //block bypass page
//accounts
router.get('/account.html', useSession, sessionRefresh, isLoggedIn, csrf, account); //page showing boards you are mod/owner of, links to password rese, logout, etc
router.get('/account.html', useSession, sessionRefresh, isLoggedIn, calcPerms, csrf, account); //page showing boards you are mod/owner of, links to password rese, logout, etc
router.get('/mypermissions.html', useSession, sessionRefresh, isLoggedIn, calcPerms, csrf, myPermissions);
router.get('/login.html', login);
router.get('/register.html', register);
router.get('/changepassword.html', changePassword);

@ -25,21 +25,26 @@ module.exports = {
}
},
findOne: (username) => {
return db.findOne({ '_id': username });
findOne: async (username) => {
const account = await db.findOne({ '_id': username });
//hmmm
if (account != null) {
account.permissions = account.permissions.toString('base64');
}
return account;
},
insertOne: async (original, username, password, authLevel) => {
insertOne: async (original, username, password, permissions) => {
// hash the password
const passwordHash = await bcrypt.hash(password, 12);
//add to db
const res = await db.insertOne({
'_id': username,
original,
authLevel,
passwordHash,
'permissions': Mongo.Binary(permissions.array),
'ownedBoards': [],
'modBoards': []
'staffBoards': []
});
cache.del(`users:${username}`);
return res;
@ -58,6 +63,30 @@ module.exports = {
return res;
},
setAccountPermissions: async (username, permissions) => {
const res = await db.updateOne({
'_id': username
}, {
'$set': {
'permissions': Mongo.Binary(permissions.array),
}
});
cache.del(`users:${username}`);
return res;
},
setNewRolePermissions: async (oldPermissions, permissions) => {
const res = await db.updateMany({
'permissions': Mongo.Binary(oldPermissions.array),
}, {
'$set': {
'permissions': Mongo.Binary(permissions.array),
}
});
cache.deletePattern(`users:*`);
return res;
},
updateLastActiveDate: (username) => {
return db.updateOne({
'_id': username
@ -65,7 +94,7 @@ module.exports = {
'$set': {
lastActiveDate: new Date()
}
})
});
},
find: (filter, skip=0, limit=0) => {
@ -73,8 +102,6 @@ module.exports = {
'projection': {
'passwordHash': 0
}
}).sort({
'authLevel': 1
}).skip(skip).limit(limit).toArray();
},
@ -120,35 +147,35 @@ module.exports = {
return res;
},
addModBoard: async (usernames, board) => {
addStaffBoard: async (usernames, board) => {
const res = await db.updateMany({
'_id': {
'$in': usernames
}
}, {
'$addToSet': {
'modBoards': board
'staffBoards': board
}
});
cache.del(usernames.map(n => `users:${n}`));
return res;
},
removeModBoard: async (usernames, board) => {
removeStaffBoard: async (usernames, board) => {
const res = await db.updateMany({
'_id': {
'$in': usernames
}
}, {
'$pull': {
'modBoards': board
'staffBoards': board
}
});
cache.del(usernames.map(n => `users:${n}`));
return res;
},
getOwnedOrModBoards: (usernames) => {
getOwnedOrStaffBoards: (usernames) => {
return db.find({
'_id': {
'$in': usernames
@ -160,7 +187,7 @@ module.exports = {
},
},
{
'modBoards.0': {
'staffBoards.0': {
'$exists': true
}
}
@ -168,26 +195,11 @@ module.exports = {
}, {
'projection': {
'ownedBoards': 1,
'modBoards': 1,
'staffBoards': 1,
}
}).toArray();
},
setLevel: async (usernames, level) => {
//increase users auth level
const res = await db.updateMany({
'_id': {
'$in': usernames
}
}, {
'$set': {
'authLevel': level
}
});
cache.del(usernames.map(n => `users:${n}`));
return res;
},
deleteAll: () => {
return db.deleteMany({});
},

@ -17,6 +17,10 @@ module.exports = {
} else {
board = await db.findOne({ '_id': name });
if (board) {
//should really handle this in every db find
for (let staff in board.staff) {
board.staff[staff].permissions = board.staff[staff].permissions.toString('base64');
}
cache.set(`board:${name}`, board, 3600);
if (board.banners.length > 0) {
cache.sadd(`banners:${name}`, board.banners);
@ -28,6 +32,18 @@ module.exports = {
return board;
},
getStaffPerms: async (boards, username) => {
return db.find({
'_id': {
'$in': boards,
}
}, {
'projection': {
[`staff.${username}.permissions`]: 1,
}
}).toArray();
},
randomBanner: async (name) => {
let banner = await cache.srand(`banners:${name}`);
if (!banner) {
@ -39,17 +55,6 @@ module.exports = {
return banner;
},
setOwner: (board, username) => {
cache.del(`board:${board}`);
return db.updateOne({
'_id': board
}, {
'$set': {
'owner': username
}
});
},
insertOne: (data) => {
cache.del(`board:${data._id}`); //removing cached no_exist
if (!data.settings.unlistedLocal) {
@ -86,19 +91,66 @@ module.exports = {
return db.deleteMany({});
},
removeModerator: (board, username) => {
addStaff: async (board, username, permissions, setOwner=false) => {
const update = {
'$set': {
[`staff.${username}`]: {
'permissions': Mongo.Binary(permissions.array),
'addedDate': new Date(),
},
},
};
if (setOwner === true) {
update['$set']['owner'] = username;
}
const res = db.updateOne({
'_id': board,
}, update);
cache.del(`board:${board}`);
return res;
},
removeStaff: (board, usernames) => {
cache.del(`board:${board}`);
const unsetObject = usernames.reduce((acc, username) => {
acc[`staff.${username}`] = "";
return acc;
}, {});
return db.updateOne(
{
'_id': board,
}, {
'$pull': {
'settings.moderators': username
}
'$unset': unsetObject,
}
);
},
setStaffPermissions: (board, username, permissions, setOwner = false) => {
cache.del(`board:${board}`);
const update = {
'$set': {
[`staff.${username}.permissions`]: Mongo.Binary(permissions.array),
}
};
if (setOwner === true) {
update['$set']['owner'] = username;
}
return db.updateOne({
'_id': board,
}, update);
},
setOwner: (board, username = null) => {
cache.del(`board:${board}`);
return db.updateOne({
'_id': board,
}, {
'$set': {
'owner': null,
},
});
},
addToArray: (board, key, list) => {
return db.updateOne(
{
@ -218,10 +270,9 @@ module.exports = {
}
if (filter.filter_abandoned) {
addedFilter['owner'] = null;
addedFilter['settings.moderators'] = [];
}
addedFilter['webring'] = false;
projection['settings.moderators'] = 1;
projection['staff'] = 1;
projection['owner'] = 1;
}
if (filter.search) {
@ -277,7 +328,6 @@ module.exports = {
}
if (filter.filter_abandoned) {
addedFilter['owner'] = null;
addedFilter['settings.moderators'] = [];
}
addedFilter['webring'] = false;
}

@ -1,7 +1,7 @@
'use strict';
const secrets = require(__dirname+'/../configs/secrets.js')
, { MongoClient, ObjectId, Int32 } = require('mongodb')
, { MongoClient, ObjectId, Int32, Binary } = require('mongodb')
, { migrateVersion } = require(__dirname+'/../package.json')
module.exports = {
@ -36,4 +36,6 @@ module.exports = {
NumberInt: Int32,
Binary,
}

@ -6,6 +6,7 @@ module.exports = {
Boards: require(__dirname+'/boards.js'),
Stats: require(__dirname+'/stats.js'),
Accounts: require(__dirname+'/accounts.js'),
Roles: require(__dirname+'/roles.js'),
Bans: require(__dirname+'/bans.js'),
Captchas: require(__dirname+'/captchas.js'),
Files: require(__dirname+'/files.js'),

@ -4,6 +4,7 @@ const Mongo = require(__dirname+'/db.js')
, { isIP } = require('net')
, Boards = require(__dirname+'/boards.js')
, Stats = require(__dirname+'/stats.js')
, Permissions = require(__dirname+'/../helpers/permissions.js')
, db = Mongo.db.collection('posts')
, config = require(__dirname+'/../config.js');
@ -22,7 +23,7 @@ module.exports = {
return Math.ceil(threadsBefore/10) || 1; //1 because 0 threads before is page 1
},
getBoardRecent: async (offset=0, limit=20, ip, board, permLevel) => {
getBoardRecent: async (offset=0, limit=20, ip, board, permissions) => {
const query = {};
if (board) {
query['board'] = board;
@ -43,7 +44,7 @@ module.exports = {
query['ip.cloak'] = ip;
}
}
if (permLevel > config.get.ipHashPermLevel) {
if (!permissions.get(Permissions.VIEW_RAW_IP)) {
projection['ip.raw'] = 0;
//MongoError, why cant i just projection['reports.ip.raw'] = 0;
if (board) {
@ -517,13 +518,13 @@ module.exports = {
})
},
getReports: async (board, permLevel) => {
getReports: async (board, permissions) => {
const projection = {
'salt': 0,
'password': 0,
'globalreports': 0,
};
if (permLevel > config.get.ipHashPermLevel) {
if (!permissions.get(Permissions.VIEW_RAW_IP)) {
projection['ip.raw'] = 0;
projection['reports'] = { ip: { raw: 0 } };
}
@ -536,13 +537,13 @@ module.exports = {
return posts;
},
getGlobalReports: async (offset=0, limit, ip, permLevel) => {
getGlobalReports: async (offset=0, limit, ip, permissions) => {
const projection = {
'salt': 0,
'password': 0,
'reports': 0,
};
if (permLevel > config.get.ipHashPermLevel) {
if (!permissions.get(Permissions.VIEW_RAW_IP)) {
projection['ip.raw'] = 0;
projection['globalreports'] = { ip: { raw: 0 } };
}

@ -0,0 +1,46 @@
'use strict';
const Mongo = require(__dirname+'/db.js')
, db = Mongo.db.collection('roles')
, cache = require(__dirname+'/../redis.js');
module.exports = {
db,
findOne: async (id) => {
//is there any point even caching
let role = await cache.get(`role:${id}`);
if (role) {
return role;
} else {
role = await db.findOne({ '_id': id });
if (role) {
role.permissions = role.permissions.toString('base64');
cache.set(`role:${id}`, role);
}
}
return role;
},
find: () => {
return db.find({}).toArray();
},
updateOne: async (id, permissions) => {
const res = await db.updateOne({
'_id': id
}, {
'$set': {
'permissions': Mongo.Binary(permissions.array),
},
});
cache.del(`role:${id}`);
return res;
},
deleteAll: () => {
return db.deleteMany({});
},
};

@ -100,6 +100,7 @@ class postFormHandler {
constructor(form) {
this.form = form;
this.resetOnSubmit = this.form.dataset.resetOnSubmit == "true";
this.enctype = this.form.getAttribute('enctype');
this.messageBox = form.querySelector('#message');
this.captchaField = form.querySelector('.captchafield') || form.querySelector('.g-recaptcha') || form.querySelector('.h-captcha');
@ -257,8 +258,9 @@ class postFormHandler {
}
//dont reset on edit, keep the new values in there. todo: add exceptions/better handling for this situation
const formAction = this.form.getAttribute('action');
if (formAction !== '/forms/editpost'
&& !formAction.endsWith('/settings')) {
if (this.resetOnSubmit) {
//formAction !== '/forms/editpost'
//!formAction.endsWith('/settings')) {
this.reset();
}
} else {

@ -11,7 +11,7 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
const liveElem = document.getElementById('livetext');
const livetext = (isThread || isRecent) && liveElem ? liveElem.childNodes[1] : null;
let room = liveElem && liveElem.dataset.room;
const permLevel = liveElem ? liveElem.dataset.permLevel : 4;
const viewRawIp = liveElem && liveElem.dataset.viewRawIp === 'true';
const updateButton = document.getElementById('updatepostsbutton');
const updateLive = (message, color, showRelativeTime) => {
livecolor.style.backgroundColor = color;
@ -66,8 +66,7 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
lastPostIds[postData.board] = postData.postId;
//create a new post
const postHtml = post({
ipHashPermLevel,
permLevel,
viewRawIp,
post: postData,
modview: isModView,
manage: (isRecent && !isGlobalRecent),

@ -1,6 +1,9 @@
'use strict';
const config = require(__dirname+'/config.js')
, { Binary } = require('mongodb')
, Permission = require(__dirname+'/helpers/permission.js')
, Permissions = require(__dirname+'/helpers/permissions.js')
, { hcaptcha, google } = require(__dirname+'/configs/secrets.js')
, gulp = require('gulp')
, fs = require('fs-extra')
@ -151,7 +154,7 @@ async function wipe() {
const db = Mongo.db;
const collectionNames = ['accounts', 'bans', 'custompages', 'boards', 'captcha', 'files',
'modlog','news', 'posts', 'poststats', 'ratelimit', 'bypass'];
'modlog','news', 'posts', 'poststats', 'ratelimit', 'bypass', 'roles'];
for (const name of collectionNames) {
//drop collection so gulp reset can be run again. ignores error of dropping non existing collection first time
await db.dropCollection(name).catch(e => {});
@ -159,7 +162,7 @@ async function wipe() {
}
const { Boards, Posts, Captchas, Ratelimits, News, CustomPages,
Accounts, Files, Stats, Modlogs, Bans, Bypass } = require(__dirname+'/db/');
Accounts, Files, Stats, Modlogs, Bans, Bypass, Roles } = require(__dirname+'/db/');
//wipe db shit
await Promise.all([
@ -167,6 +170,7 @@ async function wipe() {
Captchas.deleteAll(),
Ratelimits.deleteAll(),
Accounts.deleteAll(),
Roles.deleteAll(),
Posts.deleteAll(),
Boards.deleteAll(),
Bans.deleteAll(),
@ -183,6 +187,7 @@ async function wipe() {
await Boards.db.createIndex({tags: 1})
await Boards.db.createIndex({uri: 1})
await Boards.db.createIndex({lastPostTimestamp:1})
await Roles.db.dropIndexes()
await Bans.db.dropIndexes()
await Captchas.db.dropIndexes()
await Ratelimits.db.dropIndexes()
@ -190,6 +195,7 @@ async function wipe() {
await Modlogs.db.dropIndexes()
await CustomPages.db.dropIndexes()
await CustomPages.db.createIndex({ 'board': 1, 'page': 1 }, { unique: true })
await Roles.db.createIndex({ 'permissions': 1 }, { unique: true })
await Modlogs.db.createIndex({ 'board': 1 })
await Files.db.createIndex({ 'count': 1 })
await Bans.db.createIndex({ 'ip.cloak': 1 , 'board': 1 })
@ -202,8 +208,47 @@ async function wipe() {
await Posts.db.createIndex({ 'board': 1, 'reports.0': 1 }, { 'partialFilterExpression': { 'reports.0': { '$exists': true } } })
await Posts.db.createIndex({ 'globalreports.0': 1 }, { 'partialFilterExpression': { 'globalreports.0': { '$exists': true } } })
const ANON = new Permission()
ANON.setAll([
Permissions.USE_MARKDOWN_PINKTEXT, Permissions.USE_MARKDOWN_GREENTEXT, Permissions.USE_MARKDOWN_BOLD,
Permissions.USE_MARKDOWN_UNDERLINE, Permissions.USE_MARKDOWN_STRIKETHROUGH, Permissions.USE_MARKDOWN_TITLE,
Permissions.USE_MARKDOWN_ITALIC, Permissions.USE_MARKDOWN_SPOILER, Permissions.USE_MARKDOWN_MONO,
Permissions.USE_MARKDOWN_CODE, Permissions.USE_MARKDOWN_DETECTED, Permissions.USE_MARKDOWN_LINK,
Permissions.USE_MARKDOWN_DICE, Permissions.USE_MARKDOWN_FORTUNE, Permissions.CREATE_BOARD,
Permissions.CREATE_ACCOUNT
]);
const BOARD_STAFF = new Permission(ANON.base64)
BOARD_STAFF.setAll([
Permissions.MANAGE_BOARD_GENERAL, Permissions.MANAGE_BOARD_BANS, Permissions.MANAGE_BOARD_LOGS,
]);
const BOARD_OWNER = new Permission(BOARD_STAFF.base64)
BOARD_OWNER.setAll([
Permissions.MANAGE_BOARD_OWNER, Permissions.MANAGE_BOARD_STAFF, Permissions.MANAGE_BOARD_CUSTOMISATION,
Permissions.MANAGE_BOARD_SETTINGS,
]);
const GLOBAL_STAFF = new Permission(BOARD_OWNER.base64);
GLOBAL_STAFF.setAll([
Permissions.MANAGE_GLOBAL_GENERAL, Permissions.MANAGE_GLOBAL_BANS, Permissions.MANAGE_GLOBAL_LOGS, Permissions.MANAGE_GLOBAL_NEWS,
Permissions.MANAGE_GLOBAL_BOARDS, Permissions.MANAGE_GLOBAL_SETTINGS, Permissions.MANAGE_BOARD_OWNER, Permissions.BYPASS_FILTERS,
Permissions.BYPASS_BANS, Permissions.BYPASS_SPAMCHECK, Permissions.BYPASS_RATELIMITS,
]);
const ADMIN = new Permission(GLOBAL_STAFF.base64);
ADMIN.setAll([
Permissions.MANAGE_GLOBAL_ACCOUNTS, Permissions.MANAGE_GLOBAL_ROLES, Permissions.VIEW_RAW_IP,
]);
const ROOT = new Permission();
ROOT.setAll(Permission.allPermissions);
await Roles.db.insertMany([
{ name: 'ANON', permissions: Binary(ANON.array) },
{ name: 'BOARD_STAFF', permissions: Binary(BOARD_STAFF.array) },
{ name: 'BOARD_OWNER', permissions: Binary(BOARD_OWNER.array) },
{ name: 'GLOBAL_STAFF', permissions: Binary(GLOBAL_STAFF.array) },
{ name: 'ADMIN', permissions: Binary(ADMIN.array) },
{ name: 'ROOT', permissions: Binary(ROOT.array) },
]);
const randomPassword = randomBytes(20).toString('base64')
await Accounts.insertOne('admin', 'admin', randomPassword, 0);
await Accounts.insertOne('admin', 'admin', randomPassword, ROOT);
console.log('=====LOGIN DETAILS=====\nusername: admin\npassword:', randomPassword, '\n=======================');
await db.collection('version').replaceOne({
@ -354,7 +399,7 @@ async function custompages() {
])
.pipe(gulppug({
locals: {
authLevelNames: ['Admin', 'Global Staff', 'Global Board Owner', 'Global Board Mod', 'Regular User'],
Permissions,
early404Fraction: config.get.early404Fraction,
early404Replies: config.get.early404Replies,
meta: config.get.meta,
@ -386,7 +431,6 @@ const codeThemes = ['${codeThemes.join("', '")}'];
const captchaType = '${config.get.captchaOptions.type}';
const captchaGridSize = ${config.get.captchaOptions.grid.size};
const SERVER_TIMEZONE = '${Intl.DateTimeFormat().resolvedOptions().timeZone}';
const ipHashPermLevel = ${config.get.ipHashPermLevel};
const settings = ${JSON.stringify(config.get.frontendScriptDefault)};
const extraLocals = ${JSON.stringify({ meta: config.get.meta, reverseImageLinksURL: config.get.reverseImageLinksURL })};
`;

@ -1,35 +1,36 @@
'use strict';
const actions = [
{name:'unlink_file', global:true, auth:4, passwords:true, build:true},
{name:'delete_file', global:true, auth:1, passwords:false, build:true},
{name:'spoiler', global:true, auth:4, passwords:true, build:true},
{name:'edit', global:true, auth:3, passwords:false, build:true},
{name:'delete', global:true, auth:4, passwords:true, build:true},
{name:'lock', global:false, auth:3, passwords:false, build:true},
{name:'sticky', global:false, auth:3, passwords:false, build:true},
{name:'cyclic', global:false, auth:3, passwords:false, build:true},
{name:'bumplock', global:false, auth:3, passwords:false, build:true},
{name:'report', global:false, auth:4, passwords:false, build:false},
{name:'global_report', global:true, auth:4, passwords:false, build:false},
{name:'move', global:false, auth:3, passwords:false, build:true},
{name:'delete_ip_board', global:true, auth:3, passwords:false, build:true},
{name:'delete_ip_thread', global:true, auth:3, passwords:false, build:true},
{name:'delete_ip_global', global:true, auth:1, passwords:false, build:true},
{name:'dismiss', global:false, auth:3, passwords:false, build:false},
{name:'global_dismiss', global:true, auth:1, passwords:false, build:false},
{name:'report_ban', global:false, auth:3, passwords:false, build:false},
{name:'global_report_ban', global:true, auth:1, passwords:false, build:false},
{name:'ban', global:false, auth:3, passwords:false, build:false},//will build if there is a ban message
{name:'global_ban', global:true, auth:1, passwords:false, build:false},//will build if there is a ban message
];
const Permissions = require(__dirname+'/../permissions.js')
, actions = [
{name:'unlink_file', global:true, passwords:true, build:true},
{name:'delete_file', global:true, auth:Permissions.MANAGE_GLOBAL_GENERAL, passwords:false, build:true},
{name:'spoiler', global:true, passwords:true, build:true},
{name:'edit', global:true, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:true},
{name:'delete', global:true, passwords:true, build:true},
{name:'lock', global:false, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:true},
{name:'sticky', global:false, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:true},
{name:'cyclic', global:false, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:true},
{name:'bumplock', global:false, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:true},
{name:'report', global:false, passwords:false, build:false},
{name:'global_report', global:true, passwords:false, build:false},
{name:'move', global:false, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:true},
{name:'delete_ip_board', global:true, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:true},
{name:'delete_ip_thread', global:true, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:true},
{name:'delete_ip_global', global:true, auth:Permissions.MANAGE_GLOBAL_GENERAL, passwords:false, build:true},
{name:'dismiss', global:false, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:false},
{name:'global_dismiss', global:true, auth:Permissions.MANAGE_GLOBAL_GENERAL, passwords:false, build:false},
{name:'report_ban', global:false, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:false},
{name:'global_report_ban', global:true, auth:Permissions.MANAGE_GLOBAL_GENERAL, passwords:false, build:false},
{name:'ban', global:false, auth:Permissions.MANAGE_BOARD_GENERAL, passwords:false, build:false},
{name:'global_ban', global:true, auth:Permissions.MANAGE_GLOBAL_GENERAL, passwords:false, build:false},
];
module.exports = (req, res) => {
let numGlobal = 0
, authRequired = 4
, numPasswords = 0
, numBuild = 0
, hasPermission = true
, validActions = [];
for (let i = 0; i < actions.length; i++) {
@ -40,8 +41,9 @@ module.exports = (req, res) => {
if (action.global) {
numGlobal++;
}
if (action.auth && action.auth < authRequired) {
authRequired = action.auth;
if (action.auth && !res.locals.permissions.get(action.auth)) {
hasPermission = false;
//maybe break;?
}
if (action.passwords) {
numPasswords++;
@ -52,6 +54,6 @@ module.exports = (req, res) => {
}
}
return { numGlobal, authRequired, validActions, numPasswords, numBuild };
return { numGlobal, hasPermission, validActions, numPasswords, numBuild };
}

@ -1,26 +1,34 @@
'use strict';
const { Bans } = require(__dirname+'/../../db/')
, hasPerms = require(__dirname+'/hasperms.js')
, dynamicResponse = require(__dirname+'/../dynamic.js');
, dynamicResponse = require(__dirname+'/../dynamic.js')
, Permissions = require(__dirname+'/../permissions.js');
module.exports = async (req, res, next) => {
if (res.locals.permLevel > 1) {//global staff or admin bypass
const bans = await Bans.find(res.locals.ip, res.locals.board ? res.locals.board._id : null);
if (bans && bans.length > 0) {
const globalBans = bans.filter(ban => { return ban.board === null });
if (globalBans.length > 0 || (res.locals.permLevel >= 4 && globalBans.length !== bans.length)) {
//board staff bypass bans on their own board, but not global bans
const unseenBans = bans.filter(b => !b.seen).map(b => b._id);
await Bans.markSeen(unseenBans); //mark bans as seen
bans.forEach(ban => ban.seen = true); //mark seen as true in memory for user viewed ban page
return res.status(403).render('ban', {
bans: bans,
});
}
}
//bypass all bans, special permission
if (res.locals.permissions.get(Permissions.BYPASS_BANS)) {
return next();
}
next();
//fetch bans
const banBoard = res.locals.board ? res.locals.board._id : null; //if no board, global bans or "null" board.
let bans = await Bans.find(res.locals.ip, banBoard);
//board staff still bypass bans on their board by default
if (res.locals.permissions.get(Permissions.MANAGE_BOARD_GENERAL)) {
//filter bans to leave only global bans remaining
bans = bans.filter(ban => ban.board !== res.locals.board);
}
if (bans && bans.length > 0) {
const unseenBans = bans.filter(b => !b.seen).map(b => b._id);
await Bans.markSeen(unseenBans); //mark bans as seen
bans.forEach(ban => ban.seen = true); //mark seen as true in memory for user viewed ban page
//todo: make a dynamicresponse, handle in frontend modal.
return res.status(403).render('ban', {
bans: bans,
});
}
next(); //no bans found
}

@ -0,0 +1,48 @@
'use strict';
const Permissions = require(__dirname+'/../permissions.js') //needs rename
, Permission = require(__dirname+'/../permission.js')
, roleManager = require(__dirname+'/../rolemanager.js');
module.exports = (req, res) => {
let calculatedPermissions;
if (req.session && res.locals && res.locals.user) {
//has a session and user, not anon, so their permissions from the db/user instead.
const { user } = res.locals;
calculatedPermissions = new Permission(user.permissions);
//if they are on a board endpoint, also apply the board perms.
if (res.locals.board != null) {
if (res.locals.board.owner === user.username) {
//they are board owner, give them board owner perms, in this board context
calculatedPermissions.set(Permissions.MANAGE_BOARD_OWNER);
} else if (res.locals.board.staff[user.username] != null) {
//they are board staff, give them their board level staff perms, OR'd with account/global perms
const boardPermissions = new Permission(res.locals.board.staff[user.username].permissions);
for (let bit of Permissions._MANAGE_BOARD_BITS) {
const inheritOrGlobal = calculatedPermissions.get(bit) || boardPermissions.get(bit);
calculatedPermissions.set(bit, inheritOrGlobal);
}
}
//and note, in future since we might need multiple-boards permission checks, we will have to change this.
//could even build it with a map for each board, based on their stored permissions in that board, maybe like:
//res.locals.boardPermissions[board] = new Permission(res.locals.board.settings.staff[user.username].permissions);
//and then the MANAGE_BOARD_OWNER inheritance could be removed, since it should be set immutable
//inside the board perms instead. and the existing code would make it for "global" BOs to have the permissions.
//so we would remove the "...permissions.set(Permissions.MANAGE_BOARD_OWNER)..." above
}
//give ROOT all permission, BOARD_OWNER all MANAGE_BOARD*, etc
calculatedPermissions.applyInheritance();
} else {
//not logged in, gets default anon permission
calculatedPermissions = new Permission(roleManager.roles.ANON.base64);
}
return calculatedPermissions;
}

@ -1,8 +1,8 @@
'use strict';
const hasPerms = require(__dirname+'/hasperms.js');
const calcPerms = require(__dirname+'/calcperms.js');
module.exports = (req, res, next) => {
res.locals.permLevel = hasPerms(req, res);
res.locals.permissions = calcPerms(req, res);
next();
}

@ -1,20 +0,0 @@
'use strict';
module.exports = (req, res) => {
if (req.session) {
const { user } = res.locals;
if (user != null) {
if (user.authLevel < 4) { //assigned levels
return user.authLevel;
}
if (res.locals.board != null) {
if (res.locals.board.owner === user.username) {
return 2; //board owner 2
} else if (res.locals.board.settings.moderators.includes(user.username) === true) {
return 3; //board staff 3
}
}
}
}
return 4; //not logged in, not staff or moderator
}

@ -1,18 +1,53 @@
'use strict';
const cache = {};
//todo: refactor
const Permissions = require(__dirname+'/../permissions.js')
, dynamicResponse = require(__dirname+'/../dynamic.js')
, cache = {
one: {}, all: {}, any: {},
};
module.exports = (requiredLevel) => {
module.exports = {
return cache[requiredLevel] || (cache[requiredLevel] = function(req, res, next) {
if (res.locals.permLevel > requiredLevel) {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'No Permission',
'redirect': req.headers.referer || '/'
});
}
next();
});
one: (requiredPermission) => {
return cache.one[requiredPermission] || (cache.one[requiredPermission] = function(req, res, next) {
if (!res.locals.permissions.get(requiredPermission)) {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'No Permission',
'redirect': req.headers.referer || '/',
});
}
next();
});
},
}
all: (...requiredPermissions) => {
//these caches working as intended with arrays?
return cache.all[requiredPermissions] || (cache.all[requiredPermissions] = function(req, res, next) {
if (!res.locals.permissions.hasAll(...requiredPermissions)) {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'No Permission',
'redirect': req.headers.referer || '/',
});
}
next();
});
},
any: (...requiredPermissions) => {
//these caches working as intended with arrays?
return cache.any[requiredPermissions] || (cache.any[requiredPermissions] = function(req, res, next) {
if (!res.locals.permissions.hasAny(...requiredPermissions)) {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'No Permission',
'redirect': req.headers.referer || '/',
});
}
next();
});
},
};

@ -3,16 +3,17 @@
const Mongo = require(__dirname+'/../../db/db.js')
, { Posts } = require(__dirname+'/../../db/')
, timeUtils = require(__dirname+'/../timeutils.js')
, Permissions = require(__dirname+'/../permissions.js')
, config = require(__dirname+'/../../config.js');
module.exports = async (req, res) => {
const { sameContentSameIp, sameContentAnyIp, anyContentSameIp } = config.get.floodTimers;
if (res.locals.permLevel <= 1) { //global staff bypass spam check
if (res.locals.permissions.get(Permissions.BYPASS_SPAMCHECK)) {
return false;
}
const { sameContentSameIp, sameContentAnyIp, anyContentSameIp } = config.get.floodTimers;
if (sameContentSameIp === 0
&& sameContentAnyIp === 0
&& anyContentSameIp === 0) {

@ -2,16 +2,16 @@
const escapeRegExp = require(__dirname+'/escaperegexp.js')
, { isIP } = require('net')
, config = require(__dirname+'/../config.js')
, Permissions = require(__dirname+'/permissions.js');
module.exports = (query, permLevel) => {
const { ipHashPermLevel } = config.get;
module.exports = (query, permissions) => {
if (query.ip && typeof query.ip === 'string') {
const decoded = decodeURIComponent(query.ip);
if (permLevel <= ipHashPermLevel || !isIP(decoded)) {
//if they have perm to view raw IP, or its NOT a raw ip, return
return decoded;
//if is IP but no permission, return null
if (isIP(decoded) && !permissions.get(Permissions.VIEW_RAW_IP)) {
return null;
}
return decoded; //otherwise return ip/cloak query
}
return null; //else, no ip filter
}

@ -5,7 +5,7 @@ const imageHash = require('imghash').hash
module.exports = async (req, res, next) => {
const { hashImages } = config.get;
if (hashImages && res.locals.numFiles > 0 && res.locals.permLevel > 1) {
if (hashImages && res.locals.numFiles > 0) {
const hashPromises = [];
for (let i = 0; i < res.locals.numFiles; i++) {
const mainType = req.files.file[i].mimetype.split('/')[0];

@ -0,0 +1,43 @@
'use strict';
const Permissions = require(__dirname+'/permissions.js')
, PermissionText = require(__dirname+'/permissiontext.js') //todo:combine^
, BigBitfield = require('big-bitfield');
class Permission extends BigBitfield {
constructor(data) {
super(data);
}
static permissionEntries = Object.entries(Permissions)
.filter(e => typeof e[1] === 'number');
static allPermissions = this.permissionEntries
.map(e => e[1]);
toJSON() {
return this.constructor.permissionEntries
.reduce((acc, entry) => {
acc[entry[0]] = {
bit: entry[1],
state: this.get(entry[1]),
label: PermissionText[entry[0]].label,
desc: PermissionText[entry[0]].desc,
title: PermissionText[entry[0]].title,
};
return acc;
}, {});
}
applyInheritance() {
if (this.get(Permissions.ROOT)){ //root gets all perms
this.setAll(this.constructor.allPermissions);
} else if (this.get(Permissions.MANAGE_BOARD_OWNER)) { //BOs and "global staff"
this.setAll(Permissions._MANAGE_BOARD_BITS);
}
}
};
module.exports = Permission;

@ -0,0 +1,44 @@
'use strict';
const Permissions = {
ROOT: 0,
VIEW_RAW_IP: 1,
CREATE_BOARD: 2,
CREATE_ACCOUNT: 3,
BYPASS_BANS: 4,
BYPASS_SPAMCHECK: 5,
BYPASS_RATELIMITS: 6,
BYPASS_FILTERS: 7,
MANAGE_GLOBAL_GENERAL: 10,
MANAGE_GLOBAL_BANS: 11,
MANAGE_GLOBAL_LOGS: 12,
MANAGE_GLOBAL_NEWS: 13,
MANAGE_GLOBAL_BOARDS: 14,
MANAGE_GLOBAL_SETTINGS: 15,
MANAGE_GLOBAL_ACCOUNTS: 16,
MANAGE_GLOBAL_ROLES: 17,
MANAGE_BOARD_OWNER: 20,
MANAGE_BOARD_GENERAL: 21,
MANAGE_BOARD_BANS: 22,
MANAGE_BOARD_LOGS: 23,
MANAGE_BOARD_SETTINGS: 24,
MANAGE_BOARD_CUSTOMISATION: 25,
MANAGE_BOARD_STAFF: 26,
_MANAGE_BOARD_BITS: [20,21,22,23,24,25,26],
USE_MARKDOWN_PINKTEXT: 35,
USE_MARKDOWN_GREENTEXT: 36,
USE_MARKDOWN_BOLD: 37,
USE_MARKDOWN_UNDERLINE: 38,
USE_MARKDOWN_STRIKETHROUGH: 39,
USE_MARKDOWN_TITLE: 40,
USE_MARKDOWN_ITALIC: 41,
USE_MARKDOWN_SPOILER: 42,
USE_MARKDOWN_MONO: 43,
USE_MARKDOWN_CODE: 44,
USE_MARKDOWN_DETECTED: 45,
USE_MARKDOWN_LINK: 46,
USE_MARKDOWN_DICE: 47,
USE_MARKDOWN_FORTUNE: 48,
};
module.exports = Object.seal(Object.freeze(Object.preventExtensions(Permissions)));

@ -0,0 +1,42 @@
'use strict';
//todo: merge in permissions.js
module.exports = {
ROOT: { title: 'Root', label: 'Root', desc: 'Full control. Use with caution!' },
VIEW_RAW_IP: { title: 'Raw IPs', label: 'View Raw IPs', desc: 'Ability to see raw IPs in moderation interfaces.' },
CREATE_BOARD: { title: 'Create', label: 'Create Board', desc: 'Ability to create new boards.' },
CREATE_ACCOUNT: { label: 'Create Account', desc: 'Ability to register an account.' },
BYPASS_BANS: { title: 'Bypasses', label: 'Bypass Bans', desc: 'Bypass all bans.' },
BYPASS_SPAMCHECK: { label: 'Bypass Spamcheck', desc: 'Bypass the basic anti-flood spamcheck for too frequent similar posting.' },
BYPASS_RATELIMITS: { label: 'Bypass Ratelimits', desc: 'Bypass ratelimits for getting new captchas, editing posts, editing board settings, etc.' },
BYPASS_FILTERS: { label: 'Bypass Filters', desc: 'Bypass all post filters.' },
MANAGE_GLOBAL_GENERAL: { title: 'Global Management',label: 'Global Staff', desc: 'General global staff permission. Access to recent posts and reports. Ability to submit global actions.' },
MANAGE_GLOBAL_BANS: { label: 'Global Bans', desc: 'Access global bans. Ability to unban, edit, or deny appeals.' },
MANAGE_GLOBAL_LOGS: { label: 'Global Logs', desc: 'Access global logs. Ability to search/filter' },
MANAGE_GLOBAL_NEWS: { label: 'News', desc: 'Access news posting. Ability to add, edit, or delete newsposts.' },
MANAGE_GLOBAL_BOARDS: { label: 'Boards', desc: 'Access the global board list. Ability to search/filter. Also grants the ability to transfer or delete any board.' },
MANAGE_GLOBAL_SETTINGS: { label: 'Global Settings', desc: 'Access global settings. Ability to change any settings.' },
MANAGE_GLOBAL_ACCOUNTS: { label: 'Accounts', desc: 'Access the accounts list. Ability to search/sort. Ability to edit permissions of any user. Can only be given by somebody else with "Root" permission.' }, //view, delete, change account permissions
MANAGE_GLOBAL_ROLES: { label: 'Roles', desc: 'Access roles list. Ability to edit roles. Can only be given by somebody else with "Root" permission.' }, //view and edit roles
MANAGE_BOARD_OWNER: { title: 'Board Management', label: 'Board Owner', desc: 'Full control of the board, equivalent to the BO. Can delete and/or transfer the board. Can only be given by somebody else with "Board Owner" permission. Use with caution!' },
MANAGE_BOARD_GENERAL: { label: 'Board Staff', desc: 'General board staff permission. Access mod index, catalog, recent posts and reports. Ability to submit mod actions. Bypass board-specific bans and post filters.' },
MANAGE_BOARD_BANS: { label: 'Bans', desc: 'Access board bans. Ability to unban, edit, or deny appeals.' },
MANAGE_BOARD_LOGS: { label: 'Logs', desc: 'Access board logs. Ability to search/filter.' },
MANAGE_BOARD_SETTINGS: { label: 'Settings', desc: 'Access board settings. Ability to change any settings. Settings page will show transfer/delete forms for those with "Board Owner" permission.' },
MANAGE_BOARD_CUSTOMISATION: { label: 'Customisation', desc: 'Access to board assets and custompages. Ability to upload, create, edit, delete.' },
MANAGE_BOARD_STAFF: { label: 'Staff', desc: 'Access to staff management, and ability to add or remove permissions from others. Can only be given by somebody else with "Board Owner" permission. Use with caution!' },
USE_MARKDOWN_PINKTEXT: { title: 'Post styling', label: 'Pinktext', desc: 'Use pinktext' },
USE_MARKDOWN_GREENTEXT: { label: 'Greentext', desc: 'Use greentext' },
USE_MARKDOWN_BOLD: { label: 'Bold', desc: 'Use bold' },
USE_MARKDOWN_UNDERLINE: { label: 'Underline', desc: 'Use underline' },
USE_MARKDOWN_STRIKETHROUGH: { label: 'Strikethrough', desc: 'Use strikethrough' },
USE_MARKDOWN_TITLE: { label: 'Title', desc: 'Use titles' },
USE_MARKDOWN_ITALIC: { label: 'Italic', desc: 'Use italics' },
USE_MARKDOWN_SPOILER: { label: 'Spoiler', desc: 'Use spoilers' },
USE_MARKDOWN_MONO: { label: 'Inline Monospace', desc: 'Use inline monospace' },
USE_MARKDOWN_CODE: { label: 'Code Block', desc: 'Use code blocks' },
USE_MARKDOWN_DETECTED: { label: 'Detected', desc: 'Use detected' },
USE_MARKDOWN_LINK: { label: 'Links', desc: 'Make links clickable' },
USE_MARKDOWN_DICE: { label: 'Dice Roll', desc: 'Use dice rolls' },
USE_MARKDOWN_FORTUNE: { label: 'Fortune', desc: 'Use fortunes' },
};

@ -30,7 +30,7 @@ module.exports = {
return `${matchWithoutValue}=${sum}${value ? value : ''}`;
},
markdown: (permLevel, match, numdice, numsides, operator, modifier, value) => {
markdown: (permissions, match, numdice, numsides, operator, modifier, value) => {
numdice = parseInt(numdice);
numsides = parseInt(numsides);
value = parseInt(value);

@ -1,11 +1,13 @@
'use strict';
module.exports = (permLevel, match, p1, p2, p3, offset, string, groups) => {
const Permissions = require(__dirname+'/../permissions.js');
module.exports = (permissions, match, p1, p2, p3, offset, string, groups) => {
let { url, label, urlOnly } = groups;
url = url || urlOnly;
if (permLevel >= 4) {
if (!permissions.get(Permissions.MANAGE_BOARD_GENERAL)) {
label = url
.replace(/\(/g, '&lpar;')
.replace(/\)/g, '&rpar;');

@ -22,29 +22,27 @@ const greentextRegex = /^&gt;((?!&gt;\d+|&gt;&gt;&#x2F;\w+(&#x2F;\d*)?|&gt;&gt;#
, config = require(__dirname+'/../../config.js')
, diceroll = require(__dirname+'/diceroll.js')
, fortune = require(__dirname+'/fortune.js')
, linkmatch = require(__dirname+'/linkmatch.js');
, linkmatch = require(__dirname+'/linkmatch.js')
, Permissions = require(__dirname+'/../permissions.js');
let replacements = []
, markdownPermLevels;
const updateMarkdownPerms = () => {
markdownPermLevels = config.get.permLevels.markdown;
replacements = [
{ permLevel: markdownPermLevels.pink, regex: pinktextRegex, cb: (permLevel, match, pinktext) => `<span class='pinktext'>&lt;${pinktext}</span>` },
{ permLevel: markdownPermLevels.green, regex: greentextRegex, cb: (permLevel, match, greentext) => `<span class='greentext'>&gt;${greentext}</span>` },
{ permLevel: markdownPermLevels.bold, regex: boldRegex, cb: (permLevel, match, bold) => `<span class='bold'>${bold}</span>` },
{ permLevel: markdownPermLevels.underline, regex: underlineRegex, cb: (permLevel, match, underline) => `<span class='underline'>${underline}</span>` },
{ permLevel: markdownPermLevels.strike, regex: strikeRegex, cb: (permLevel, match, strike) => `<span class='strike'>${strike}</span>` },
{ permLevel: markdownPermLevels.title, regex: titleRegex, cb: (permLevel, match, title) => `<span class='title'>${title}</span>` },
{ permLevel: markdownPermLevels.italic, regex: italicRegex, cb: (permLevel, match, italic) => `<span class='em'>${italic}</span>` },
{ permLevel: markdownPermLevels.spoiler, regex: spoilerRegex, cb: (permLevel, match, spoiler) => `<span class='spoiler'>${spoiler}</span>` },
{ permLevel: markdownPermLevels.mono, regex: monoRegex, cb: (permLevel, match, mono) => `<span class='mono'>${mono}</span>` },
{ permLevel: markdownPermLevels.detected, regex: detectedRegex, cb: (permLevel, match, detected) => `<span class='detected'>&lpar;&lpar;&lpar; ${detected} &rpar;&rpar;&rpar;</span>` },
{ permLevel: markdownPermLevels.link, regex: linkRegex, cb: linkmatch },
{ permLevel: markdownPermLevels.dice, regex: diceroll.regexMarkdown, cb: diceroll.markdown },
{ permLevel: markdownPermLevels.fortune, regex: fortune.regex, cb: fortune.markdown },
{ permission: Permissions.USE_MARKDOWN_PINKTEXT, regex: pinktextRegex, cb: (permissions, match, pinktext) => `<span class='pinktext'>&lt;${pinktext}</span>` },
{ permission: Permissions.USE_MARKDOWN_GREENTEXT, regex: greentextRegex, cb: (permissions, match, greentext) => `<span class='greentext'>&gt;${greentext}</span>` },
{ permission: Permissions.USE_MARKDOWN_BOLD, regex: boldRegex, cb: (permissions, match, bold) => `<span class='bold'>${bold}</span>` },
{ permission: Permissions.USE_MARKDOWN_UNDERLINE, regex: underlineRegex, cb: (permissions, match, underline) => `<span class='underline'>${underline}</span>` },
{ permission: Permissions.USE_MARKDOWN_STRIKETHROUGH, regex: strikeRegex, cb: (permissions, match, strike) => `<span class='strike'>${strike}</span>` },
{ permission: Permissions.USE_MARKDOWN_TITLE, regex: titleRegex, cb: (permissions, match, title) => `<span class='title'>${title}</span>` },
{ permission: Permissions.USE_MARKDOWN_ITALIC, regex: italicRegex, cb: (permissions, match, italic) => `<span class='em'>${italic}</span>` },
{ permission: Permissions.USE_MARKDOWN_SPOILER, regex: spoilerRegex, cb: (permissions, match, spoiler) => `<span class='spoiler'>${spoiler}</span>` },
{ permission: Permissions.USE_MARKDOWN_MONO, regex: monoRegex, cb: (permissions, match, mono) => `<span class='mono'>${mono}</span>` },
{ permission: Permissions.USE_MARKDOWN_DETECTED, regex: detectedRegex, cb: (permissions, match, detected) => `<span class='detected'>&lpar;&lpar;&lpar; ${detected} &rpar;&rpar;&rpar;</span>` },
{ permission: Permissions.USE_MARKDOWN_LINK, regex: linkRegex, cb: linkmatch },
{ permission: Permissions.USE_MARKDOWN_DICE, regex: diceroll.regexMarkdown, cb: diceroll.markdown },
{ permission: Permissions.USE_MARKDOWN_FORTUNE, regex: fortune.regex, cb: fortune.markdown },
];
//todo: add any missing perm levels so no migration required so people can add custom markdown on their own. maybe give these a name property and give it a class
};
updateMarkdownPerms();
@ -67,7 +65,7 @@ module.exports = {
return chunks.join('');
},
markdown: (text, permLevel=4) => {
markdown: (text, permissions) => {
const chunks = text.split(splitRegex);
const { highlightOptions } = config.get;
for (let i = 0; i < chunks.length; i++) {
@ -75,8 +73,8 @@ module.exports = {
if (i % 2 === 0) {
const escaped = escape(chunks[i]);
const newlineFix = escaped.replace(/^\r?\n/,''); //fix ending newline because of codeblock
chunks[i] = module.exports.processRegularChunk(newlineFix, permLevel);
} else if (permLevel <= markdownPermLevels.code){
chunks[i] = module.exports.processRegularChunk(newlineFix, permissions);
} else if (permissions.get(Permissions.USE_MARKDOWN_CODE)){
chunks[i] = module.exports.processCodeChunk(chunks[i], highlightOptions);
}
}
@ -106,12 +104,12 @@ module.exports = {
return `<span class='code'>${escape(trimFix)}</span>`;
},
processRegularChunk: (text, permLevel) => {
//so theoretically now with some more options in the global manage page you can set permissions or enable/disable markdowns
const allowedReplacements = replacements.filter(r => r.permLevel >= permLevel);
processRegularChunk: (text, permissions) => {
//filter replacements based on their permissions
const allowedReplacements = replacements.filter(r => permissions.get(r.permission));
for (let i = 0; i < allowedReplacements.length; i++) {
//could bind more variables here and make them available as additional arguments. would pass more args -> markdown -> procesRegularChunk, etc.
text = text.replace(allowedReplacements[i].regex, allowedReplacements[i].cb.bind(null, permLevel));
text = text.replace(allowedReplacements[i].regex, allowedReplacements[i].cb.bind(null, permissions));
}
return text;
},

@ -1,19 +1,27 @@
'use strict';
const quoteHandler = require(__dirname+'/quotes.js')
, { markdown } = require(__dirname+'/markdown.js')
, sanitizeOptions = require(__dirname+'/sanitizeoptions.js')
, Permission = require(__dirname+'/../permissions.js')
, roleManager = require(__dirname+'/../rolemanager.js')
, sanitize = require('sanitize-html');
module.exports = async (inputMessage, boardName, threadId=null, permLevel=4) => {
module.exports = async (inputMessage, boardName, threadId=null, permissions=null) => {
let message = inputMessage;
let quotes = [];
let crossquotes = [];
if (permissions === null) {
//technically there has for a long time been a bug here, but it can be fixed later. permissions unknown for old msgs
permissions = new Permission(roleManager.roles.ANON.base64);
}
//markdown a post, link the quotes, sanitize and return message and quote arrays
if (message && message.length > 0) {
message = markdown(message, permLevel);
message = markdown(message, permissions);
const { quotedMessage, threadQuotes, crossQuotes } = await quoteHandler.process(boardName, message, threadId);
message = quotedMessage;
quotes = threadQuotes;

@ -1,32 +1,40 @@
'use strict';
const { getInsecureTrip, getSecureTrip } = require(__dirname+'/tripcode.js')
, Permissions = require(__dirname+'/../permissions.js')
, nameRegex = /^(?<name>[^#].*?)?(?:(?<tripcode>##(?<strip>[^ ].*?)|#(?<itrip>[^#].*?)))?(?<capcode>##(?<capcodetext> .*?)?)?$/
, staffLevels = ['Admin', 'Global Staff', 'Board Owner', 'Board Mod']
, staffLevelsRegex = new RegExp(`(${staffLevels.join('|')})+`, 'igm')
, staffLevelsRegex = new RegExp(`(${staffLevels.join('|')})+`, 'igm');
module.exports = async (inputName, permLevel, boardSettings, boardOwner, username) => {
module.exports = async (inputName, permissions, boardSettings, boardOwner, boardStaff, username) => {
const { forceAnon, defaultName, moderators } = boardSettings;
const isBoardOwner = username === boardOwner;
const isBoardMod = moderators.includes(username);
const { forceAnon, defaultName } = boardSettings;
const isBoardOwner = username === boardOwner; //why not just check staffboards and ownedboards?
const staffUsernames = Object.keys(boardStaff);
const isBoardMod = staffUsernames.includes(username);
const staffPermissions = [permissions.get(Permissions.ROOT),
permissions.get(Permissions.MANAGE_GLOBAL_GENERAL),
isBoardOwner, isBoardMod];
let name = defaultName;
let tripcode = null;
let capcode = null;
if ((permLevel < 4 || !forceAnon) && inputName && inputName.length > 0) {
if ((permissions.get(Permissions.MANAGE_BOARD_GENERAL) || !forceAnon) && inputName && inputName.length > 0) {
// get matches with named groups for name, trip and capcode in 1 regex
const matches = inputName.match(nameRegex);
if (matches && matches.groups) {
const groups = matches.groups;
//name
if (groups.name) {
name = groups.name;
}
//tripcode
if (groups.tripcode) {
let tripcodeText = groups.strip || groups.itrip;
if (permLevel >= 4 && groups.capcode === '##' && !groups.capcodetext) {
if (!permissions.get(Permissions.MANAGE_BOARD_GENERAL) && groups.capcode === '##' && !groups.capcodetext) {
//for the complaining non-staff troglodyte who puts the name as all #s
tripcodeText = tripcodeText.concat('##');
}
@ -36,14 +44,26 @@ module.exports = async (inputName, permLevel, boardSettings, boardOwner, usernam
tripcode = `!${getInsecureTrip(tripcodeText)}`;
}
}
//capcode
if (permLevel < 4 && groups.capcode) {
if (permissions.get(Permissions.MANAGE_BOARD_GENERAL) && groups.capcode) {
let capcodeInput = groups.capcodetext ? groups.capcodetext.trim() : '';
//by default, use board staff if ismod/owner, else use higher staff
let staffLevel = staffLevels.find((sl, l) => capcodeInput.toLowerCase().startsWith(sl.toLowerCase()) && l === permLevel)
|| (isBoardOwner ? staffLevels[2]
: isBoardMod ? staffLevels[3]
: staffLevels[permLevel]); //kill me
const { staffLevelDirect, staffLevelFallback } = staffPermissions.reduce((acc, sp, i) => {
if (sp === true) {
if (!acc.staffLevelFallback) {
acc.staffLevelFallback = staffLevels[i];
}
if (!acc.staffLevelDirect && capcodeInput.toLowerCase().startsWith(staffLevels[i].toLowerCase())) {
acc.staffLevelDirect = staffLevels[i];
}
}
return acc;
}, { 'staffLevelDirect': null, 'staffLevelFallback': null });
//we still get the same fallbacks as before, its just more annoying without the direct permlevel mapping
const staffLevel = staffLevelDirect ||
(isBoardOwner ? staffLevels[2]
: isBoardMod ? staffLevels[3]
: staffLevelFallback);
capcode = staffLevel;
if (capcodeInput && capcodeInput.toLowerCase() !== staffLevel.toLowerCase()) {
capcode = `${staffLevel} ${capcodeInput.replace(staffLevelsRegex, '').trim()}`;

@ -19,7 +19,7 @@ module.exports = (req, res, next) => {
}
//ip for normal user
const { ipHeader, ipHashPermLevel } = config.get;
const { dontStoreRawIps, ipHeader } = config.get;
const ip = req.headers[ipHeader] || req.connection.remoteAddress;
try {
const ipParsed = parse(ip);
@ -40,9 +40,11 @@ module.exports = (req, res, next) => {
}
const cloak = `${hashIp(hrange).substring(0,8)}.${hashIp(qrange).substring(0,7)}.${hashIp(ipStr).substring(0,7)}.IP`;
res.locals.ip = {
raw: ipHashPermLevel === -1 ? cloak : ipStr,
raw: dontStoreRawIps === true ? cloak : ipStr,
cloak,
}
//#426
//console.log(`net-${hashIp(hrange).substring(0,6)}.${hashIp(qrange).substring(0,4)}.${hashIp(ipStr).substring(0,4)}.IP`)
next();
} catch(e) {
console.error('Ip parse failed', e);

@ -11,19 +11,18 @@ const { outputFile } = require('fs-extra')
, { addCallback } = require(__dirname+'/../redis.js')
, { version } = require(__dirname+'/../package.json')
, templateDirectory = path.join(__dirname+'/../views/pages/')
, Permissions = require(__dirname+'/permissions.js')
, config = require(__dirname+'/../config.js');
let { enableUserBoardCreation, enableUserAccountCreation, archiveLinksURL,
lockWait, globalLimits, boardDefaults, cacheTemplates, reverseImageLinksURL,
meta, enableWebring, captchaOptions, globalAnnouncement } = config.get
let { archiveLinksURL, lockWait, globalLimits, boardDefaults, cacheTemplates,
reverseImageLinksURL, meta, enableWebring, captchaOptions, globalAnnouncement } = config.get
, renderLocals = null;
const updateLocals = () => {
({ enableUserBoardCreation, enableUserAccountCreation, archiveLinksURL,
lockWait, globalLimits, boardDefaults, cacheTemplates, reverseImageLinksURL,
meta, enableWebring, captchaOptions, globalAnnouncement } = config.get);
const updateLocals = () => {
({ archiveLinksURL, lockWait, globalLimits, boardDefaults, cacheTemplates,
reverseImageLinksURL, meta, enableWebring, captchaOptions, globalAnnouncement } = config.get);
renderLocals = {
authLevelNames: ['Admin', 'Global Staff', 'Global Board Owner', 'Global Board Mod', 'Regular User'],
Permissions,
cache: cacheTemplates,
archiveLinksURL,
reverseImageLinksURL,
@ -33,8 +32,6 @@ const updateLocals = () => {
defaultTheme: boardDefaults.theme,
defaultCodeTheme: boardDefaults.codeTheme,
postFilesSize: formatSize(globalLimits.postFilesSize.max),
enableUserAccountCreation,
enableUserBoardCreation,
globalLimits,
enableWebring,
captchaType: captchaOptions.type,

@ -0,0 +1,39 @@
'use strict';
const { Roles } = require(__dirname+'/../db/')
, { Binary } = require(__dirname+'/../db/db.js')
, redis = require(__dirname+'/../redis.js')
, Permissions = require(__dirname+'/permissions.js')
, Permission = require(__dirname+'/permission.js');
const load = async () => {
//todo: take a message argument from callback
//maybe make it a separate func just for reloading single role?
let roles = await Roles.find();
roles = roles.reduce((acc, r) => {
acc[r.name] = new Permission(r.permissions.toString('base64'));
return acc;
}, {});
module.exports.roles = roles;
module.exports.roleNameMap = {
[roles.ANON.base64]: 'Regular User',
[roles.BOARD_STAFF.base64]: 'Board Staff',
[roles.BOARD_OWNER.base64]: 'Board Owner',
[roles.GLOBAL_STAFF.base64]: 'Global Staff',
[roles.ADMIN.base64]: 'Admin',
[roles.ROOT.base64]: 'Root',
};
};
redis.addCallback('roles', load);
module.exports = {
roles: {},
roleNameMap: {},
load,
};

@ -58,10 +58,13 @@ module.exports = {
},
//check the actual schema
checkSchema: async (schema, permLevel) => {
checkSchema: async (schema, permissions) => {
const errors = [];
//filter check if my perm level is lower than the requirement. e.g. bypass filters checks
const filteredSchema = schema.filter(c => c.permLevel == null || c.permLevel < permLevel);
//filter to checks with no permission or ones we dont have permission to skip.
let filteredSchema = schema;
if (permissions) {
filteredSchema = filteredSchema.filter(c => c.permission == null || !permissions.get(c.permission));
}
for (let check of filteredSchema) {
const result = await (typeof check.result === 'function' ? check.result() : check.result);
const expected = (check.expected || false);

@ -17,8 +17,8 @@ module.exports = async (req, res, next) => {
await Accounts.updateLastActiveDate(req.session.user);
res.locals.user = {
'username': account._id,
'authLevel': account.authLevel,
'modBoards': account.modBoards,
'permissions': account.permissions.toString('base64'),
'staffBoards': account.staffBoards,
'ownedBoards': account.ownedBoards,
};
cache.set(`users:${req.session.user}`, res.locals.user, 3600);

@ -6,6 +6,26 @@ module.exports = async(db, redis) => {
console.log('add markdown permissions');
const template = require(__dirname+'/../configs/template.js.example');
const settings = await redis.get('globalsettings');
const newSettings = { ...settings, permLevels: template.permLevels };
const newSettings = {
...settings,
permLevels: {
markdown: {
pink: 4,
green: 4,
bold: 4,
underline: 4,
strike: 4,
italic: 4,
title: 4,
spoiler: 4,
mono: 4,
code: 4,
link: 4,
detected: 4,
dice: 4,
fortune: 0,
},
},
};
redis.set('globalsettings', newSettings);
};

@ -2,14 +2,19 @@
const hashIp = require(__dirname+'/../helpers/haship.js')
, { createCIDR, parse } = require('ip6addr')
, config = require(__dirname+'/../config.js');
, config = require(__dirname+'/../config.js')
, Permission = require(__dirname+'/../helpers/permission.js')
, Permissions = require(__dirname+'/../helpers/permissions.js')
, { Binary } = require('mongodb');
module.exports = async(db, redis) => {
// IP CLOAKING
const postIps = await db.collection('posts').distinct('ip.raw');
const logIps = await db.collection('modlog').distinct('ip.raw');
const banIps = await db.collection('bans').distinct('ip.raw');
const allDistinctIps = postIps.concat(logIps).concat(banIps);
const bulkWrites = allDistinctIps.map(ip => {
const ipBulkWrites = allDistinctIps.map(ip => {
const ipSet = {};
try {
const ipParsed = parse(ip);
@ -53,9 +58,9 @@ module.exports = async(db, redis) => {
});
console.log('adjusting ip in modlogs, bans and posts');
//the bulkwrites should work for ip, bans, and logs
await db.collection('posts').bulkWrite(bulkWrites);
await db.collection('modlog').bulkWrite(bulkWrites);
await db.collection('bans').bulkWrite(bulkWrites);
await db.collection('posts').bulkWrite(ipBulkWrites);
await db.collection('modlog').bulkWrite(ipBulkWrites);
await db.collection('bans').bulkWrite(ipBulkWrites);
console.log('removing saved posts inside bans');
await db.collection('bans').updateMany({}, {
'$set':{
@ -74,4 +79,123 @@ module.exports = async(db, redis) => {
await db.collection('bans').dropIndexes();
await db.collection('bans').createIndex({ 'ip.cloak': 1 , 'board': 1 });
await db.collection('bans').createIndex({ 'expireAt': 1 }, { expireAfterSeconds: 0 });
}
// PERMISSIONS UPDATE
console.log('making db changes for permissions update');
console.log('setting new permission templates to replace old permission "levels"');
const ANON = new Permission()
ANON.setAll([
Permissions.USE_MARKDOWN_PINKTEXT, Permissions.USE_MARKDOWN_GREENTEXT, Permissions.USE_MARKDOWN_BOLD,
Permissions.USE_MARKDOWN_UNDERLINE, Permissions.USE_MARKDOWN_STRIKETHROUGH, Permissions.USE_MARKDOWN_TITLE,
Permissions.USE_MARKDOWN_ITALIC, Permissions.USE_MARKDOWN_SPOILER, Permissions.USE_MARKDOWN_MONO,
Permissions.USE_MARKDOWN_CODE, Permissions.USE_MARKDOWN_DETECTED, Permissions.USE_MARKDOWN_LINK,
Permissions.USE_MARKDOWN_DICE, Permissions.USE_MARKDOWN_FORTUNE, Permissions.CREATE_BOARD,
Permissions.CREATE_ACCOUNT
]);
const BOARD_STAFF = new Permission(ANON.base64)
BOARD_STAFF.setAll([
Permissions.MANAGE_BOARD_GENERAL, Permissions.MANAGE_BOARD_BANS, Permissions.MANAGE_BOARD_LOGS,
]);
const BOARD_OWNER = new Permission(BOARD_STAFF.base64)
BOARD_OWNER.setAll([
Permissions.MANAGE_BOARD_OWNER, Permissions.MANAGE_BOARD_STAFF, Permissions.MANAGE_BOARD_CUSTOMISATION,
Permissions.MANAGE_BOARD_SETTINGS,
]);
const GLOBAL_STAFF = new Permission(BOARD_OWNER.base64);
GLOBAL_STAFF.setAll([
Permissions.MANAGE_GLOBAL_GENERAL, Permissions.MANAGE_GLOBAL_BANS, Permissions.MANAGE_GLOBAL_LOGS, Permissions.MANAGE_GLOBAL_NEWS,
Permissions.MANAGE_GLOBAL_BOARDS, Permissions.MANAGE_GLOBAL_SETTINGS, Permissions.MANAGE_BOARD_OWNER, Permissions.BYPASS_FILTERS,
Permissions.BYPASS_BANS, Permissions.BYPASS_SPAMCHECK, Permissions.BYPASS_RATELIMITS,
]);
const ADMIN = new Permission(GLOBAL_STAFF.base64);
ADMIN.setAll([
Permissions.MANAGE_GLOBAL_ACCOUNTS, Permissions.MANAGE_GLOBAL_ROLES, Permissions.VIEW_RAW_IP,
]);
const ROOT = new Permission();
ROOT.setAll(Permission.allPermissions);
await db.collection('roles').deleteMany({});
await db.collection('roles').insertMany([
{ name: 'ANON', permissions: Binary(ANON.array) },
{ name: 'BOARD_STAFF', permissions: Binary(BOARD_STAFF.array) },
{ name: 'BOARD_OWNER', permissions: Binary(BOARD_OWNER.array) },
{ name: 'GLOBAL_STAFF', permissions: Binary(GLOBAL_STAFF.array) },
{ name: 'ADMIN', permissions: Binary(ADMIN.array) },
{ name: 'ROOT', permissions: Binary(ROOT.array) },
]);
await db.collection('accounts').updateMany({ authLevel: 0 }, {
'$set': {
'permissions': Binary(ROOT.array),
},
});
await db.collection('accounts').updateMany({ authLevel: 1 }, {
'$set': {
'permissions': Binary(GLOBAL_STAFF.array),
},
});
//not doing 2 and 3 anymore, since they were a weird, ugly part of the old "levels" system.
//they can be added back manually by editing global perms if desired
await db.collection('accounts').updateMany({ authLevel: { $gte: 2 } }, { //gte2, to get 2, 3, and 4.
'$set': {
'permissions': Binary(ANON.array),
},
});
console.log('renaming account modBoards->staffBoards');
await db.collection('accounts').updateMany({}, {
'$unset': {
'authLevel': "",
},
'$rename': {
'modBoards': 'staffBoards',
},
});
console.log('Adjusting global settings, and removing some redundant global settings that are now permission controlled');
await db.collection('globalsettings').updateOne({ _id: 'globalsettings' }, {
'$unset': {
'userAccountCreation': '',
'userBoardCreation': '',
'ipHashPermLevel': '',
'deleteBoardPermLevel': '',
},
'$set': {
'dontStoreRawIps': false,
}
});
//board moderators -> staff, and give them all the BOARD_STAFF perms
console.log('converting old "moderators" arrays to "staff" perms map and giving BOARD_STAFF template');
const allBoards = await db.collection('boards').find({ webring: false }).toArray();
const staffBulkWrites = allBoards.map(board => {
const staffObject = board.settings.moderators.reduce((acc, mod) => {
acc[mod] = {
permissions: Binary(BOARD_STAFF.array),
addedDate: new Date(),
};
return acc;
}, {});
//add add the BO to staff
staffObject[board.owner] = {
permissions: Binary(BOARD_OWNER.array),
addedDate: new Date(),
}
return {
'updateOne': {
'filter': {
'_id': board._id,
},
'update': {
'$unset': {
'settings.moderators': '',
},
'$set': {
'staff': staffObject,
}
}
}
};
});
await db.collection('boards').bulkWrite(staffBulkWrites);
console.log('Clearing globalsettings cache');
await redis.deletePattern('globalsettings');
console.log('Clearing user and board cache');
await redis.deletePattern('board:*');
await redis.deletePattern('users:*');
};

@ -17,6 +17,7 @@ const { Posts, Boards, Modlogs } = require(__dirname+'/../../db/')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js')
, getAffectedBoards = require(__dirname+'/../../helpers/affectedboards.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, buildQueue = require(__dirname+'/../../queue.js')
, { postPasswordSecret } = require(__dirname+'/../../configs/secrets.js')
, threadRegex = /\/[a-z0-9]+\/(?:manage\/)?thread\/(\d+)\.html/i
@ -34,7 +35,8 @@ module.exports = async (req, res, next) => {
}
//if user isnt staff, and they put an action that requires password, e.g. delete/spoiler, then filter posts to only matching password
if (res.locals.permLevel >= 4 && res.locals.actions.numPasswords > 0) {
const isStaffOrGlobal = res.locals.permissions.hasAny(Permissions.MANAGE_GLOBAL_GENERAL, Permissions.MANAGE_BOARD_GENERAL);
if (!isStaffOrGlobal && res.locals.actions.numPasswords > 0) {
let passwordPosts = [];
if (req.body.postpassword && req.body.postpassword.length > 0) {
//hash their input and make it a buffer
@ -96,8 +98,8 @@ module.exports = async (req, res, next) => {
messages.push(message);
}
if (deleting) {
if (res.locals.permLevel >= 4) {
//delete protection. this could only be single board actions obvously with permLevel >=4
if (!isStaffOrGlobal) {
//OP delete protection. for old or many replied OPs
const { deleteProtectionAge, deleteProtectionCount } = res.locals.board.settings;
if (deleteProtectionAge > 0 || deleteProtectionCount > 0) {
const protectedThread = res.locals.posts.some(p => {
@ -296,7 +298,8 @@ module.exports = async (req, res, next) => {
const logDate = new Date(); //all events current date
const message = req.body.log_message || null;
let logUser;
if (res.locals.permLevel < 4) { //if staff
//could even do if (req.session.user) {...}, but might cause cross-board log username contamination
if (isStaffOrGlobal) {
logUser = req.session.user;
} else {
logUser = 'Unregistered User';

@ -9,7 +9,7 @@ const { CustomPages } = require(__dirname+'/../../db/')
module.exports = async (req, res, next) => {
const message = prepareMarkdown(req.body.message, false);
const { message: markdownMessage } = await messageHandler(message, null, null, res.locals.permLevel);
const { message: markdownMessage } = await messageHandler(message, null, null, res.locals.permissions);
const post = {
'board': req.params.board,

@ -9,7 +9,7 @@ const { News } = require(__dirname+'/../../db/')
module.exports = async (req, res, next) => {
const message = prepareMarkdown(req.body.message, false);
const { message: markdownNews } = await messageHandler(message, null, null, res.locals.permLevel);
const { message: markdownNews } = await messageHandler(message, null, null, res.locals.permissions);
const post = {
'title': req.body.title,

@ -0,0 +1,20 @@
'use strict';
const { Boards, Accounts } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, roleManager = require(__dirname+'/../../helpers/rolemanager.js');
module.exports = async (req, res, next) => {
await Promise.all([
Accounts.addStaffBoard([req.body.username], res.locals.board._id),
Boards.addStaff(res.locals.board._id, req.body.username, roleManager.roles.BOARD_STAFF)
]);
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',
'message': 'Added staff',
'redirect': `/${req.params.board}/manage/staff.html`,
});
}

@ -41,32 +41,7 @@ module.exports = async (req, res, next) => {
const announcement = req.body.announcement === null ? null : prepareMarkdown(req.body.announcement, false);
let markdownAnnouncement = oldSettings.announcement.markdown;
if (announcement !== oldSettings.announcement.raw) {
({ message: markdownAnnouncement } = await messageHandler(announcement, req.params.board, null, res.locals.permLevel))
}
let moderators = req.body.moderators != null ? req.body.moderators.split(/\r?\n/).filter(n => n && !(n == res.locals.board.owner)).slice(0,10) : [];
if (moderators.length === 0 && oldSettings.moderators.length > 0) {
//remove all mods if mod list being emptied
promises.push(Accounts.removeModBoard(oldSettings.moderators, req.params.board));
} else if (moderators !== oldSettings.moderators) {
if (moderators.length > 0) {
//make sure moderators actually have existing accounts
const validCount = await Accounts.countUsers(moderators);
if (validCount !== moderators.length) {
//some usernames were not valid, reset to old setting
moderators = oldSettings.moderators;
} else {
//all accounts exist, check added/removed
const modsRemoved = oldSettings.moderators.filter(m => !moderators.includes(m));
const modsAdded = moderators.filter(m => !oldSettings.moderators.includes(m));
if (modsRemoved.length > 0) {
promises.push(Accounts.removeModBoard(modsRemoved, req.params.board));
}
if (modsAdded.length > 0) {
promises.push(Accounts.addModBoard(modsAdded, req.params.board));
}
}
}
({ message: markdownAnnouncement } = await messageHandler(announcement, req.params.board, null, res.locals.permissions))
}
if (req.body.countries) {
@ -76,7 +51,6 @@ module.exports = async (req, res, next) => {
}
const newSettings = {
moderators,
'name': trimSetting(req.body.name, oldSettings.name),
'description': trimSetting(req.body.description, oldSettings.description),
'defaultName': trimSetting(req.body.default_name, oldSettings.defaultName),

@ -46,14 +46,9 @@ module.exports = async (req, res, next) => {
const announcement = req.body.global_announcement === null ? null : prepareMarkdown(req.body.global_announcement, false);
let markdownAnnouncement = oldSettings.globalAnnouncement.markdown;
if (announcement !== oldSettings.globalAnnouncement.raw) {
({ message: markdownAnnouncement } = await messageHandler(announcement, null, null, res.locals.permLevel))
({ message: markdownAnnouncement } = await messageHandler(announcement, null, null, res.locals.permissions))
}
const newMarkdownPermLevels = Object.keys(oldSettings.permLevels.markdown).reduce((acc, val) => {
acc[val] = numberSetting(req.body[`perm_levels_markdown_${val}`], oldSettings.permLevels.markdown[val]);
return acc;
}, {});
const newSettings = {
filters: arraySetting(req.body.filters, oldSettings.filters),
filterMode: numberSetting(req.body.filter_mode, oldSettings.filterMode),
@ -105,11 +100,6 @@ module.exports = async (req, res, next) => {
expireAfterTime: numberSetting(req.body.block_bypass_expire_after_time, oldSettings.blockBypass.expireAfterTime),
bypassDnsbl: booleanSetting(req.body.block_bypass_bypass_dnsbl, oldSettings.blockBypass.bypassDnsbl),
},
ipHashPermLevel: numberSetting(req.body.ip_hash_perm_level, oldSettings.ipHashPermLevel),
deleteBoardPermLevel: numberSetting(req.body.delete_board_perm_level, oldSettings.deleteBoardPermLevel),
permLevels: {
markdown: newMarkdownPermLevels,
},
pruneImmediately: booleanSetting(req.body.prune_immediately, oldSettings.pruneImmediately),
hashImages: booleanSetting(req.body.hash_images, oldSettings.hashImages),
rateLimitCost: {
@ -125,6 +115,7 @@ module.exports = async (req, res, next) => {
cacheTemplates: booleanSetting(req.body.cache_templates, oldSettings.cacheTemplates),
lockWait: numberSetting(req.body.lock_wait, oldSettings.lockWait),
pruneModlogs: numberSetting(req.body.prune_modlogs, oldSettings.pruneModlogs),
dontStoreRawIps: booleanSetting(req.body.dont_store_raw_ips, oldSettings.dontStoreRawIps),
pruneIps: numberSetting(req.body.prune_ips, oldSettings.pruneIps),
enableWebring: booleanSetting(req.body.enable_webring, oldSettings.enableWebring),
following: arraySetting(req.body.webring_following, oldSettings.following),
@ -134,8 +125,6 @@ module.exports = async (req, res, next) => {
enabled: booleanSetting(req.body.webring_proxy_enabled, oldSettings.proxy.enabled),
address: trimSetting(req.body.webring_proxy_address, oldSettings.proxy.address),
},
enableUserBoardCreation: booleanSetting(req.body.enable_user_board_creation, oldSettings.enableUserBoardCreation),
enableUserAccountCreation: booleanSetting(req.body.enable_user_account_creation, oldSettings.enableUserAccountCreation),
thumbExtension: trimSetting(req.body.thumb_extension, oldSettings.thumbExtension),
highlightOptions: {
languageSubset: arraySetting(req.body.highlight_options_language_subset, oldSettings.highlightOptions.languageSubset),

@ -1,7 +1,9 @@
'use strict';
const { Boards, Accounts } = require(__dirname+'/../../db/')
, { Binary } = require(__dirname+'/../../db/db.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, roleManager = require(__dirname+'/../../helpers/rolemanager.js')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js')
, restrictedURIs = new Set(['captcha', 'forms', 'randombanner', 'all'])
, { ensureDir } = require('fs-extra')
@ -48,12 +50,17 @@ module.exports = async (req, res, next) => {
'ips': 0,
'lastPostTimestamp': null,
'webring': false,
'staff': {
[owner]: {
'permissions': Binary(roleManager.roles.BOARD_OWNER.array),
'addedDate': new Date(),
},
},
'flags': {},
'assets': [],
'settings': {
name,
description,
'moderators': [],
...boardDefaults
}
}

@ -0,0 +1,66 @@
'use strict';
const { Accounts, Boards } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, cache = require(__dirname+'/../../redis.js');
module.exports = async (req, res, next) => {
const accountsWithBoards = await Accounts.getOwnedOrStaffBoards(req.body.checkedaccounts);
if (accountsWithBoards.length > 0) {
const bulkWrites = [];
for (let i = 0; i < accountsWithBoards.length; i++) {
const acc = accountsWithBoards[i];
if (acc.staffBoards.length > 0) {
//remove from staff of any boards they are mod on
bulkWrites.push({
'updateMany': {
'filter': {
'_id': {
'$in': acc.staffBoards
}
},
'update': {
'$unset': {
[`staff.${acc.username}`]: "",
}
}
}
});
cache.del(acc.staffBoards.map(b => `board:${b}`));
}
if (acc.ownedBoards.length > 0) {
//remove as owner of any boards they own
bulkWrites.push({
'updateMany': {
'filter': {
'_id': {
'$in': acc.ownedBoards
}
},
'update': {
'$set': {
'owner': null,
},
'$unset': {
[`staff.${acc.username}`]: "",
},
}
}
});
cache.del(acc.ownedBoards.map(b => `board:${b}`));
//todo: use list of board with no owners for claims
}
}
await Boards.db.bulkWrite(bulkWrites);
}
const amount = await Accounts.deleteMany(req.body.checkedaccounts).then(res => res.deletedCount);
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',
'message': `${req.body.delete_account ? 'Deleted' : 'Edited'} ${amount} accounts`,
'redirect': '/globalmanage/accounts.html'
});
}

@ -17,7 +17,7 @@ module.exports = async (uri, board) => {
}
await Promise.all([
Accounts.removeOwnedBoard(board.owner, uri), //remove board from owner account
board.settings.moderators.length > 0 ? Accounts.removeModBoard(board.settings.moderators, uri) : void 0, //remove board from mods accounts
Object.keys(board.staff).length > 0 ? Accounts.removeStaffBoard(Object.keys(board.staff), uri) : void 0, //remove staffboard from staff accounts
Modlogs.deleteBoard(uri), //modlogs for the board
Bans.deleteBoard(uri), //bans for the board
Stats.deleteBoard(uri), //stats for the board
@ -26,7 +26,7 @@ module.exports = async (uri, board) => {
remove(`${uploadDirectory}/json/${uri}/`), //json
remove(`${uploadDirectory}/banner/${uri}/`), //banners
remove(`${uploadDirectory}/flag/${uri}/`), //flags
remove(`${uploadDirectory}/asset/${uri}/`), //flags
remove(`${uploadDirectory}/asset/${uri}/`), //assets
]);
}

@ -0,0 +1,24 @@
'use strict';
const { Boards, Accounts } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js');
module.exports = async (req, res, next) => {
//only a ROOT could do this, per the permission bypass in the controller
const deletingBoardOwner = req.body.checkedstaff.some(s => s === res.locals.board.owner);
await Promise.all([
Accounts.removeStaffBoard(req.body.checkedstaff, res.locals.board._id),
Boards.removeStaff(res.locals.board._id, req.body.checkedstaff),
deletingBoardOwner ? Accounts.removeOwnedBoard(res.locals.board.owner, res.locals.board._id) : void 0,
deletingBoardOwner ? Boards.setOwner(res.locals.board._id, null) : void 0,
]);
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',
'message': 'Deleted staff',
'redirect': `/${req.params.board}/manage/staff.html`,
});
}

@ -0,0 +1,74 @@
'use strict';
const { Accounts } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, Permission = require(__dirname+'/../../helpers/permission.js');
module.exports = async (req, res, next) => {
let updatingPermissions;
if (req.body.template) {
updatingPermissions = new Permission(req.body.template);
} else {
updatingPermissions = new Permission(res.locals.editingAccount.permissions);
updatingPermissions.set(Permissions.VIEW_RAW_IP, (req.body.VIEW_RAW_IP != null));
updatingPermissions.set(Permissions.CREATE_BOARD, (req.body.CREATE_BOARD != null));
updatingPermissions.set(Permissions.CREATE_ACCOUNT, (req.body.CREATE_ACCOUNT != null));
updatingPermissions.set(Permissions.BYPASS_BANS, (req.body.BYPASS_BANS != null));
updatingPermissions.set(Permissions.BYPASS_SPAMCHECK, (req.body.BYPASS_SPAMCHECK != null));
updatingPermissions.set(Permissions.BYPASS_RATELIMITS, (req.body.BYPASS_RATELIMITS != null));
updatingPermissions.set(Permissions.BYPASS_FILTERS, (req.body.BYPASS_FILTERS != null));
updatingPermissions.set(Permissions.MANAGE_GLOBAL_GENERAL, (req.body.MANAGE_GLOBAL_GENERAL != null));
updatingPermissions.set(Permissions.MANAGE_GLOBAL_BANS, (req.body.MANAGE_GLOBAL_BANS != null));
updatingPermissions.set(Permissions.MANAGE_GLOBAL_LOGS, (req.body.MANAGE_GLOBAL_LOGS != null));
updatingPermissions.set(Permissions.MANAGE_GLOBAL_NEWS, (req.body.MANAGE_GLOBAL_NEWS != null));
updatingPermissions.set(Permissions.MANAGE_GLOBAL_BOARDS, (req.body.MANAGE_GLOBAL_BOARDS != null));
updatingPermissions.set(Permissions.MANAGE_GLOBAL_SETTINGS, (req.body.MANAGE_GLOBAL_SETTINGS != null));
updatingPermissions.set(Permissions.MANAGE_BOARD_GENERAL, (req.body.MANAGE_BOARD_GENERAL != null));
updatingPermissions.set(Permissions.MANAGE_BOARD_OWNER, (req.body.MANAGE_BOARD_OWNER != null));
updatingPermissions.set(Permissions.MANAGE_BOARD_BANS, (req.body.MANAGE_BOARD_BANS != null));
updatingPermissions.set(Permissions.MANAGE_BOARD_LOGS, (req.body.MANAGE_BOARD_LOGS != null));
updatingPermissions.set(Permissions.MANAGE_BOARD_SETTINGS, (req.body.MANAGE_BOARD_SETTINGS != null));
updatingPermissions.set(Permissions.MANAGE_BOARD_CUSTOMISATION, (req.body.MANAGE_BOARD_CUSTOMISATION != null));
updatingPermissions.set(Permissions.MANAGE_BOARD_STAFF, (req.body.MANAGE_BOARD_STAFF != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_PINKTEXT, (req.body.USE_MARKDOWN_PINKTEXT != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_GREENTEXT, (req.body.USE_MARKDOWN_GREENTEXT != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_BOLD, (req.body.USE_MARKDOWN_BOLD != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_UNDERLINE, (req.body.USE_MARKDOWN_UNDERLINE != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_STRIKETHROUGH, (req.body.USE_MARKDOWN_STRIKETHROUGH != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_TITLE, (req.body.USE_MARKDOWN_TITLE != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_ITALIC, (req.body.USE_MARKDOWN_ITALIC != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_SPOILER, (req.body.USE_MARKDOWN_SPOILER != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_MONO, (req.body.USE_MARKDOWN_MONO != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_CODE, (req.body.USE_MARKDOWN_CODE != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_DETECTED, (req.body.USE_MARKDOWN_DETECTED != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_LINK, (req.body.USE_MARKDOWN_LINK != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_DICE, (req.body.USE_MARKDOWN_DICE != null));
updatingPermissions.set(Permissions.USE_MARKDOWN_FORTUNE, (req.body.USE_MARKDOWN_FORTUNE != null));
if (res.locals.permissions.get(Permissions.ROOT)) {
updatingPermissions.set(Permissions.MANAGE_GLOBAL_ACCOUNTS, (req.body.MANAGE_GLOBAL_ACCOUNTS != null))
updatingPermissions.set(Permissions.MANAGE_GLOBAL_ROLES, (req.body.MANAGE_GLOBAL_ROLES != null))
updatingPermissions.set(Permissions.ROOT, (req.body.ROOT != null));
}
}
updatingPermissions.applyInheritance();
const updated = await Accounts.setAccountPermissions(req.body.username, updatingPermissions).then(r => r.matchedCount);
if (updated === 0) {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'errors': 'Account does not exist',
'redirect': req.headers.referer || `/globalmanage/accounts.html`,
});
}
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',
'message': 'Edited account',
'redirect': `/globalmanage/editaccount/${req.body.username}.html`,
});
}

@ -1,68 +0,0 @@
'use strict';
const { Accounts, Boards } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, cache = require(__dirname+'/../../redis.js')
module.exports = async (req, res, next) => {
//edit the accounts
let amount = 0;
if (req.body.delete_account) {
const accountsWithBoards = await Accounts.getOwnedOrModBoards(req.body.checkedaccounts);
if (accountsWithBoards.length > 0) {
const bulkWrites = [];
for (let i = 0; i < accountsWithBoards.length; i++) {
const acc = accountsWithBoards[i];
if (acc.modBoards.length > 0) {
//remove from moderators of any boards they are mod on
bulkWrites.push({
'updateMany': {
'filter': {
'_id': {
'$in': acc.modBoards
}
},
'update': {
'$pull': {
'settings.moderators': acc._id
}
}
}
});
cache.del(acc.modBoards.map(b => `board:${b}`));
}
if (acc.ownedBoards.length > 0) {
//remove from moderators of any boards they are mod on
bulkWrites.push({
'updateMany': {
'filter': {
'_id': {
'$in': acc.ownedBoards
}
},
'update': {
'$set': {
'owner': null //board has no owner
}
}
}
});
cache.del(acc.ownedBoards.map(b => `board:${b}`));
//todo: use list of board with no owners for claims
}
}
await Boards.db.bulkWrite(bulkWrites);
}
amount = await Accounts.deleteMany(req.body.checkedaccounts).then(res => res.deletedCount);
} else {
amount = await Accounts.setLevel(req.body.checkedaccounts, req.body.auth_level).then(res => res.modifiedCount);
}
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',
'message': `${req.body.delete_account ? 'Deleted' : 'Edited'} ${amount} accounts`,
'redirect': '/globalmanage/accounts.html'
});
}

@ -11,7 +11,7 @@ const { CustomPages } = require(__dirname+'/../../db/')
module.exports = async (req, res, next) => {
const message = prepareMarkdown(req.body.message, false);
const { message: markdownPage } = await messageHandler(message, null, null, res.locals.permLevel);
const { message: markdownPage } = await messageHandler(message, null, null, res.locals.permissions);
const editedDate = new Date();
const oldPage = await CustomPages.findOneAndUpdate(req.body.page_id, req.params.board,

@ -9,7 +9,7 @@ const { News } = require(__dirname+'/../../db/')
module.exports = async (req, res, next) => {
const message = prepareMarkdown(req.body.message, false);
const { message: markdownNews } = await messageHandler(message, null, null, res.locals.permLevel);
const { message: markdownNews } = await messageHandler(message, null, null, res.locals.permissions);
const updated = await News.updateOne(req.body.news_id, req.body.title, message, markdownNews).then(r => r.matchedCount);

@ -19,13 +19,14 @@ todo: handle some more situations
- last activity date
- correct bump date when editing thread or last post in a thread
- allow for regular users (OP ONLY) and option for staff to disable in board settings
- different permission levels for historical posts when remarked up (or not, fuck that)
*/
const { previewReplies, strictFiltering } = config.get;
const { board, post } = res.locals;
//filters
if (res.locals.permLevel > 1) { //global staff bypass filters for edit
if (res.locals.permissions.get(BYPASS_FILTERS)) { //global staff bypass filters for edit
const globalSettings = config.get;
if (globalSettings && globalSettings.filters.length > 0 && globalSettings.filterMode > 0) {
let hitGlobalFilter = false
@ -82,11 +83,11 @@ todo: handle some more situations
messageHash = createHash('sha256').update(noQuoteMessage).digest('base64');
}
//new name, trip and cap
const { name, tripcode, capcode } = await nameHandler(req.body.name, res.locals.permLevel,
board.settings, board.owner, res.locals.user ? res.locals.user.username : null);
const { name, tripcode, capcode } = await nameHandler(req.body.name, res.locals.permissions,
board.settings, board.owner, board.staff, res.locals.user ? res.locals.user.username : null);
//new message and quotes
const nomarkup = prepareMarkdown(req.body.message, false);
const { message, quotes, crossquotes } = await messageHandler(nomarkup, req.body.board, post.thread, res.locals.permLevel);
const { message, quotes, crossquotes } = await messageHandler(nomarkup, req.body.board, post.thread, res.locals.permissions);
//todo: email and subject (probably dont need any transformation since staff bypass limits on forceanon, and it doesnt have to account for sage/etc
//intersection/difference of quotes sets for linking and unlinking

@ -0,0 +1,85 @@
'use strict';
const { Roles, Accounts } = require(__dirname+'/../../db/')
, { Binary } = require(__dirname+'/../../db/db.js')
, redis = require(__dirname+'/../../redis.js')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, roleManager = require(__dirname+'/../../helpers/rolemanager.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, Permission = require(__dirname+'/../../helpers/permission.js');
module.exports = async (req, res, next) => {
let rolePermissions = new Permission(res.locals.editingRole.permissions);
rolePermissions.set(Permissions.VIEW_RAW_IP, (req.body.VIEW_RAW_IP != null));
rolePermissions.set(Permissions.CREATE_BOARD, (req.body.CREATE_BOARD != null));
rolePermissions.set(Permissions.CREATE_ACCOUNT, (req.body.CREATE_ACCOUNT != null));
rolePermissions.set(Permissions.BYPASS_BANS, (req.body.BYPASS_BANS != null));
rolePermissions.set(Permissions.BYPASS_SPAMCHECK, (req.body.BYPASS_SPAMCHECK != null));
rolePermissions.set(Permissions.BYPASS_RATELIMITS, (req.body.BYPASS_RATELIMITS != null));
rolePermissions.set(Permissions.BYPASS_FILTERS, (req.body.BYPASS_FILTERS != null));
rolePermissions.set(Permissions.MANAGE_GLOBAL_GENERAL, (req.body.MANAGE_GLOBAL_GENERAL != null));
rolePermissions.set(Permissions.MANAGE_GLOBAL_BANS, (req.body.MANAGE_GLOBAL_BANS != null));
rolePermissions.set(Permissions.MANAGE_GLOBAL_LOGS, (req.body.MANAGE_GLOBAL_LOGS != null));
rolePermissions.set(Permissions.MANAGE_GLOBAL_NEWS, (req.body.MANAGE_GLOBAL_NEWS != null));
rolePermissions.set(Permissions.MANAGE_GLOBAL_BOARDS, (req.body.MANAGE_GLOBAL_BOARDS != null));
rolePermissions.set(Permissions.MANAGE_GLOBAL_SETTINGS, (req.body.MANAGE_GLOBAL_SETTINGS != null));
rolePermissions.set(Permissions.MANAGE_BOARD_GENERAL, (req.body.MANAGE_BOARD_GENERAL != null));
rolePermissions.set(Permissions.MANAGE_BOARD_OWNER, (req.body.MANAGE_BOARD_OWNER != null));
rolePermissions.set(Permissions.MANAGE_BOARD_BANS, (req.body.MANAGE_BOARD_BANS != null));
rolePermissions.set(Permissions.MANAGE_BOARD_LOGS, (req.body.MANAGE_BOARD_LOGS != null));
rolePermissions.set(Permissions.MANAGE_BOARD_SETTINGS, (req.body.MANAGE_BOARD_SETTINGS != null));
rolePermissions.set(Permissions.MANAGE_BOARD_CUSTOMISATION, (req.body.MANAGE_BOARD_CUSTOMISATION != null));
rolePermissions.set(Permissions.MANAGE_BOARD_STAFF, (req.body.MANAGE_BOARD_STAFF != null));
rolePermissions.set(Permissions.USE_MARKDOWN_PINKTEXT, (req.body.USE_MARKDOWN_PINKTEXT != null));
rolePermissions.set(Permissions.USE_MARKDOWN_GREENTEXT, (req.body.USE_MARKDOWN_GREENTEXT != null));
rolePermissions.set(Permissions.USE_MARKDOWN_BOLD, (req.body.USE_MARKDOWN_BOLD != null));
rolePermissions.set(Permissions.USE_MARKDOWN_UNDERLINE, (req.body.USE_MARKDOWN_UNDERLINE != null));
rolePermissions.set(Permissions.USE_MARKDOWN_STRIKETHROUGH, (req.body.USE_MARKDOWN_STRIKETHROUGH != null));
rolePermissions.set(Permissions.USE_MARKDOWN_TITLE, (req.body.USE_MARKDOWN_TITLE != null));
rolePermissions.set(Permissions.USE_MARKDOWN_ITALIC, (req.body.USE_MARKDOWN_ITALIC != null));
rolePermissions.set(Permissions.USE_MARKDOWN_SPOILER, (req.body.USE_MARKDOWN_SPOILER != null));
rolePermissions.set(Permissions.USE_MARKDOWN_MONO, (req.body.USE_MARKDOWN_MONO != null));
rolePermissions.set(Permissions.USE_MARKDOWN_CODE, (req.body.USE_MARKDOWN_CODE != null));
rolePermissions.set(Permissions.USE_MARKDOWN_DETECTED, (req.body.USE_MARKDOWN_DETECTED != null));
rolePermissions.set(Permissions.USE_MARKDOWN_LINK, (req.body.USE_MARKDOWN_LINK != null));
rolePermissions.set(Permissions.USE_MARKDOWN_DICE, (req.body.USE_MARKDOWN_DICE != null));
rolePermissions.set(Permissions.USE_MARKDOWN_FORTUNE, (req.body.USE_MARKDOWN_FORTUNE != null));
if (res.locals.permissions.get(Permissions.ROOT)) {
rolePermissions.set(Permissions.MANAGE_GLOBAL_ACCOUNTS, (req.body.MANAGE_GLOBAL_ACCOUNTS != null))
rolePermissions.set(Permissions.MANAGE_GLOBAL_ROLES, (req.body.MANAGE_GLOBAL_ROLES != null))
rolePermissions.set(Permissions.ROOT, (req.body.ROOT != null));
}
rolePermissions.applyInheritance();
const existingRoleName = roleManager.roleNameMap[rolePermissions.base64]
if (existingRoleName) {
return dynamicResponse(req, res, 409, 'message', {
'title': 'Conflict',
'error': `Another role already exists with those same permissions: "${existingRoleName}"`,
'redirect': req.headers.referer || `/globalmanage/roles.html`,
});
}
const updated = await Roles.updateOne(req.body.roleid, rolePermissions).then(r => r.matchedCount);
if (updated === 0) {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'error': 'Role does not exist',
'redirect': req.headers.referer || `/globalmanage/roles.html`,
});
}
const oldPermissions = new Permission(res.locals.editingRole.permissions);
await Accounts.setNewRolePermissions(oldPermissions, rolePermissions)
redis.redisPublisher.publish('roles', null);
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',
'message': 'Edited role',
'redirect': `/globalmanage/editrole/${req.body.roleid}.html`,
});
}

@ -0,0 +1,40 @@
'use strict';
const { Boards } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, Permission = require(__dirname+'/../../helpers/permission.js');
module.exports = async (req, res, next) => {
let updatingPermissions = new Permission(res.locals.board.staff[req.body.username].permissions);
//maybe these can be changed
//updatingPermissions.set(Permissions.MANAGE_BOARD_GENERSL, (req.body.MANAGE_BOARD_GENERAL != null))
updatingPermissions.set(Permissions.MANAGE_BOARD_BANS, (req.body.MANAGE_BOARD_BANS != null))
updatingPermissions.set(Permissions.MANAGE_BOARD_LOGS, (req.body.MANAGE_BOARD_LOGS != null))
updatingPermissions.set(Permissions.MANAGE_BOARD_SETTINGS, (req.body.MANAGE_BOARD_SETTINGS != null))
updatingPermissions.set(Permissions.MANAGE_BOARD_CUSTOMISATION, (req.body.MANAGE_BOARD_CUSTOMISATION != null))
if (res.locals.permissions.get(Permissions.MANAGE_BOARD_OWNER)) {
//be careful giving others manage_board_owner!
updatingPermissions.set(Permissions.MANAGE_BOARD_OWNER, (req.body.MANAGE_BOARD_OWNER != null))
updatingPermissions.set(Permissions.MANAGE_BOARD_STAFF, (req.body.MANAGE_BOARD_STAFF != null))
}
const updated = await Boards.setStaffPermissions(req.params.board, req.body.username, updatingPermissions).then(r => r.matchedCount);
if (updated === 0) {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'errors': 'Staff does not exist',
'redirect': req.headers.referer || `/${req.params.board}/manage/staff.html`,
});
}
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',
'message': 'Edited staff',
'redirect': `/${req.params.board}/manage/editstaff/${req.body.username}.html`,
});
}

@ -23,6 +23,7 @@ const path = require('path')
, deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js')
, fixGifs = require(__dirname+'/../../helpers/files/fixgifs.js')
, timeUtils = require(__dirname+'/../../helpers/timeutils.js')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, deletePosts = require(__dirname+'/deletepost.js')
, spamCheck = require(__dirname+'/../../helpers/checks/spamcheck.js')
, config = require(__dirname+'/../../config.js')
@ -34,7 +35,7 @@ const path = require('path')
module.exports = async (req, res, next) => {
const { checkRealMimeTypes, thumbSize, thumbExtension, videoThumbPercentage,
strictFiltering, animatedGifThumbnails, audioThumbnails, ipHashPermLevel } = config.get;
strictFiltering, animatedGifThumbnails, audioThumbnails, dontStoreRawIps } = config.get;
//spam/flood check
const flood = await spamCheck(req, res);
@ -51,12 +52,13 @@ module.exports = async (req, res, next) => {
let redirect = `/${req.params.board}/`
let salt = null;
let thread = null;
const isStaffOrGlobal = res.locals.permissions.hasAny(Permissions.MANAGE_GLOBAL_GENERAL, Permissions.MANAGE_BOARD_GENERAL);
const { filterBanDuration, filterMode, filters, blockedCountries, threadLimit, ids, userPostSpoiler,
lockReset, captchaReset, pphTrigger, tphTrigger, tphTriggerAction, pphTriggerAction,
sageOnlyEmail, forceAnon, replyLimit, disableReplySubject,
captchaMode, lockMode, allowedFileTypes, customFlags, geoFlags, fileR9KMode, messageR9KMode } = res.locals.board.settings;
if (res.locals.permLevel >= 4
&& res.locals.country
if (!isStaffOrGlobal
&& res.locals.country //permission for this or nah?
&& blockedCountries.includes(res.locals.country.code)) {
await deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 403, 'message', {
@ -66,7 +68,7 @@ module.exports = async (req, res, next) => {
});
}
if ((lockMode === 2 || (lockMode === 1 && !req.body.thread)) //if board lock, or thread lock and its a new thread
&& res.locals.permLevel >= 4) { //and not staff
&& !isStaffOrGlobal) { //and not staff
await deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
@ -86,7 +88,7 @@ module.exports = async (req, res, next) => {
}
salt = thread.salt;
redirect += `thread/${req.body.thread}.html`
if (thread.locked && res.locals.permLevel >= 4) {
if (thread.locked && !isStaffOrGlobal) {
await deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
@ -104,7 +106,7 @@ module.exports = async (req, res, next) => {
}
}
//filters
if (res.locals.permLevel > 1) { //global staff bypass filters
if (!res.locals.permissions.get(Permissions.BYPASS_FILTERS)) {
const { filters: globalFilters, filterMode: globalFilterMode,
filterBanDuration: globalFilterBanDuration } = config.get;
let hitGlobalFilter = false
@ -123,8 +125,9 @@ ${res.locals.numFiles > 0 ? req.files.file.map(f => f.name+'|'+(f.phash || '')).
if (globalFilters && globalFilters.length > 0 && globalFilterMode > 0) {
hitGlobalFilter = globalFilters.some(filter => { return allContents.includes(filter.toLowerCase()) });
}
//board-specific filters (doesnt use strict filtering)
if (!hitGlobalFilter && res.locals.permLevel >= 4 && filterMode > 0 && filters && filters.length > 0) {
//board-specific filters
if (!hitGlobalFilter && !res.locals.permissions.get(Permissions.MANAGE_BOARD_GENERAL)
&& filterMode > 0 && filters && filters.length > 0) {
const localFilterContents = res.locals.board.settings.strictFiltering ? allContents : concatContents;
hitLocalFilter = filters.some(filter => { return localFilterContents.includes(filter.toLowerCase()) });
}
@ -165,7 +168,6 @@ ${res.locals.numFiles > 0 ? req.files.file.map(f => f.name+'|'+(f.phash || '')).
});
}
}
}
//for r9k messages. usually i wouldnt process these if its not enabled e.g. flags and IDs but in this case I think its necessary
@ -173,7 +175,7 @@ ${res.locals.numFiles > 0 ? req.files.file.map(f => f.name+'|'+(f.phash || '')).
if (req.body.message && req.body.message.length > 0) {
const noQuoteMessage = req.body.message.replace(/>>\d+/g, '').replace(/>>>\/\w+(\/\d*)?/gm, '').trim();
messageHash = createHash('sha256').update(noQuoteMessage).digest('base64');
if (res.locals.permLevel >= 4 && (req.body.thread && messageR9KMode === 1) || messageR9KMode === 2) {
if ((req.body.thread && messageR9KMode === 1) || messageR9KMode === 2) {
const postWithExistingMessage = await Posts.checkExistingMessage(res.locals.board._id, (messageR9KMode === 2 ? null : req.body.thread), messageHash);
if (postWithExistingMessage != null) {
await deleteTempFiles(req).catch(e => console.error);
@ -191,7 +193,7 @@ ${res.locals.numFiles > 0 ? req.files.file.map(f => f.name+'|'+(f.phash || '')).
if (res.locals.numFiles > 0) {
//unique files check
if (res.locals.permLevel >= 4 && (req.body.thread && fileR9KMode === 1) || fileR9KMode === 2) {
if ((req.body.thread && fileR9KMode === 1) || fileR9KMode === 2) {
const filesHashes = req.files.file.map(f => f.sha256);
const postWithExistingFiles = await Posts.checkExistingFiles(res.locals.board._id, (fileR9KMode === 2 ? null : req.body.thread), filesHashes);
if (postWithExistingFiles != null) {
@ -242,7 +244,7 @@ ${res.locals.numFiles > 0 ? req.files.file.map(f => f.name+'|'+(f.phash || '')).
//get metadata
let processedFile = {
filename: file.filename,
spoiler: (res.locals.permLevel >= 4 || userPostSpoiler) && req.body.spoiler && req.body.spoiler.includes(file.sha256),
spoiler: (!isStaffOrGlobal || userPostSpoiler) && req.body.spoiler && req.body.spoiler.includes(file.sha256),
hash: file.sha256,
originalFilename: req.body.strip_filename && req.body.strip_filename.includes(file.sha256) ? file.filename : file.name,
mimetype: file.mimetype,
@ -418,19 +420,19 @@ ${res.locals.numFiles > 0 ? req.files.file.map(f => f.name+'|'+(f.phash || '')).
}
//spoiler files only if board settings allow
const spoiler = (res.locals.permLevel >= 4 || userPostSpoiler) && req.body.spoiler_all ? true : false;
const spoiler = (!isStaffOrGlobal || userPostSpoiler) && req.body.spoiler_all ? true : false;
//forceanon and sageonlyemail only allow sage email
let email = (res.locals.permLevel < 4 || (!forceAnon && !sageOnlyEmail) || req.body.email === 'sage') ? req.body.email : null;
let email = (!isStaffOrGlobal || (!forceAnon && !sageOnlyEmail) || req.body.email === 'sage') ? req.body.email : null;
//disablereplysubject
let subject = (res.locals.permLevel >= 4 && req.body.thread && disableReplySubject) ? null : req.body.subject;
let subject = (!isStaffOrGlobal && req.body.thread && disableReplySubject) ? null : req.body.subject;
//get name, trip and cap
const { name, tripcode, capcode } = await nameHandler(req.body.name, res.locals.permLevel,
res.locals.board.settings, res.locals.board.owner, res.locals.user ? res.locals.user.username : null);
const { name, tripcode, capcode } = await nameHandler(req.body.name, res.locals.permissions,
res.locals.board.settings, res.locals.board.owner, res.locals.board.staff, res.locals.user ? res.locals.user.username : null);
//get message, quotes and crossquote array
const nomarkup = prepareMarkdown(req.body.message, true);
const { message, quotes, crossquotes } = await messageHandler(nomarkup, req.params.board, req.body.thread, res.locals.permLevel);
const { message, quotes, crossquotes } = await messageHandler(nomarkup, req.params.board, req.body.thread, res.locals.permissions);
//build post data for db. for some reason all the property names are lower case :^)
const now = Date.now()
@ -609,16 +611,15 @@ ${res.locals.numFiles > 0 ? req.files.file.map(f => f.name+'|'+(f.phash || '')).
'cyclic': data.cyclic,
}
if (data.thread) {
//dont emit thread to this socket, because the room onyl exists when the thread is open
//dont emit thread to this socket, because the room only exists when the thread is open
Socketio.emitRoom(`${res.locals.board._id}-${data.thread}`, 'newPost', projectedPost);
}
const { raw, cloak } = data.ip;
//but emit it to manage pages because they need to get all posts through socket including thread
Socketio.emitRoom('globalmanage-recent-hashed', 'newPost', { ...projectedPost, ip: { cloak, raw: null } });
Socketio.emitRoom(`${res.locals.board._id}-manage-recent-hashed`, 'newPost', { ...projectedPost, ip: { cloak, raw: null } });
if (ipHashPermLevel > -1) {
//small optimisation for boards where this is manually set to -1 for privacy, no need to emit to rooms that cant be accessed
//even if they are empty it will create extra communication noise in redis, socket adapter, etc.
if (!dontStoreRawIps) {
//no need to emit to these rooms if raw IPs are not stored
Socketio.emitRoom('globalmanage-recent-raw', 'newPost', { ...projectedPost, ip: { cloak, raw } });
Socketio.emitRoom(`${res.locals.board._id}-manage-recent-raw`, 'newPost', { ...projectedPost, ip: { cloak, raw } });
}

@ -1,7 +1,8 @@
'use strict';
const { Accounts } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js');
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, roleManager = require(__dirname+'/../../helpers/rolemanager.js');
module.exports = async (req, res, next) => {
@ -21,7 +22,7 @@ module.exports = async (req, res, next) => {
}
// add account to db. password is hashed in db model func for easier tests
await Accounts.insertOne(original, username, password, 4);
await Accounts.insertOne(original, username, password, roleManager.roles.ANON);
return res.redirect('/login.html');

@ -5,7 +5,7 @@ const { Boards, Accounts } = require(__dirname+'/../../db/')
module.exports = async (req, res, next) => {
const moderatesBoard = res.locals.user.modBoards.includes(req.body.board);
const moderatesBoard = res.locals.user.staffBoards.includes(req.body.board);
const ownsBoard = res.locals.user.ownedBoards.includes(req.body.board);
if (!ownsBoard && !moderatesBoard) {
return dynamicResponse(req, res, 400, 'message', {
@ -19,17 +19,18 @@ module.exports = async (req, res, next) => {
await Promise.all([
Accounts.removeOwnedBoard(res.locals.user.username, req.body.board),
Boards.setOwner(req.body.board, null),
Boards.removeStaff(req.body.board, [res.locals.user.username]),
]);
} else if (moderatesBoard) {
await Promise.all([
Boards.removeModerator(req.body.board, res.locals.user.username),
Accounts.removeModBoard([res.locals.user.username], req.body.board),
Boards.removeStaff(req.body.board, [res.locals.user.username]),
Accounts.removeStaffBoard([res.locals.user.username], req.body.board),
]);
}
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',
'message': `Resigned from ${ownsBoard ? 'owner' : 'moderator'} position on /${req.body.board}/`,
'message': `Resigned from ${ownsBoard ? 'owner' : 'staff'} position on /${req.body.board}/`,
'redirect': `/account.html`
});

@ -1,35 +1,37 @@
'use strict';
const { Boards, Accounts } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js');
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, roleManager = require(__dirname+'/../../helpers/rolemanager.js');
module.exports = async (req, res, next) => {
const newOwner = await Accounts.findOne(req.body.username.toLowerCase());
const newOwner = res.locals.newOwner;
if (!newOwner) {
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': 'Cannot transfer to account that does not exist',
'redirect': `/${req.params.board}/manage/settings.html`
});
}
//remove current owner
await Promise.all([
Accounts.removeOwnedBoard(res.locals.board.owner, req.params.board),
Boards.removeStaff(req.params.board, [res.locals.board.owner]),
]);
//remove owned board from current account
await Accounts.removeOwnedBoard(res.locals.board.owner, req.params.board)
//set new owner in locals
res.locals.board.owner = newOwner._id;
//remove new owner as moderator if they were one
if (res.locals.board.settings.moderators.includes(newOwner._id)) {
await Boards.removeModerator(req.params.board, res.locals.user.username)
await Accounts.removeModBoard([newOwner._id], req.params.board)
if (res.locals.board.staff[newOwner._id] != null) {
//if already a staff, just change their permission instead of removing+adding back
await Promise.all([
Boards.setStaffPermissions(req.params.board, newOwner._id, roleManager.roles.BOARD_OWNER, true),
Accounts.removeStaffBoard([newOwner._id], req.params.board),
Accounts.addOwnedBoard(newOwner._id, req.params.board),
]);
} else {
//otherwise add them as a new staff+owner
await Promise.all([
Boards.addStaff(req.params.board, newOwner._id, roleManager.roles.BOARD_OWNER, true),
Accounts.addOwnedBoard(newOwner._id, req.params.board),
]);
}
//set owner in memory and in db
res.locals.board.owner = newOwner._id;
await Boards.setOwner(req.params.board, res.locals.board.owner);
//add ownership to new owner account
await Accounts.addOwnedBoard(newOwner._id, req.params.board);
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',
'message': 'Transferred ownership',

@ -1,20 +1,23 @@
'use strict';
const Posts = require(__dirname+'/../../db/posts.js');
const { Posts, Boards } = require(__dirname+'/../../db/')
, Permissions = require(__dirname+'/../../helpers/permissions.js')
, Permission = require(__dirname+'/../../helpers/permission.js');
module.exports = async (req, res, next) => {
let boardReportCountMap = {}; //map of board to open report count
let globalReportCount = 0; //number of open global reports
let boardPermissions; //map of board perms
let userBoards = [];
let boardReportCounts;
try {
const userBoards = res.locals.user.ownedBoards.concat(res.locals.user.modBoards);
([boardReportCounts, globalReportCount] = await Promise.all([
//if user owns or mods any boards, get the open report count for them
userBoards = res.locals.user.ownedBoards.concat(res.locals.user.staffBoards);
([boardReportCounts, boardPermissions, globalReportCount] = await Promise.all([
userBoards.length > 0 ? Posts.getBoardReportCounts(userBoards) : [],
//if user is global staff get the open global report count
res.locals.user.authLevel <= 1 ? Posts.getGlobalReportsCount() : 0
res.locals.user.staffBoards.length > 0 ? Boards.getStaffPerms(res.locals.user.staffBoards, res.locals.user.username) : null,
res.locals.permissions.get(Permissions.MANAGE_GLOBAL_GENERAL) ? Posts.getGlobalReportsCount() : 0,
]));
} catch (err) {
return next(err)
@ -28,11 +31,27 @@ module.exports = async (req, res, next) => {
}, boardReportCountMap);
}
if (boardPermissions) {
//calcperms isnt multi-board (but neither is this, really) its alright until then
boardPermissions = boardPermissions.reduce((acc, bs) => {
acc[bs._id] = new Permission(bs.staff[res.locals.user.username].permissions.toString('base64'));
if (acc[bs._id].get(Permissions.MANAGE_BOARD_OWNER)) {
acc[bs._id].setAll([
Permissions.MANAGE_BOARD_GENERAL, Permissions.MANAGE_BOARD_BANS, Permissions.MANAGE_BOARD_LOGS,
Permissions.MANAGE_BOARD_SETTINGS, Permissions.MANAGE_BOARD_CUSTOMISATION, Permissions.MANAGE_BOARD_STAFF,
]);
}
return acc;
}, {});
}
res
.set('Cache-Control', 'private, max-age=5')
.render('account', {
csrf: req.csrfToken(),
user: res.locals.user,
permissions: res.locals.permissions,
boardPermissions,
boardReportCountMap,
globalReportCount,
});

@ -1,6 +1,7 @@
'use strict';
const { Accounts } = require(__dirname+'/../../../db/')
, roleManager = require(__dirname+'/../../../helpers/rolemanager.js')
, pageQueryConverter = require(__dirname+'/../../../helpers/pagequeryconverter.js')
, limit = 20;
@ -20,7 +21,7 @@ module.exports = async (req, res, next) => {
'ownedBoards': uri
},
{
'modBoards': uri
'staffBoards': uri
},
];
}
@ -40,12 +41,15 @@ module.exports = async (req, res, next) => {
.set('Cache-Control', 'private, max-age=5')
.render('globalmanageaccounts', {
csrf: req.csrfToken(),
permissions: res.locals.permissions,
user: res.locals.user,
queryString,
username,
uri,
accounts,
page,
maxPage,
roleNameMap: roleManager.roleNameMap,
});
}

@ -1,6 +1,7 @@
'use strict';
const { Bans } = require(__dirname+'/../../../db/');
const { Bans } = require(__dirname+'/../../../db/')
, Permissions = require(__dirname+'/../../../helpers/permissions.js');
module.exports = async (req, res, next) => {
@ -15,6 +16,8 @@ module.exports = async (req, res, next) => {
.set('Cache-Control', 'private, max-age=5')
.render('globalmanagebans', {
csrf: req.csrfToken(),
permissions: res.locals.permissions,
viewRawIp: res.locals.permissions.get(Permissions.VIEW_RAW_IP),
bans,
});

@ -68,6 +68,7 @@ module.exports = async (req, res, next) => {
} else {
res.render('globalmanageboardlist', {
localBoards,
permissions: res.locals.permissions,
page,
maxPage,
query: req.query,

@ -0,0 +1,29 @@
'use strict';
const { Accounts } = require(__dirname+'/../../../db/')
, roleManager = require(__dirname+'/../../../helpers/rolemanager.js')
, Permission = require(__dirname+'/../../../helpers/permission.js');
module.exports = async (req, res, next) => {
const editingAccount = await Accounts.findOne(req.params.accountusername);
if (editingAccount == null) {
//account does not exist
return next();
}
const accountPermissions = new Permission(editingAccount.permissions);
//accountPermissions.applyInheritance();
res
.set('Cache-Control', 'private, max-age=5')
.render('editaccount', {
csrf: req.csrfToken(),
board: res.locals.board,
accountUsername: req.params.accountusername,
accountPermissions,
roles: roleManager.roles,
});
}

@ -19,6 +19,7 @@ module.exports = async (req, res, next) => {
.set('Cache-Control', 'private, max-age=5')
.render('editnews', {
csrf: req.csrfToken(),
permissions: res.locals.permissions,
news,
});

@ -0,0 +1,25 @@
'use strict';
const { Roles } = require(__dirname+'/../../../db/')
, roleManager = require(__dirname+'/../../../helpers/rolemanager.js')
, Permission = require(__dirname+'/../../../helpers/permission.js');
module.exports = async (req, res, next) => {
const role = await Roles.findOne(req.params.roleid);
if (role == null) {
//role does not exist
return next();
}
res
.set('Cache-Control', 'private, max-age=5')
.render('editrole', {
csrf: req.csrfToken(),
role,
rolePermissions: new Permission(role.permissions),
roleNameMap: roleManager.roleNameMap,
});
}

@ -9,5 +9,8 @@ module.exports = {
globalManageNews: require(__dirname+'/news.js'),
globalManageAccounts: require(__dirname+'/accounts.js'),
globalManageSettings: require(__dirname+'/settings.js'),
globalManageRoles: require(__dirname+'/roles.js'),
editNews: require(__dirname+'/editnews.js'),
editAccount: require(__dirname+'/editaccount.js'),
editRole: require(__dirname+'/editrole.js'),
}

@ -2,6 +2,7 @@
const { Modlogs } = require(__dirname+'/../../../db/')
, pageQueryConverter = require(__dirname+'/../../../helpers/pagequeryconverter.js')
, Permissions = require(__dirname+'/../../../helpers/permissions.js')
, decodeQueryIP = require(__dirname+'/../../../helpers/decodequeryip.js')
, { isIP } = require('net')
, limit = 50;
@ -19,7 +20,7 @@ module.exports = async (req, res, next) => {
if (uri && !Array.isArray(uri)) {
filter.board = uri;
}
const ipMatch = decodeQueryIP(req.query, res.locals.permLevel);
const ipMatch = decodeQueryIP(req.query, res.locals.permissions);
if (ipMatch != null) {
if (isIP(ipMatch)) {
filter['ip.raw'] = ipMatch;
@ -43,9 +44,12 @@ module.exports = async (req, res, next) => {
.set('Cache-Control', 'private, max-age=5')
.render('globalmanagelogs', {
csrf: req.csrfToken(),
permissions: res.locals.permissions,
queryString,
username,
uri,
permissions: res.locals.permissions,
viewRawIp: res.locals.permissions.get(Permissions.VIEW_RAW_IP),
ip: ipMatch ? req.query.ip : null,
logs,
page,

@ -15,6 +15,7 @@ module.exports = async (req, res, next) => {
.set('Cache-Control', 'private, max-age=5')
.render('globalmanagenews', {
csrf: req.csrfToken(),
permissions: res.locals.permissions,
news,
});

@ -1,18 +1,21 @@
'use strict';
const { Posts } = require(__dirname+'/../../../db/')
, config = require(__dirname+'/../../../config.js')
, pageQueryConverter = require(__dirname+'/../../../helpers/pagequeryconverter.js')
, Permissions = require(__dirname+'/../../../helpers/permissions.js')
, decodeQueryIP = require(__dirname+'/../../../helpers/decodequeryip.js')
, limit = 20;
module.exports = async (req, res, next) => {
const { dontStoreRawIps } = config.get;
const { page, offset, queryString } = pageQueryConverter(req.query, limit);
let ipMatch = decodeQueryIP(req.query, res.locals.permLevel);
let ipMatch = decodeQueryIP(req.query, res.locals.permissions);
let posts;
try {
posts = await Posts.getBoardRecent(offset, limit, ipMatch, null, res.locals.permLevel);
posts = await Posts.getBoardRecent(offset, limit, ipMatch, null, res.locals.permissions);
} catch (err) {
return next(err)
}
@ -25,6 +28,8 @@ module.exports = async (req, res, next) => {
res.render('globalmanagerecent', {
csrf: req.csrfToken(),
posts,
permissions: res.locals.permissions,
viewRawIp: res.locals.permissions.get(Permissions.VIEW_RAW_IP) && !dontStoreRawIps,
page,
ip: ipMatch ? req.query.ip : null,
queryString,

@ -2,17 +2,18 @@
const { Posts } = require(__dirname+'/../../../db/')
, pageQueryConverter = require(__dirname+'/../../../helpers/pagequeryconverter.js')
, Permissions = require(__dirname+'/../../../helpers/permissions.js')
, decodeQueryIP = require(__dirname+'/../../../helpers/decodequeryip.js')
, limit = 20;
module.exports = async (req, res, next) => {
const { page, offset, queryString } = pageQueryConverter(req.query, limit);
let ipMatch = decodeQueryIP(req.query, res.locals.permLevel);
let ipMatch = decodeQueryIP(req.query, res.locals.permissions);
let reports;
try {
reports = await Posts.getGlobalReports(offset, limit, ipMatch, res.locals.permLevel);
reports = await Posts.getGlobalReports(offset, limit, ipMatch, res.locals.permissions);
} catch (err) {
return next(err)
}
@ -30,6 +31,8 @@ module.exports = async (req, res, next) => {
res.render('globalmanagereports', {
csrf: req.csrfToken(),
reports,
permissions: res.locals.permissions,
viewRawIp: res.locals.permissions.get(Permissions.VIEW_RAW_IP),
page,
ip: ipMatch ? req.query.ip : null,
queryString,

@ -0,0 +1,19 @@
'use strict';
const { Roles } = require(__dirname+'/../../../db/')
, roleManager = require(__dirname+'/../../../helpers/rolemanager.js');
module.exports = async (req, res, next) => {
const allRoles = await Roles.find();
res
.set('Cache-Control', 'private, max-age=5')
.render('globalmanageroles', {
csrf: req.csrfToken(),
permissions: res.locals.permissions,
allRoles,
roleNameMap: roleManager.roleNameMap,
});
}

@ -12,6 +12,7 @@ module.exports = async (req, res, next) => {
.render('globalmanagesettings', {
csrf: req.csrfToken(),
settings: config.get,
permissions: res.locals.permissions,
countryNamesMap,
countryCodes,
themes,

@ -5,6 +5,7 @@ module.exports = {
blockBypass: require(__dirname+'/blockbypass.js'),
register: require(__dirname+'/register.js'),
account: require(__dirname+'/account.js'),
myPermissions: require(__dirname+'/mypermissions.js'),
home: require(__dirname+'/home.js'),
login: require(__dirname+'/login.js'),
create: require(__dirname+'/create.js'),

@ -1,6 +1,7 @@
'use strict';
const Bans = require(__dirname+'/../../../db/bans.js');
const Bans = require(__dirname+'/../../../db/bans.js')
, Permissions = require(__dirname+'/../../../helpers/permissions.js');
module.exports = async (req, res, next) => {
@ -15,6 +16,8 @@ module.exports = async (req, res, next) => {
.set('Cache-Control', 'private, max-age=5')
.render('managebans', {
csrf: req.csrfToken(),
permissions: res.locals.permissions,
viewRawIp: res.locals.permissions.get(Permissions.VIEW_RAW_IP),
bans,
});

@ -0,0 +1,24 @@
'use strict';
const Permission = require(__dirname+'/../../../helpers/permission.js');
module.exports = async (req, res, next) => {
let staffData = res.locals.board.staff[req.params.staffusername];
if (staffData == null) {
//staff does not exist
return next();
}
res
// .set('Cache-Control', 'private, max-age=5')
.render('editstaff', {
csrf: req.csrfToken(),
board: res.locals.board,
permissions: res.locals.permissions,
staffUsername: req.params.staffusername,
staffPermissions: new Permission(staffData.permissions),
});
}

@ -11,5 +11,8 @@ module.exports = {
manageCatalog: require(__dirname+'/catalog.js'),
manageThread: require(__dirname+'/thread.js'),
manageCustomPages: require(__dirname+'/custompages.js'),
manageMyPermissions: require(__dirname+'/mypermissions.js'),
editCustomPage: require(__dirname+'/editcustompage.js'),
manageStaff: require(__dirname+'/staff.js'),
editStaff: require(__dirname+'/editstaff.js'),
}

@ -2,6 +2,7 @@
const { Modlogs } = require(__dirname+'/../../../db/')
, pageQueryConverter = require(__dirname+'/../../../helpers/pagequeryconverter.js')
, Permissions = require(__dirname+'/../../../helpers/permissions.js')
// , decodeQueryIP = require(__dirname+'/../../../helpers/decodequeryip.js')
, limit = 50;
@ -40,6 +41,8 @@ module.exports = async (req, res, next) => {
queryString,
username,
uri,
permissions: res.locals.permissions,
viewRawIp: res.locals.permissions.get(Permissions.VIEW_RAW_IP),
//posterid here
logs,
page,

@ -0,0 +1,13 @@
'use strict';
module.exports = async (req, res, next) => {
res
.set('Cache-Control', 'private, max-age=5')
.render('managemypermissions', {
user: res.locals.user,
board: res.locals.board,
permissions: res.locals.permissions,
});
}

@ -0,0 +1,11 @@
'use strict';
module.exports = async (req, res, next) => {
res
.set('Cache-Control', 'private, max-age=5')
.render('managemypermissions', {
permissions: res.locals.permissions,
});
}

@ -1,25 +1,28 @@
'use strict';
const { Posts } = require(__dirname+'/../../../db/')
, config = require(__dirname+'/../../../config.js')
, decodeQueryIP = require(__dirname+'/../../../helpers/decodequeryip.js')
, Permissions = require(__dirname+'/../../../helpers/permissions.js')
, pageQueryConverter = require(__dirname+'/../../../helpers/pagequeryconverter.js')
, limit = 20;
module.exports = async (req, res, next) => {
const { dontStoreRawIps } = config.get;
const { page, offset, queryString } = pageQueryConverter(req.query, limit);
let ip = decodeQueryIP(req.query, res.locals.permLevel);
let ip = decodeQueryIP(req.query, res.locals.permissions);
const postId = typeof req.query.postid === 'string' ? req.query.postid : null;
if (postId && +postId === parseInt(postId) && Number.isSafeInteger(+postId)) {
const fetchedPost = await Posts.getPost(req.params.board, +postId, true);
if (fetchedPost) {
ip = decodeQueryIP({ ip: fetchedPost.ip.cloak }, res.locals.permlevel);
ip = decodeQueryIP({ ip: fetchedPost.ip.cloak }, res.locals.permissions);
}
}
let posts;
try {
posts = await Posts.getBoardRecent(offset, limit, ip, req.params.board, res.locals.permLevel);
posts = await Posts.getBoardRecent(offset, limit, ip, req.params.board, res.locals.permissions);
} catch (err) {
return next(err)
}
@ -32,6 +35,8 @@ module.exports = async (req, res, next) => {
res.render('managerecent', {
csrf: req.csrfToken(),
posts,
permissions: res.locals.permissions,
viewRawIp: res.locals.permissions.get(Permissions.VIEW_RAW_IP) && !dontStoreRawIps,
page,
postId,
queryIp: ip ? req.query.ip : null,

@ -1,12 +1,13 @@
'use strict';
const Posts = require(__dirname+'/../../../db/posts.js');
const Posts = require(__dirname+'/../../../db/posts.js')
, Permissions = require(__dirname+'/../../../helpers/permissions.js');
module.exports = async (req, res, next) => {
let reports;
try {
reports = await Posts.getReports(req.params.board, res.locals.permLevel);
reports = await Posts.getReports(req.params.board, res.locals.permissions);
} catch (err) {
return next(err)
}
@ -21,6 +22,8 @@ module.exports = async (req, res, next) => {
res.render('managereports', {
csrf: req.csrfToken(),
reports,
permissions: res.locals.permissions,
viewRawIp: res.locals.permissions.get(Permissions.VIEW_RAW_IP),
});
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save