ref #377, the fun begins

still a few things to do, see TODO (heh)
mostly working how i imagined, with a few quirks.
pretty happy with it.
merge-requests/341/head
Thomas Lynch 2 years ago
parent 0b01673e06
commit d1f9c78258
  1. 13
      CHANGELOG.md
  2. 59
      TODO
  3. 34
      configs/template.js.example
  4. 91
      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. 40
      controllers/forms/deletestaff.js
  13. 47
      controllers/forms/editaccount.js
  14. 2
      controllers/forms/editcustompage.js
  15. 3
      controllers/forms/editpost.js
  16. 45
      controllers/forms/editstaff.js
  17. 6
      controllers/forms/globalactions.js
  18. 23
      controllers/forms/globalsettings.js
  19. 6
      controllers/forms/index.js
  20. 7
      controllers/forms/register.js
  21. 4
      controllers/forms/transfer.js
  22. 84
      controllers/pages.js
  23. 60
      db/accounts.js
  24. 84
      db/boards.js
  25. 4
      db/db.js
  26. 13
      db/posts.js
  27. 6
      gulp/res/js/forms.js
  28. 5
      gulp/res/js/live.js
  29. 7
      gulpfile.js
  30. 56
      helpers/checks/actionchecker.js
  31. 42
      helpers/checks/bancheck.js
  32. 48
      helpers/checks/calcperms.js
  33. 4
      helpers/checks/calcpermsmiddleware.js
  34. 20
      helpers/checks/hasperms.js
  35. 61
      helpers/checks/haspermsmiddleware.js
  36. 7
      helpers/checks/spamcheck.js
  37. 8
      helpers/decodequeryip.js
  38. 2
      helpers/imagehash.js
  39. 48
      helpers/permission.js
  40. 43
      helpers/permissions.js
  41. 41
      helpers/permissiontext.js
  42. 43
      helpers/permtemplates.js
  43. 2
      helpers/posting/diceroll.js
  44. 4
      helpers/posting/linkmatch.js
  45. 46
      helpers/posting/markdown.js
  46. 12
      helpers/posting/message.js
  47. 46
      helpers/posting/name.js
  48. 7
      helpers/processip.js
  49. 17
      helpers/render.js
  50. 6
      helpers/schema.js
  51. 4
      helpers/sessionrefresh.js
  52. 22
      migrations/0.0.23.js
  53. 86
      migrations/0.4.0.js
  54. 11
      models/forms/actionhandler.js
  55. 2
      models/forms/addcustompage.js
  56. 2
      models/forms/addnews.js
  57. 20
      models/forms/addstaff.js
  58. 28
      models/forms/changeboardsettings.js
  59. 15
      models/forms/changeglobalsettings.js
  60. 9
      models/forms/create.js
  61. 63
      models/forms/deleteaccounts.js
  62. 4
      models/forms/deleteboard.js
  63. 19
      models/forms/deletestaff.js
  64. 69
      models/forms/editaccount.js
  65. 68
      models/forms/editaccounts.js
  66. 2
      models/forms/editcustompage.js
  67. 2
      models/forms/editnews.js
  68. 9
      models/forms/editpost.js
  69. 40
      models/forms/editstaff.js
  70. 44
      models/forms/makepost.js
  71. 5
      models/forms/register.js
  72. 8
      models/forms/resign.js
  73. 41
      models/forms/transferboard.js
  74. 31
      models/pages/account.js
  75. 5
      models/pages/globalmanage/accounts.js
  76. 5
      models/pages/globalmanage/bans.js
  77. 1
      models/pages/globalmanage/boards.js
  78. 24
      models/pages/globalmanage/editaccount.js
  79. 1
      models/pages/globalmanage/editnews.js
  80. 1
      models/pages/globalmanage/index.js
  81. 6
      models/pages/globalmanage/logs.js
  82. 1
      models/pages/globalmanage/news.js
  83. 7
      models/pages/globalmanage/recent.js
  84. 7
      models/pages/globalmanage/reports.js
  85. 1
      models/pages/globalmanage/settings.js
  86. 1
      models/pages/index.js
  87. 5
      models/pages/manage/bans.js
  88. 24
      models/pages/manage/editstaff.js
  89. 3
      models/pages/manage/index.js
  90. 3
      models/pages/manage/logs.js
  91. 13
      models/pages/manage/mypermissions.js
  92. 11
      models/pages/manage/permissions.js
  93. 9
      models/pages/manage/recent.js
  94. 7
      models/pages/manage/reports.js
  95. 1
      models/pages/manage/settings.js
  96. 14
      models/pages/manage/staff.js
  97. 12
      models/pages/mypermissions.js
  98. 12
      models/pages/permissions.js
  99. 502
      package-lock.json
  100. 5
      package.json
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,3 +1,16 @@
### 0.4.0
- 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.2
- Minor bugfix to post moving.

59
TODO

@ -0,0 +1,59 @@
RANDOM SHIT DISREGARD
- [x] permission checks in helpers/checks/actionchecker.js
- [x] permission bypasses in helpers/schema.js, and edit controllers schema check perm levels to new Permissions.XXX
- [x] helpers/posting/markdown.js markdown permission levels, and in binded custom callback markdowns like diceroll, linkmatch, etc
- [x] remove permLevels, permLevels.markdown, the global settings for it, the controller code for checking it, and put a bit of the template into the migrations
- [x] helpers/decodequeryip.js for ip query decoding, and handle the iphashpermlevel stuff
- [x] make sure a valid permission always passed to markdown. make note about bug for moving, and permission on other posts when getting remarked.
- [x] helpers/posting/message.js, argument permlevel, and passing to markdown function (and other places like editing and moving!)
- [x] helpers/checks/spamcheck.js, for BYPASS_SPAMCHECKS
- [x] helpers/checks/bancheck.js, for BYPASS_BANS or BOARD_MANAGE_OWNER/GENERAL, for board bans
- [x] helpers/checks/calcpermsmiddleware.js, make it return the Permission instance, not return and set res.locals.permLevel
- [x] remove ipHashPermLevel, use the permission. remove from template.js, globalmanage, controller, etc
- [x] remove permLevel bypass in imagehash helper
- [x] helpers/posting/name.js, to emulate old "levels" for capcodes
- [x] per-board permissions page, to show a users permissions
- [x] global permissions page /permissions.html
- [x] remove authLevel from models/pages/account.js
- [x] update account.html, to show only the pages you are able to access in the quick links
- [x] redo whole permissions check in socketio.js, to use calcPerms (and hasPerms? or a separate check)
- [x] fix permissions check in views/mixins/managenav.pug, and make it all more like account.pug
- [x] remove unnecessary board_owner check in page controllers, since they inherit all othe board perms
- [x] link in board staff table, by clicking aand "edit" of the permission base64, to /board/manage/editstaff/username.html,
- [x] remove permissions fluff from FAQ, that shit doesnt need to be there
- [x] rename addModBoard removeModBoard, getOwnedOrModBoards, modBoards, rename mod to "staff"
- [x] db migration, modBoards to staffBoards
- [x] create db/boards addStaff method, unused yet,
- [x] update removeStaffBoard to handle staff object instead of moderators, change those db methods to be called "staff" not "moderators"
- [x] change board "moderators" array, rename "staff" and make it a map of {username:{permissions, dateAdded, etc}}
- [x] add forms to add and delete staff from a board, using new staff methods in db/boards and db/accounts
- [x] add new page /board/manage/editstaff/username.html accessible with MANAGE_BOARD_STAFF, showing the permissions for that user ON THAT BOARD (the board_ stuff only basically)
- [x] link in accounts table, by clicking and "edit" of the permission base64, to /globalmanage/editpermissions/account_id.html,
- [x] add permisions default template to newly created accounts under permissions.
- [x] finally remove authLevel from helpers/checks/calcperms.js once the data is in the db
- [x] update setting authLevel in db/accounts.js, since it should just be permissions data now
- [x] add permtemplates to accounts with migration, based on authlevel, then UNSET auth level
- [x] remove deleteBoardsPermLevel, use ther permission (permissions already checked on the endpoint, so remove from template.js, globalmanage, controller, etc
- [x] remove authLevelNames
- [x] controllers/forms/editstaff.js models/forms/editstaff.js
- [x] fix inheriting board staff perms in calcperms
- [x] adapt controllers/forms/editaccount.js models/forms/editaccount.js to ACCOUNTS, instead of STAFF!
- [x] do another grep for permLevel and authLevel, userBoardCreation, userAccountCreation, ipHashPermLevel, etc
- [x] add new setting "dont store raw ips" instead of based on "-1" ipHashPermLevel which is now removed.
- [x] remove "allow user board creation" and "allow user account creation" in global settings, since it should be part of editing the default/anon users permission template
- [x] make sure staff/acount adding and removing stays consistent
- [x] change bunch permLevel comparisons, esp in models/forms/makepost. to use the perms, or just a combo of some perm like manage_board_general/manage_global_general
- [x] finish migration
- [x] get permissions for multiple boards in account page, to show the proper links for staff and all the links can be available in "boards you own" vs "moderate", depending on their staff permissions
- [x] add some "edit" link and edit account "upgrade"-type buttons, like the local->global post history links (where permssions allow)
- [x] add some "friendly" permission names and descriptions for permissions.toJSON, mostly for frontend, and will be good for translation later
- [x] make permission class better, static allPermissions and permissionEntries
- [x] make BOs show up properly in edit staff, controller check to prevent editing them
- [~] make at least the ANON template editable with MANAGE_GLOBAL_ACCOUNTS. can store in globalsettings for now, since there are only the default 5 (for now)
- [~] implement showing "template name" or "custom", based on id a permission is one of the templates or not
- [ ] update nginx config
Improvements for later
- [ ] update jschan-docs. add a section about accounts, staff and permissions.
- [ ] !!explore options for permission changes applying to users when a template is updated. e.g. change the anon template to add the CREATE_BOARD, but an existing user (who does not have this permission) doesnt get it added to their account. I think use mongo $bitsAllSet+$bitsAllClear works for this (since the binary is stored), and then set the new permission binary. for missing ADDED permissions, inheriting from anon could be a good interim solution, but needs to be properly changed to handle the inverse properly.
- [ ] permission editing page to mass update or apply a change to multiple users just for specific permissions, not a full overwrite. maybe a page with options "dont change, set to false, set to true"?
- [ ] full on ability to add custom templates for roles, and

@ -74,32 +74,6 @@ module.exports = {
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,84 @@ 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,
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 deleting
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.single, '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,40 @@
'use strict';
const deleteStaff = require(__dirname+'/../../models/forms/deletestaff.js')
, 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({
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' },
//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,47 @@
'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')
, { 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" },
]);
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.single, 'edit', rateLimitCost.editPost));
if (ratelimitUser > 100 || ratelimitIp > 100) {

@ -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,18 @@ 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'),
//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', {

@ -20,6 +20,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,11 +17,11 @@ 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,
, { 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,
globalManageRecent, globalManageAccounts, globalManageNews, globalManageLogs } = require(__dirname+'/../models/pages/globalmanage/')
, { changePassword, blockBypass, home, register, login, create,
, { 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 })
@ -52,31 +53,57 @@ 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/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);
//TODO: edit post edit page form, like editnews/editaccount endpoint
//captcha
router.get('/captcha', geoAndTor, processIp, captcha); //get captcha image and cookie
@ -85,7 +112,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,18 @@ 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;
},
updateLastActiveDate: (username) => {
return db.updateOne({
'_id': username
@ -65,7 +82,7 @@ module.exports = {
'$set': {
lastActiveDate: new Date()
}
})
});
},
find: (filter, skip=0, limit=0) => {
@ -73,8 +90,6 @@ module.exports = {
'projection': {
'passwordHash': 0
}
}).sort({
'authLevel': 1
}).skip(skip).limit(limit).toArray();
},
@ -120,35 +135,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 +175,7 @@ module.exports = {
},
},
{
'modBoards.0': {
'staffBoards.0': {
'$exists': true
}
}
@ -168,26 +183,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,14 +91,61 @@ 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,
}, {
'$unset': unsetObject,
}
);
},
setStaffPermissions: (board, username, permissions) => {
cache.del(`board:${board}`);
return db.updateOne(
{
'_id': board,
}, {
'$set': {
[`staff.${username}.permissions`]: Mongo.Binary(permissions.array),
}
}
);
},
setOwner: (board, username = null) => {
cache.del(`board:${board}`);
return db.updateOne(
{
'_id': board,
}, {
'$pull': {
'settings.moderators': username
'$set': {
'owner': null
}
}
);
@ -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,
}

@ -3,6 +3,7 @@
const Mongo = require(__dirname+'/db.js')
, 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');
@ -21,7 +22,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;
@ -40,7 +41,7 @@ module.exports = {
} else if (typeof ip === 'string') {
query['ip.raw'] = 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) {
@ -529,13 +530,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 } };
}
@ -556,13 +557,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 } };
}

@ -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');
@ -264,8 +265,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 : false;
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),

@ -7,6 +7,7 @@ const config = require(__dirname+'/config.js')
, semver = require('semver')
, uploadDirectory = require(__dirname+'/helpers/files/uploadDirectory.js')
, commit = require(__dirname+'/helpers/commit.js')
, PermissionTemplates = require(__dirname+'/helpers/permtemplates.js')
, replace = require('gulp-replace')
, less = require('gulp-less')
, concat = require('gulp-concat')
@ -203,7 +204,7 @@ async function wipe() {
await Posts.db.createIndex({ 'globalreports.0': 1 }, { 'partialFilterExpression': { 'globalreports.0': { '$exists': true } } })
const randomPassword = randomBytes(20).toString('base64')
await Accounts.insertOne('admin', 'admin', randomPassword, 0);
await Accounts.insertOne('admin', 'admin', randomPassword, PermissionTemplates.ROOT);
console.log('=====LOGIN DETAILS=====\nusername: admin\npassword:', randomPassword, '\n=======================');
await db.collection('version').replaceOne({
@ -344,6 +345,7 @@ function deletehtml() {
async function custompages() {
const formatSize = require(__dirname+'/helpers/files/formatsize.js');
const Permissions = require(__dirname+'/helpers/permissions.js');
return gulp.src([
`${paths.pug.src}/custompages/*.pug`,
`${paths.pug.src}/pages/404.pug`,
@ -354,7 +356,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 +388,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')
, PermissionTemplates = require(__dirname+'/../permtemplates.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(PermissionTemplates.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,13 +2,13 @@
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) || decoded.match(/[a-z0-9]{24}/i))) { //if perms to view raw ip or bypass, allow querying
if (permissions.get(Permissions.VIEW_RAW_IP) //allow raw ip query, if has perms to view raw ip
&& (isIP(decoded) || decoded.match(/[a-z0-9]{24}/i))) { //and is ip or bypass
return decoded;
} else if (decoded.length === 10) { //otherwise, only allow last 10 char substring
return new RegExp(`${escapeRegExp(decoded)}$`);

@ -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,48 @@
'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, name = 'Custom') {
super(data);
this.name = name;
}
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_GENERAL, Permissions.MANAGE_BOARD_BANS,
Permissions.MANAGE_BOARD_LOGS, Permissions.MANAGE_BOARD_SETTINGS,
Permissions.MANAGE_BOARD_CUSTOMISATION, Permissions.MANAGE_BOARD_STAFF,
]);
}
}
};
module.exports = Permission;

@ -0,0 +1,43 @@
'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_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,41 @@
'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_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' },
};

@ -0,0 +1,43 @@
'use strict';
const Permissions = require(__dirname+'/permissions.js')
, Permission = require(__dirname+'/permission.js');
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, Permissions.MANAGE_BOARD_SETTINGS, Permissions.MANAGE_BOARD_CUSTOMISATION,
]);
const BOARD_OWNER = new Permission(BOARD_STAFF.base64)
BOARD_OWNER.setAll([
Permissions.MANAGE_BOARD_OWNER, Permissions.MANAGE_BOARD_STAFF,
]);
const GLOBAL_STAFF = new Permission(BOARD_OWNER.base64);
GLOBAL_STAFF.setAll([
//no MANAGE_GLOBAL_ACCOUNTS, for now
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 ROOT = new Permission();
ROOT.setAll(Permission.allPermissions);
module.exports = {
ANON,
BOARD_STAFF,
BOARD_OWNER,
GLOBAL_STAFF,
ROOT,
};

@ -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,11 @@
'use strict';
module.exports = (permLevel, match, p1, p2, p3, offset, string, groups) => {
module.exports = (permissions, match, p1, p2, p3, offset, string, groups) => {
let { url, label, urlOnly } = groups;
url = url || urlOnly;
if (permLevel >= 4) {
if (!permissions.get(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')
, PermissionTemplates = require(__dirname+'/../permtemplates.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(PermissionTemplates.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()}`;

@ -21,7 +21,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,12 +40,15 @@ module.exports = (req, res, next) => {
qrange = createCIDR(ipStr, 64).toString();
hrange = createCIDR(ipStr, 48).toString();
}
res.locals.ip = {
raw: ipHashPermLevel === -1 ? hashIp(ipStr) : ipStr,
raw: dontStoreRawIps === true ? hashIp(ipStr) : ipStr,
single: hashIp(ipStr),
qrange: hashIp(qrange),
hrange: hashIp(hrange),
}
//#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,

@ -58,10 +58,10 @@ 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.
const filteredSchema = schema.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);
};

@ -0,0 +1,86 @@
'use strict';
const PermissionTemplates = require(__dirname+'/../helpers/permtemplates.js')
, Permission = require(__dirname+'/../helpers/permission.js')
, { Binary } = require('mongodb');
module.exports = async(db, redis) => {
console.log('making db changes for permissions update');
console.log('setting new permission templates to replace old permission "levels"');
await db.collection('accounts').updateMany({ permLevel: 0 }, {
'$set': {
'permissions': Binary(PermissionTemplates.ROOT.array),
},
});
await db.collection('accounts').updateMany({ permLevel: 1 }, {
'$set': {
'permissions': Binary(PermissionTemplates.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({ permLevel: { $gte: 2 } }, { //gte2, to get 2, 3, and 4.
'$set': {
'permissions': Binary(PermissionTemplates.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 bulkWrites = allBoards.map(board => {
const staffObject = board.moderators.reduce((acc, mod) => {
acc[mod] = {
permissions: Binary(PermissionTemplates.BOARD_STAFF.array),
addedDate: new Date(),
};
return acc;
}, {});
//add add the BO to staff
staffObject[board.owner] = {
permissions: Binary(PermissionTemplates.BOARD_OWNER.array),
addedDate: new Date(),
}
return {
'updateOne': {
'filter': {
'_id': board._id,
},
'update': {
'$unset': {
'moderators': '',
},
'$set': {
'staff': staffObject,
}
}
}
});
});
await db.collection('boards').bulkWrite(bulkWrites);
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')
, PermissionTemplates = require(__dirname+'/../../helpers/permtemplates.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, PermissionTemplates.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')
, PermissionTemplates = require(__dirname+'/../../helpers/permtemplates.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(PermissionTemplates.BOARD_OWNER.array),
'addedDate': new Date(),
},
},
'flags': {},
'assets': [],
'settings': {
name,
description,
'moderators': [],
...boardDefaults
}
}

@ -0,0 +1,63 @@
'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 //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);
}
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,19 @@
'use strict';
const { Boards, Accounts } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js');
module.exports = async (req, res, next) => {
await Promise.all([
Accounts.removeStaffBoard(req.body.checkedstaff, res.locals.board._id),
Boards.removeStaff(res.locals.board._id, req.body.checkedstaff)
]);
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',
'message': 'Deleted staff',
'redirect': `/${req.params.board}/manage/staff.html`,
});
}

@ -0,0 +1,69 @@
'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 = new Permission(res.locals.editingAccount.permissions);
//this can probably be made more general
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)) {
//be careful giving others manage_global_accounts!
updatingPermissions.set(Permissions.MANAGE_GLOBAL_ACCOUNTS, (req.body.MANAGE_GLOBAL_ACCOUNTS != null))
updatingPermissions.set(Permissions.ROOT, (req.body.ROOT != null));
}
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,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 } = 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()
@ -616,12 +618,10 @@ ${res.locals.numFiles > 0 ? req.files.file.map(f => f.name+'|'+(f.phash || '')).
//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: { single: single.slice(-10), raw: null } });
Socketio.emitRoom(`${res.locals.board._id}-manage-recent-hashed`, 'newPost', { ...projectedPost, ip: { single: single.slice(-10), 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 (ip hash perm level no longer exists) {
Socketio.emitRoom('globalmanage-recent-raw', 'newPost', { ...projectedPost, ip: { single: single.slice(-10), raw } });
Socketio.emitRoom(`${res.locals.board._id}-manage-recent-raw`, 'newPost', { ...projectedPost, ip: { single: single.slice(-10), raw } });
}
// }
//now add other pages to be built in background
if (enableCaptcha) {

@ -1,7 +1,8 @@
'use strict';
const { Accounts } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js');
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, PermissionTemplates = require(__dirname+'/../../helpers/permtemplates.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, PermissionTemplates.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', {
@ -22,14 +22,14 @@ module.exports = async (req, res, next) => {
]);
} 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,34 +1,33 @@
'use strict';
const { Boards, Accounts } = require(__dirname+'/../../db/')
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js');
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js')
, PermissionTemplates = require(__dirname+'/../../helpers/permtemplates.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 owned board from current account
//remove current owner
await Accounts.removeOwnedBoard(res.locals.board.owner, req.params.board)
//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)
}
//set owner in memory and in db
//set new owner in locals
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);
if (res.locals.board.staff[newOwner.username] != null) {
//if already a staff, just change their permission instead of removing+adding back
await Promise.all([
Boards.setStaffPermissions(req.params.board, res.locals.user.username),
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, req.params.board, PermissionTemplates.BOARD_OWNER, true),
Accounts.addOwnedBoard(newOwner._id, req.params.board),
]);
}
return dynamicResponse(req, res, 200, 'message', {
'title': 'Success',

@ -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,
});

@ -20,7 +20,7 @@ module.exports = async (req, res, next) => {
'ownedBoards': uri
},
{
'modBoards': uri
'staffBoards': uri
},
];
}
@ -40,6 +40,9 @@ module.exports = async (req, res, next) => {
.set('Cache-Control', 'private, max-age=5')
.render('globalmanageaccounts', {
csrf: req.csrfToken(),
user: res.locals.user,
permissions: res.locals.permissions,
user: res.locals.user,
queryString,
username,
uri,

@ -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,24 @@
'use strict';
const { Accounts } = require(__dirname+'/../../../db/')
, 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();
}
res
.set('Cache-Control', 'private, max-age=5')
.render('editaccount', {
csrf: req.csrfToken(),
board: res.locals.board,
accountUsername: req.params.accountusername,
accountPermissions: new Permission(editingAccount.permissions),
});
}

@ -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,
});

@ -10,4 +10,5 @@ module.exports = {
globalManageAccounts: require(__dirname+'/accounts.js'),
globalManageSettings: require(__dirname+'/settings.js'),
editNews: require(__dirname+'/editnews.js'),
editAccount: require(__dirname+'/editaccount.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;
@ -18,7 +19,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 instanceof RegExp) {
filter['ip.single'] = ipMatch;
} else if (typeof ipMatch === 'string') {
@ -40,9 +41,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,
});

@ -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 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 +26,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),
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,

@ -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,
});
}

@ -2,24 +2,25 @@
const { Posts } = require(__dirname+'/../../../db/')
, 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 { 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.single.slice(-10) }, res.locals.permlevel);
ip = decodeQueryIP({ ip: fetchedPost.ip.single.slice(-10) }, 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 +33,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),
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),
});
}

@ -9,6 +9,7 @@ module.exports = async (req, res, next) => {
.set('Cache-Control', 'private, max-age=5')
.render('managesettings', {
csrf: req.csrfToken(),
permissions: res.locals.permissions,
countryNamesMap,
countryCodes,
themes,

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

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

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

502
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
{
"name": "jschan",
"version": "0.3.2",
"migrateVersion": "0.2.0",
"version": "0.4.0",
"migrateVersion": "0.4.0",
"description": "",
"main": "server.js",
"dependencies": {
@ -22,6 +22,7 @@
"fs": "0.0.1-security",
"fs-extra": "^10.0.0",
"gm": "git+https://gitgud.io/fatchan/gm.git",
"big-bitfield": "git+https://gitgud.io/fatchan/big-bitfield.git",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-concat": "^2.6.1",

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

Loading…
Cancel
Save