code:
'use strict';

const editAccount = require(__dirname+'/../../models/forms/editaccount.js')
	, { Accounts } = require(__dirname+'/../../db/')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, roleManager = require(__dirname+'/../../lib/permission/rolemanager.js')
	, { Permissions } = require(__dirname+'/../../lib/permission/permissions.js')
	, Permission = require(__dirname+'/../../lib/permission/permission.js')
	, { alphaNumericRegex, checkSchema, lengthBody, inArrayBody, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['username'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		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, blocking: true, error: __('Invalid account username') },
			{ result: (res.locals.user.username === req.body.username), expected: false, error: __('You can\'t edit your own permissions') },
			{ result: !existsBody(req.body.template) //no template, OR the template is a valid one
				|| inArrayBody(req.body.template, [roleManager.roles.ANON.base64, roleManager.roles.GLOBAL_STAFF.base64,
					roleManager.roles.ADMIN.base64, roleManager.roles.BOARD_STAFF.base64, roleManager.roles.BOARD_OWNER.base64]),
			expected: true, error: __('Invalid template selection') },
			{ result: () => {
				//not applying a template, OR the user doesn't have root perms, has to be a function to execute after the async result above.
				if (!existsBody(req.body.template)) {
					return true;
				}
				const editingPermission = new Permission(res.locals.editingAccount.permissions);
				return !editingPermission.get(Permissions.ROOT);
			},
			expected: true, error: __('You can\'t apply template permissions to a ROOT user.') },
		]);

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

	}

};


code:
'use strict';

const config = require(__dirname+'/../../lib/misc/config.js')
	, removeBans = require(__dirname+'/../../models/forms/removebans.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, denyAppeals = require(__dirname+'/../../models/forms/denybanappeals.js')
	, editBanDuration = require(__dirname+'/../../models/forms/editbanduration.js')
	, editBanNote = require(__dirname+'/../../models/forms/editbannote.js')
	, upgradeBans = require(__dirname+'/../../models/forms/upgradebans.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, lengthBody, numberBody, inArrayBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		timeFields: ['ban_duration'],
		numberFields: ['upgrade'],
		trimFields: ['option', 'ban_note'],
		allowedArrays: ['checkedbans'],
		objectIdArrays: ['checkedbans']
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const { globalLimits } = config.get;

		const errors = await checkSchema([
			{ result: lengthBody(req.body.checkedbans, 1), expected: false, error: __('Must select at least one ban') },
			{ result: inArrayBody(req.body.option, ['unban', 'edit_duration', 'edit_note', 'upgrade', 'deny_appeal']), expected: true, error: __('Invalid ban action') },
			{ result: req.body.option !== 'edit_duration' || numberBody(req.body.ban_duration, 1), expected: true, error: __('Invalid ban duration') },
			{ result: req.body.option !== 'edit_note' || !lengthBody(req.body.ban_note, 1, globalLimits.fieldLength.log_message), expected: true, error: __('Ban note must be %s characters or less', globalLimits.fieldLength.log_message) },
			{ result: req.body.option !== 'upgrade' || inArrayBody(req.body.upgrade, [1, 2]), expected: true, error: __('Invalid ban upgrade option') },
		]);

		const redirect = req.params.board ? `/${req.params.board}/manage/bans.html` : '/globalmanage/bans.html';

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				redirect
			});
		}

		let amount = 0;
		let message;
		try {
			switch (req.body.option) {
				case 'unban':
					amount = await removeBans(req, res, next);
					message = __('Removed %s bans', amount);
					break;
				case 'deny_appeal':
					amount = await denyAppeals(req, res, next);
					message = __('Denied %s appeals', amount);
					break;
				case 'upgrade':
					amount = await upgradeBans(req, res, next);
					message = __('Upgraded %s bans', amount);
					break;
				case 'edit_duration':
					amount = await editBanDuration(req, res, next);
					message = __('Edited duration for %s bans', amount);
					break;
				case 'edit_note':
					amount = await editBanNote(req, res, next);
					message = __('Edited note for %s bans', amount);
					break;
				default:
					throw __('Invalid ban action'); //should never happen anyway
			}
		} catch (err) {
			return next(err);
		}

		return dynamicResponse(req, res, 200, 'message', {
			'title': __('Success'),
			message,
			redirect
		});

	}

};

repo_name: diegolegion/algochan

license: mit


code:
'use strict';

const editCustomPage = require(__dirname+'/../../models/forms/editcustompage.js')
	, { CustomPages } = require(__dirname+'/../../db/')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, lengthBody, numberBody, existsBody } = require(__dirname+'/../../lib/input/schema.js')
	, config = require(__dirname+'/../../lib/misc/config.js');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['message', 'title', 'page'],
		processMessageLength: true,
		objectIdFields: ['page_id'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const { globalLimits } = config.get;

		const errors = await checkSchema([
			{ result: existsBody(req.body.page_id), expected: true, error: __('Missing page id') },
			{ result: existsBody(req.body.message), expected: true, error: __('Missing message') },
			{ result: existsBody(req.body.title), expected: true, error: __('Missing title') },
			{ result: existsBody(req.body.page), expected: true, error: __('Missing .html name') },
			{ result: () => {
				if (req.body.page) {
					return /^[a-z0-9_-]+$/i.test(req.body.page);
				}
				return false;
			} , expected: true, error: __('.html name must contain a-z 0-9 _ - only') },
			{ result: numberBody(res.locals.messageLength, 0, globalLimits.customPages.maxLength), expected: true, error: __('Message must be %s characters or less', globalLimits.customPages.maxLength) },
			{ result: lengthBody(req.body.title, 0, 50), expected: false, error: __('Title must be 50 characters or less') },
			{ result: lengthBody(req.body.page, 0, 50), expected: false, error: __('.html name must be 50 characters or less') },
			{ result: async () => {
				const existingPage = await CustomPages.findOne(req.params.board, req.body.page);
				if (existingPage && existingPage.page === req.body.page) {
					return existingPage._id.equals(req.body.page_id);
				}
				return true;
			}, expected: true, error: __('.html name must be unique') },
		]);

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': req.headers.referer || `/${req.params.board}/manage/custompages.html`,
			});
		}

		try {
			await editCustomPage(req, res, next);
		} catch (err) {
			return next(err);
		}

	}

};


code:
'use strict';

const editFilter = require(__dirname+'/../../models/forms/editfilter.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, lengthBody, numberBody, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		timeFields: ['filter_ban_duration'],
		trimFields: ['filters', 'filter_message'],
		numberFields: ['filter_mode'],
		objectIdFields: ['filter_id'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const errors = await checkSchema([
			{ result: existsBody(req.body.filter_id), expected: true, error: __('Missing filter id') },
			{ result: lengthBody(req.body.filters, 0, 50000), expected: false, error: __('Filter text cannot exceed 50000 characters') },
			{ result: numberBody(req.body.filter_mode, 0, 2), expected: true, error: __('Filter mode must be a number from 0-2') },
			{ result: numberBody(req.body.filter_ban_duration), expected: true, error: __('Invalid filter auto ban duration') },
		]);

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': req.headers.referer || (req.params.board ? `/${req.params.board}/manage/filters.html` : '/globalmanage/filters.html')
			});
		}

		try {
			await editFilter(req, res, next);
		} catch (err) {
			return next(err);
		}

	}

};


code:
'use strict';

const editNews = require(__dirname+'/../../models/forms/editnews.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, lengthBody, numberBody, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['message', 'title'],
		processMessageLength: true,
		objectIdFields: ['news_id'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const errors = await checkSchema([
			{ result: existsBody(req.body.news_id), expected: true, error: __('Missing news id') },
			{ result: existsBody(req.body.message), expected: true, error: __('Missing message') },
			{ result: numberBody(res.locals.messageLength, 0, 10000), expected: true, error: __('Message must be 10000 characters or less') },
			{ result: existsBody(req.body.title), expected: true, error: __('Missing title') },
			{ result: lengthBody(req.body.title, 0, 50), expected: false, error: __('Title must be 50 characters or less') },
		]);

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': req.headers.referer || '/globalmanage/news.html'
			});
		}

		try {
			await editNews(req, res, next);
		} catch (err) {
			return next(err);
		}

	}

};


code:
'use strict';

const editPost = require(__dirname+'/../../models/forms/editpost.js')
	, { Permissions } = require(__dirname+'/../../lib/permission/permissions.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, config = require(__dirname+'/../../lib/misc/config.js')
	, { Ratelimits, Posts } = require(__dirname+'/../../db/')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, lengthBody, numberBody, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['board', 'message', 'name', 'subject', 'email', 'log_message'],
		processMessageLength: true,
		numberFields: ['postId'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const { rateLimitCost, globalLimits } = config.get;

		const errors = await checkSchema([
			{ result: existsBody(req.body.board), expected: true, error: __('Missing board') },
			{ result: numberBody(req.body.postId, 1), expected: true, error: __('Missing postId') },
			{ result: lengthBody(req.body.message, 0, globalLimits.fieldLength.message), expected: false, error: __('Message must be %s characters or less', globalLimits.fieldLength.message) },
			{ result: lengthBody(req.body.name, 0, globalLimits.fieldLength.name), expected: false, error: __('Name must be %s characters or less', globalLimits.fieldLength.name) },
			{ result: lengthBody(req.body.subject, 0, globalLimits.fieldLength.subject), expected: false, error: __('Subject must be %s characters or less', globalLimits.fieldLength.subject) },
			{ result: lengthBody(req.body.email, 0, globalLimits.fieldLength.email), expected: false, error: __('Email must be %s characters or less', globalLimits.fieldLength.email) },
			{ result: lengthBody(req.body.log_message, 0, globalLimits.fieldLength.log_message), expected: false, error: __('Modlog message must be %s characters or less', globalLimits.fieldLength.log_message) },
			{ result: async () => {
				res.locals.post = await Posts.getPost(req.body.board, req.body.postId);
				return res.locals.post != null;
			}, expected: true, error: __('Post doesn\'t exist') },
		]);

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
			});
		}

		if (!res.locals.permissions.get(Permissions.BYPASS_RATELIMITS)) {
			const ratelimitUser = await Ratelimits.incrmentQuota(req.session.user, 'edit', rateLimitCost.editPost);
			const ratelimitIp = res.locals.anonymizer ? 0 : (await Ratelimits.incrmentQuota(res.locals.ip.cloak, 'edit', rateLimitCost.editPost));
			if (ratelimitUser > 100 || ratelimitIp > 100) {
				return dynamicResponse(req, res, 429, 'message', {
					'title': __('Ratelimited'),
					'error': __('You are editing posts too quickly, please wait a minute and try again'),
				});
			}
		}

		try {
			await editPost(req, res, next);
		} catch (err) {
			return next(err);
		}

	}

};


code:
'use strict';

const editRole = require(__dirname+'/../../models/forms/editrole.js')
	, { Roles } = require(__dirname+'/../../db/')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		objectIdFields: ['roleid'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const errors = await checkSchema([
			{ result: existsBody(req.body.roleid), expected: true, error: __('Missing role id') },
			{ result: async () => {
				res.locals.editingRole = await Roles.findOne(req.body.roleid);
				return res.locals.editingRole != null && res.locals.editingRole.name !== 'ROOT';
			}, blocking: true, expected: true, error: __('You can\'t edit this role') },
		]);

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': req.headers.referer || `/${req.params.board}/manage/roles.html`,
			});
		}

		try {
			await editRole(req, res, next);
		} catch (err) {
			return next(err);
		}

	}

};


code:
'use strict';

const editStaff = require(__dirname+'/../../models/forms/editstaff.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { alphaNumericRegex, checkSchema, lengthBody, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['username'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

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

	}

};


code:
'use strict';

const { Posts } = require(__dirname+'/../../db/')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, config = require(__dirname+'/../../lib/misc/config.js')
	, actionHandler = require(__dirname+'/../../models/forms/actionhandler.js')
	, actionChecker = require(__dirname+'/../../lib/input/actionchecker.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, lengthBody, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		timeFields: ['ban_duration'],
		trimFields: ['postpassword', 'report_reason', 'ban_reason', 'log_message'],
		allowedArrays: ['checkedreports', 'globalcheckedposts'],
		numberFields: ['move_to_thread'],
		objectIdArrays: ['globalcheckedposts']
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const { globalLimits } = config.get;

		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') },
			{ result: lengthBody(res.locals.actions.validActions, 1), expected: false, blocking: true, error: __('No actions selected') },
			{ result: lengthBody(req.body.globalcheckedposts, 1, globalLimits.multiInputs.posts.staff), expected: false, error: __('Must not select >%s posts per request', globalLimits.multiInputs.posts.staff) },
			{ result: (existsBody(req.body.global_report_ban) && !req.body.checkedreports), expected: false, error: __('Must select post and reports to ban reporter') },
			{ result: (existsBody(req.body.checkedreports) && !req.body.global_report_ban), expected: false, error: __('Must select a report action if checked reports') },
			{ result: (existsBody(req.body.checkedreports) && !req.body.globalcheckedposts), expected: false, error: __('Must check parent post if checking reports for report action') },
			{ 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.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 %s characters or less', globalLimits.fieldLength.postpassword) },
			{ result: lengthBody(req.body.ban_reason, 0, globalLimits.fieldLength.ban_reason), expected: false, error: __('Ban reason must be %s characters or less', globalLimits.fieldLength.ban_reason) },
			{ result: lengthBody(req.body.log_message, 0, globalLimits.fieldLength.log_message), expected: false, error: __('Modlog message must be %s characters or less', globalLimits.fieldLength.log_message) },
		]);

		//return the errors
		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': '/globalmanage/reports.html'
			});
		}

		//get posts with global ids only
		try {
			res.locals.posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true);
		} catch (err) {
			return next(err);
		}
		if (!res.locals.posts || res.locals.posts.length === 0) {
			return dynamicResponse(req, res, 404, 'message', {
				'title': __('Not found'),
				'error': __('Selected posts not found'),
				'redirect': '/globalmanage/reports.html'
			});
		}

		if (req.body.edit) {
			//edit post, only allowing one
			return res.render('editpost', {
				'post': res.locals.posts[0],
				'csrf': req.csrfToken(),
				'referer': (req.headers.referer || `/${res.locals.posts[0].board}/manage/thread/${res.locals.posts[0].thread || res.locals.posts[0].postId}.html`) + `#${res.locals.posts[0].postId}`,
			});
		}

		try {
			await actionHandler(req, res, next);
		} catch (err) {
			console.error(err);
			return next(err);
		}

	}

};


code:
'use strict';

const globalClear = require(__dirname+'/../../models/forms/globalclear.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, inArrayBody, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['table']
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const errors = await checkSchema([
			{ result: existsBody(req.body.confirm), expected: true, error: __('Missing confirmation') },
			{ result: inArrayBody(req.body.table, ['sessions', 'blockbypass']), expected: true, error: __('Invalid table') },
		]);

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': '/globalmanage/settings.html'
			});
		}

		try {
			await globalClear(req, res, next);
		} catch (err) {
			return next(err);
		}

	}

};


code:
'use strict';

const changeGlobalSettings = require(__dirname+'/../../models/forms/changeglobalsettings.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, themeHelper = require(__dirname+'/../../lib/misc/themes.js')
	, config = require(__dirname+'/../../lib/misc/config.js')
	, { fontPaths } = require(__dirname+'/../../lib/misc/fonts.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, i18n = require(__dirname+'/../../lib/locale/locale.js')
	, { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable,
		inArrayBody, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		timeFields: ['hot_threads_max_age', 'inactive_account_time', 'default_ban_duration', 'block_bypass_expire_after_time', 'dnsbl_cache_time', 'board_defaults_delete_protection_age'],
		trimFields: ['captcha_options_grid_question', 'captcha_options_grid_trues', 'captcha_options_grid_falses', 'captcha_options_font', 'allowed_hosts', 'dnsbl_blacklists', 'other_mime_types',
			'highlight_options_language_subset', 'global_limits_custom_css_filters', 'board_defaults_filters', 'filters', 'archive_links', 'ethereum_links', 'reverse_links', 'language', 'board_defaults_language'],
		numberFields: ['inactive_account_action', 'abandoned_board_action', 'auth_level', 'captcha_options_text_wave', 'captcha_options_text_paint', 'captcha_options_text_noise',
			'captcha_options_grid_noise', 'captcha_options_grid_edge', 'captcha_options_generate_limit', 'captcha_options_grid_size',  'captcha_options_grid_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', 'rate_limit_cost_captcha', 'rate_limit_cost_board_settings', 'rate_limit_cost_edit_post',
			'overboard_limit', 'hot_threads_limit', 'hot_threads_threshold', '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_filters_max',
			'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',
			'global_limits_post_files_max', 'global_limits_post_files_size_max', 'global_limits_asset_files_total', 'global_limits_asset_files_max', 'global_limits_asset_files_size_max',
			'global_limits_banner_files_width', 'global_limits_banner_files_height', 'global_limits_banner_files_max',
			'global_limits_banner_files_total', 'global_limits_banner_files_size_max', 'global_limits_flag_files_max', 'global_limits_flag_files_total', 'global_limits_flag_files_size_max',
			'global_limits_field_length_name', 'global_limits_field_length_email', 'global_limits_field_length_subject', 'global_limits_field_length_postpassword',
			'global_limits_field_length_message', 'global_limits_field_length_report_reason', 'global_limits_field_length_ban_reason', 'global_limits_field_length_log_message',
			'global_limits_field_length_uri', 'global_limits_field_length_boardname', 'global_limits_field_length_description', 'global_limits_multi_input_posts_anon',
			'global_limits_multi_input_posts_staff', 'global_limits_custom_css_max', 'global_limits_custom_pages_max', 'global_limits_custom_pages_max_length', 'frontend_script_default_volume',
			'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_delete_protection_count', 'frontend_script_default_tegaki_height', 'frontend_script_default_tegaki_width', 'global_limits_post_files_size_image_resolution', 'global_limits_post_files_size_video_resolution']
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const { globalLimits } = config.get;

		const errors = await checkSchema([
			{ result: () => {
				if (req.body.thumb_extension) {
					return /\.[a-z0-9]+/i.test(req.body.thumb_extension);
				}
				return false;
			}, expected: true, error: __('Thumb extension must be like .xxx') },
			{ result: () => {
				if (req.body.other_mime_types) {
					return req.body.other_mime_types
						.split('\n')
						.some(m => {
							return !m.match(/\w+\/\w+/i);
						});
				}
				return false;
			}, expected: false, error: __('Extra mime types must be like type/subtype') },
			{ result: () => {
				if (req.body.archive_links) {
					/* eslint-disable no-useless-escape */
					return /https?\:\/\/[^\s<>\[\]{}|\\^]+%s[^\s<>\[\]{}|\\^]*/i.test(req.body.archive_links);
				}
				return false;
			}, expected: true, error: __('Invalid archive links URL format, must be a link containing %s where the url param belongs.') },
			{ result: () => {
				if (req.body.reverse_links) {
					return /https?\:\/\/[^\s<>\[\]{}|\\^]+%s[^\s<>\[\]{}|\\^]*/i.test(req.body.reverse_links);
				}
				return false;
			}, expected: true, error: __('Invalid reverse image search links URL format, must be a link containing %s where the url param belongs.') },
			{ result: () => {
				if (req.body.ethereum_links) {
					return /https?\:\/\/[^\s<>\[\]{}|\\^]+%s[^\s<>\[\]{}|\\^]*/i.test(req.body.ethereum_links);
				}
				return false;
			}, expected: true, error: __('Invalid ethereum links URL format, must be a link containing %s where the url param belongs.') },
			{ result: existsBody(req.body.referrer_check) ? lengthBody(req.body.allowed_hosts, 1) : false, expected: false, error: __('Please enter at least one allowed host in the "Allowed Hosts" field when the "Referer Check" option is selected.') },
			{ result: numberBody(req.body.inactive_account_time), expected: true, error: __('Invalid inactive account time') },
			{ result: numberBody(req.body.inactive_account_action, 0, 2), expected: true, error: __('Inactive account action must be a number from 0-2') },
			{ result: numberBody(req.body.abandoned_board_action, 0, 3), expected: true, error: __('Abandoned board action must be a number from 0-3') },
			{ result: lengthBody(req.body.global_announcement, 0, 10000), expected: false, error: __('Global announcement must not exceed 10000 characters') },
			{ result: lengthBody(req.body.filters, 0, 50000), expected: false, error: __('Filter text cannot exceed 50000 characters') },
			{ result: lengthBody(req.body.allowed_hosts, 0, 10000), expected: false, error: __('Allowed hosts must not exceed 10000 characters') },
			{ result: lengthBody(req.body.country_code_header, 0, 100), expected: false, error: __('Country code header length must not exceed 100 characters') },
			{ result: lengthBody(req.body.ip_header, 0, 100), expected: false, error: __('IP header length must not exceed 100 characters') },
			{ result: lengthBody(req.body.meta_site_name, 0, 100), expected: false, error: __('Meta site name must not exceed 100 characters') },
			{ result: lengthBody(req.body.meta_url, 0, 100), expected: false, error: __('Meta url must not exceed 100 characters') },
			{ result: inArrayBody(req.body.language, i18n.getLocales()), expected: true, error: __('Invalid language') },
			{ result: inArrayBody(req.body.board_defaults_language, i18n.getLocales()), expected: true, error: __('Invalid language') },
			{ result: inArrayBody(req.body.captcha_options_type, ['grid', 'grid2', 'text', 'google', 'hcaptcha', 'yandex']), expected: true, error: __('Invalid captcha options type') },
			{ result: numberBody(req.body.captcha_options_generate_limit, 1), expected: true, error: __('Captcha options generate limit must be a number > 0') },
			{ result: numberBody(req.body.captcha_options_grid_size, 2, 6), expected: true, error: __('Captcha options grid size must be a number from 2-6') },
			{ result: numberBody(req.body.captcha_options_grid_image_size, 50, 500), expected: true, error: __('Captcha options grid image size must be a number from 50-500') },
			{ result: numberBody(req.body.captcha_options_grid_icon_y_offset, 0, 50), expected: true, error: __('Captcha options icon y offset must be a number from 0-50') },
			{ result: numberBody(req.body.captcha_options_num_distorts_min, 0, 10), expected: true, error: __('Captcha options min distorts must be a number from 0-10') },
			{ result: numberBody(req.body.captcha_options_num_distorts_max, 0, 10), expected: true, error: __('Captcha options max distorts must be a number from 0-10') },
			{ result: minmaxBody(req.body.captcha_options_num_distorts_min, req.body.captcha_options_num_distorts_max), expected: true, error: __('Captcha options distorts min must be less than max') },
			{ result: numberBody(req.body.captcha_options_distortion, 0, 50), expected: true, error: __('Captcha options distortion must be a number from 0-50') },
			{ result: inArrayBody(req.body.captcha_options_font, fontPaths), expected: true, error: __('Invalid captcha options font') },
			{ result: numberBody(req.body.captcha_options_text_wave, 0, 10), expected: true, error: __('Captcha options text wave effect strength must be a number form 0-10') },
			{ result: numberBody(req.body.captcha_options_text_paint, 0, 10), expected: true, error: __('Captcha options text paint effect strength must be a number from 0-10') },
			{ result: numberBody(req.body.captcha_options_text_noise, 0, 10), expected: true, error: __('Captcha options text noise effect strength must be a number from 0-10') },
			{ result: numberBody(req.body.captcha_options_grid_noise, 0, 10), expected: true, error: __('Captcha options grid noise effect strength must be a number from 0-10') },
			{ result: numberBody(req.body.captcha_options_grid_edge, 0, 50), expected: true, error: __('Captcha options grid edge effect strength must be a number from 0-50') },
			{ result: numberBody(req.body.dnsbl_cache_time), expected: true, error: __('Invalid dnsbl cache time') },
			{ result: numberBody(req.body.flood_timers_same_content_same_ip), expected: true, error: __('Invalid flood time same content same ip') },
			{ result: numberBody(req.body.flood_timers_same_content_any_ip), expected: true, error: __('Invalid flood time same contenet any ip') },
			{ 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.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') },
			{ result: numberBody(req.body.hot_threads_limit), expected: true, error: __('Invalid hot threads limit') },
			{ result: numberBody(req.body.hot_threads_threshold), expected: true, error: __('Invalid hot threads threshold') },
			{ result: numberBody(req.body.hot_threads_max_age), expected: true, error: __('Invalid hot threads max age') },
			{ result: numberBody(req.body.overboard_limit), expected: true, error: __('Invalid overboard limit') },
			{ result: numberBody(req.body.overboard_catalog_limit), expected: true, error: __('Invalid overboard catalog limit') },
			{ result: numberBody(req.body.lock_wait), expected: true, error: __('Invalid lock wait') },
			{ result: numberBody(req.body.prune_modlogs), expected: true, error: __('Prune modlogs must be a number of days') },
			{ result: numberBody(req.body.prune_ips), expected: true, error: __('Prune ips must be a number of days') },
			{ result: lengthBody(req.body.thumb_extension, 1), expected: false, error: __('Thumbnail extension must be at least 1 character') },
			{ result: numberBody(req.body.thumb_size), expected: true, error: __('Invalid thumbnail size') },
			{ result: numberBody(req.body.video_thumb_percentage, 0, 100), expected: true, error: __('Video thumbnail percentage must be a number from 1-100') },
			{ result: numberBody(req.body.default_ban_duration), expected: true, error: __('Invalid default ban duration') },
			{ result: numberBody(req.body.quote_limit), expected: true, error: __('Quote limit must be a number') },
			{ result: numberBody(req.body.preview_replies), expected: true, error: __('Preview replies must be a number') },
			{ result: numberBody(req.body.sticky_preview_replies), expected: true, error: __('Sticky preview replies must be a number') },
			{ result: numberBody(req.body.early_404_fraction), expected: true, error: __('Early 404 fraction must be a number') },
			{ result: numberBody(req.body.early_404_replies), expected: true, error: __('Early 404 fraction must be a number') },
			{ result: numberBody(req.body.max_recent_news), expected: true, error: __('Max recent news must be a number') },
			{ result: lengthBody(req.body.space_file_name_replacement, 1, 1), expected: false, error: __('Space file name replacement must be 1 character') },
			{ result: lengthBody(req.body.highlight_options_language_subset, 0, 10000), expected: false, error: __('Highlight options language subset must not exceed 10000 characters') },
			{ result: lengthBody(req.body.highlight_options_threshold), expected: false, error: __('Highlight options threshold must be a number') },
			{ result: numberBody(req.body.global_limits_thread_limit_min), expected: true, error: __('Global thread limit minimum must be a number') },
			{ result: numberBody(req.body.global_limits_thread_limit_max), expected: true, error: __('Global thread limit maximum must be a number') },
			{ result: minmaxBody(req.body.global_limits_thread_limit_min, req.body.global_limits_thread_limit_max), expected: true, error: __('Global thread limit min must be less than max') },
			{ result: numberBody(req.body.global_limits_reply_limit_min), expected: true, error: __('Global reply limit minimum must be a number') },
			{ result: numberBody(req.body.global_limits_reply_limit_max), expected: true, error: __('Global reply limit maximum must be a number') },
			{ result: minmaxBody(req.body.global_limits_reply_limit_min, req.body.global_limits_reply_limit_max), expected: true, error: __('Global reply limit min must be less than max') },
			{ result: numberBody(req.body.global_limits_bump_limit_min), expected: true, error: __('Global bump limit minimum must be a number') },
			{ result: numberBody(req.body.global_limits_bump_limit_max), expected: true, error: __('Global bump limit minimum must be a number') },
			{ result: minmaxBody(req.body.global_limits_bump_limit_min, req.body.global_limits_bump_limit_max), expected: true, error: __('Global bump limit min must be less than max') },
			{ result: numberBody(req.body.global_limits_post_files_max), expected: true, error: __('Post files max must be a number') },
			{ result: numberBody(req.body.global_limits_post_files_size_max), expected: true, error: __('Post files size must be a number') },
			{ result: numberBody(req.body.global_limits_post_files_size_image_resolution), expected: true, error: __('Image resolution max must be a number') },
			{ result: numberBody(req.body.global_limits_post_files_size_video_resolution), expected: true, error: __('Video resolution max must be a number') },
			{ result: numberBody(req.body.global_limits_banner_files_width, 1), expected: true, error: __('Banner files height must be a number > 0') },
			{ result: numberBody(req.body.global_limits_banner_files_height, 1), expected: true, error: __('Banner files width must be a number > 0') },
			{ result: numberBody(req.body.global_limits_banner_files_size_max), expected: true, error: __('Banner files size must be a number') },
			{ result: numberBody(req.body.global_limits_banner_files_max), expected: true, error: __('Banner files max must be a number') },
			{ result: numberBody(req.body.global_limits_banner_files_total), expected: true, error: __('Banner files total must be a number') },
			{ result: numberBody(req.body.global_limits_flag_files_size_max), expected: true, error: __('Flag files size must be a number') },
			{ result: numberBody(req.body.global_limits_flag_files_max), expected: true, error: __('Flag files max must be a number') },
			{ result: numberBody(req.body.global_limits_flag_files_total), expected: true, error: __('Flag files total must be a number') },
			{ result: numberBody(req.body.global_limits_asset_files_size_max), expected: true, error: __('Asset files size must be a number') },
			{ result: numberBody(req.body.global_limits_asset_files_max), expected: true, error: __('Asset files max must be a number') },
			{ result: numberBody(req.body.global_limits_asset_files_total), expected: true, error: __('Asset files total must be a number') },
			{ result: numberBody(req.body.global_limits_field_length_name), expected: true, error: __('Global limit name field length must be a number') },
			{ result: numberBody(req.body.global_limits_field_length_email), expected: true, error: __('Global limit email field length must be a number') },
			{ result: numberBody(req.body.global_limits_field_length_subject), expected: true, error: __('Global limit subject field length must be a number') },
			{ result: numberBody(req.body.global_limits_field_length_postpassword, 20), expected: true, error: __('Global limit postpassword field length must be a number >=20') },
			{ result: numberBody(req.body.global_limits_field_length_message), expected: true, error: __('Global limit message field length must be a number') },
			{ result: numberBody(req.body.global_limits_field_length_report_reason), expected: true, error: __('Global limit report reason field length must be a number') },
			{ result: numberBody(req.body.global_limits_field_length_ban_reason), expected: true, error: __('Global limit ban reason field length must be a number') },
			{ result: numberBody(req.body.global_limits_field_length_log_message), expected: true, error: __('Global limit log message field length must be a number') },
			{ result: numberBody(req.body.global_limits_field_length_uri), expected: true, error: __('Global limit board uri field length must be a number') },
			{ result: numberBody(req.body.global_limits_field_length_boardname), expected: true, error: __('Global limit board name field length must be a number') },
			{ result: numberBody(req.body.global_limits_field_length_description), expected: true, error: __('Global limit board description field length must be a number') },
			{ result: numberBody(req.body.global_limits_multi_input_posts_anon), expected: true, error: __('Multi input anon limit must be a number') },
			{ result: numberBody(req.body.global_limits_multi_input_posts_staff), expected: true, error: __('Multi input staff limit must be a number') },
			{ result: numberBody(req.body.global_limits_filters_max), expected: true, error: __('Filters max must be a number') },
			{ result: numberBody(req.body.global_limits_custom_css_max), expected: true, error: __('Custom css max must be a number') },
			{ result: lengthBody(req.body.global_limits_custom_css_filters, 0, 10000), expected: false, error: __('Custom css filters must not exceed 10000 characters') },
			{ result: numberBody(req.body.global_limits_custom_pages_max), expected: true, error: __('Custom pages max must be a number') },
			{ result: numberBody(req.body.global_limits_custom_pages_max_length), expected: true, error: __('Custom pages max length must be a number') },
			{ result: inArrayBody(req.body.board_defaults_theme, themeHelper.themes), expected: true, error: __('Invalid board default theme') },
			{ result: inArrayBody(req.body.board_defaults_code_theme, themeHelper.codeThemes), expected: true, error: __('Invalid board default code theme') },
			{ result: numberBody(req.body.board_defaults_lock_mode, 0, 2), expected: true, error: __('Board default lock mode must be a number from 0-2') },
			{ result: numberBody(req.body.board_defaults_file_r9k_mode, 0, 2), expected: true, error: __('Board default file r9k mode must be a number from 0-2') },
			{ result: numberBody(req.body.board_defaults_message_r9k_mode, 0, 2), expected: true, error: __('Board default message r9k mode must be a number from 0-2') },
			{ result: numberBody(req.body.board_defaults_captcha_mode, 0, 2), expected: true, error: __('Board default captcha mode must be a number from 0-2') },
			{ result: numberBody(req.body.board_defaults_tph_trigger), expected: true, error: __('Board default tph trigger must be a number') },
			{ result: numberBody(req.body.board_defaults_pph_trigger), expected: true, error: __('Board default pph trigger must be a number') },
			{ result: numberBody(req.body.board_defaults_pph_trigger_action, 0, 4), expected: true, error: __('Board default pph trigger action must be a number from 0-4') },
			{ result: numberBody(req.body.board_defaults_tph_trigger_action, 0, 4), expected: true, error: __('Board default tph trigger action must be a number from 0-4') },
			{ result: numberBody(req.body.board_defaults_captcha_reset, 0, 2), expected: true, error: __('Board defaults captcha reset must be a number from 0-2') },
			{ result: numberBody(req.body.board_defaults_lock_reset, 0, 2), expected: true, error: __('Board defaults lock reset must be a number from 0-2') },
			{ result: numberBodyVariable(req.body.board_defaults_reply_limit, globalLimits.replyLimit.min, req.body.global_limits_reply_limit_min, globalLimits.replyLimit.max, req.body.global_limits_reply_limit_max), expected: true, error: __('Board defaults reply limit must be within global limits') },
			{ result: numberBodyVariable(req.body.board_defaults_thread_limit, globalLimits.threadLimit.min, req.body.global_limits_thread_limit_min, globalLimits.threadLimit.max, req.body.global_limits_thread_limit_max), expected: true, error: __('Board defaults thread limit must be within global limits') },
			{ result: numberBodyVariable(req.body.board_defaults_bump_limit, globalLimits.bumpLimit.min, req.body.global_limits_bump_limit_min, globalLimits.bumpLimit.max, req.body.global_limits_bump_limit_max), expected: true, error: __('Board defaults bump limit must be within global limits') },
			{ result: numberBodyVariable(req.body.board_defaults_max_files, 0, 0, globalLimits.postFiles.max, req.body.global_limits_post_files_max), expected: true, error: __('Board defaults max files must be within global limits') },
			{ result: numberBodyVariable(req.body.board_defaults_max_thread_message_length, 0, 0, globalLimits.fieldLength.message, req.body.global_limits_field_length_message), expected: true, error: __('Board defaults max thread message length must be within global limits') },
			{ result: numberBodyVariable(req.body.board_defaults_max_reply_message_length, 0, 0, globalLimits.fieldLength.message, req.body.global_limits_field_length_message), expected: true, error: __('Board defaults max reply message length must be within global limits') },
			{ result: numberBody(req.body.board_defaults_min_thread_message_length), expected: true, error: __('Board defaults min thread message length must be a number') },
			{ result: numberBody(req.body.board_defaults_min_reply_message_length), expected: true, error: __('Board defaults min reply message length must be a number') },
			{ result: minmaxBody(req.body.board_defaults_min_thread_message_length, req.body.board_defaults_max_thread_message_length), expected: true, error: __('Board defaults thread message length min must be less than max') },
			{ result: minmaxBody(req.body.board_defaults_min_reply_message_length, req.body.board_defaults_max_reply_message_length), expected: true, error: __('Board defaults reply message length min must be less than max') },
			{ result: numberBody(req.body.frontend_script_default_volume, 0, 100), expected: true, error: __('Default volume must be a number from 0-100') },
			{ result: numberBody(req.body.frontend_script_default_tegaki_width), expected: true, error: __('Tegaki width must be a number') },
			{ result: numberBody(req.body.frontend_script_default_tegaki_height), expected: true, error: __('Tegaki height must be a number') },
			{ result: numberBody(req.body.board_defaults_delete_protection_age, 0), expected: true, error: __('Invalid board defaults OP thread age delete protection') },
			{ result: numberBody(req.body.board_defaults_delete_protection_count, 0), expected: true, error: __('Invalid board defaults OP thread reply count delete protection') },
			{ result: lengthBody(req.body.webring_following, 0, 10000), expected: false, error: __('Webring following list must not exceed 10000 characters') },
			{ result: lengthBody(req.body.webring_blacklist, 0, 10000), expected: false, error: __('Webring blacklist must not exceed 10000 characters') },
			{ result: lengthBody(req.body.webring_logos, 0, 10000), expected: false, error: __('Webring logos list must not exceed 10000 characters') },
		]);

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': '/globalmanage/settings.html'
			});
		}

		try {
			await changeGlobalSettings(req, res, next);
		} catch (err) {
			return next(err);
		}

	}

};


code:
'use strict';

module.exports = {

	deleteBoardController: require(__dirname+'/deleteboard.js'),
	editBansController: require(__dirname+'/editbans.js'),
	appealController: require(__dirname+'/appeal.js'),
	globalActionController: require(__dirname+'/globalactions.js'),
	actionController: require(__dirname+'/actions.js'),
	addCustomPageController: require(__dirname+'/addcustompage.js'),
	deleteCustomPageController: require(__dirname+'/deletecustompage.js'),
	addNewsController: require(__dirname+'/addnews.js'),
	editNewsController: require(__dirname+'/editnews.js'),
	editCustomPageController: require(__dirname+'/editcustompage.js'),
	deleteNewsController: require(__dirname+'/deletenews.js'),
	uploadBannersController: require(__dirname+'/uploadbanners.js'),
	deleteBannersController: require(__dirname+'/deletebanners.js'),
	addAssetsController: require(__dirname+'/addassets.js'),
	deleteAssetsController: require(__dirname+'/deleteassets.js'),
	addFlagsController: require(__dirname+'/addflags.js'),
	deleteFlagsController: require(__dirname+'/deleteflags.js'),
	boardSettingsController: require(__dirname+'/boardsettings.js'),
	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'),
	twofactorController: require(__dirname+'/twofactor.js'),
	registerController: require(__dirname+'/register.js'),
	changePasswordController: require(__dirname+'/changepassword.js'),
	deleteSessionsController: require(__dirname+'/deletesessions.js'),
	deleteAccountsController: require(__dirname+'/deleteaccounts.js'),
	addFilterController: require(__dirname+'/addfilter.js'),
	editFilterController: require(__dirname+'/editfilter.js'),
	deleteFilterController: require(__dirname+'/deletefilter.js'),
	globalSettingsController: require(__dirname+'/globalsettings.js'),
	globalClearController: require(__dirname+'/globalclear.js'),
	createBoardController: require(__dirname+'/create.js'),
	makePostController: require(__dirname+'/makepost.js'),
	editPostController: require(__dirname+'/editpost.js'),
	addStaffController: require(__dirname+'/addstaff.js'),
	deleteStaffController: require(__dirname+'/deletestaff.js'),
	editStaffController: require(__dirname+'/editstaff.js'),
	editRoleController: require(__dirname+'/editrole.js'),

	//these dont have a "real" controller
	newCaptchaForm: require(__dirname+'/../../models/forms/newcaptcha.js'),
	blockBypassForm: require(__dirname+'/../../models/forms/blockbypass.js'),
	logoutForm: require(__dirname+'/../../models/forms/logout.js'),

};


code:
'use strict';

const loginAccount = require(__dirname+'/../../models/forms/login.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, lengthBody, existsBody } = require(__dirname+'/../../lib/input/schema.js')
	, config = require(__dirname+'/../../lib/misc/config.js')
	, { timingSafeEqual } = require('crypto')
	, cache = require(__dirname+'/../../lib/redis/redis.js')
	, { recover: web3EthAccountsRecover } = require('web3-eth-accounts')
	, { isAddress: web3UtilsIsAddress } = require('web3-utils');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['username', 'password', 'twofactor', 'nonce', 'signature', 'address'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;
		const { enableWeb3 } = config.get;
		res.locals.isWeb3 = req.body.address && req.body.address.length > 0;

		let errors = [];

		if (res.locals.isWeb3) {
			errors = await checkSchema([
				{ result: enableWeb3 === true, expected: true, error: __('Web3 logins disabled') },
				{ result: web3UtilsIsAddress(req.body.address), expected: true, error: __('Invalid address') },
				{ result: existsBody(req.body.nonce), expected: true, error: __('Missing nonce') },
				{ result: existsBody(req.body.signature), expected: true, error: __('Missing signature') },
			]);
		} else {
			errors = await checkSchema([
				{ result: existsBody(req.body.username), expected: true, error: __('Missing username') },
				{ result: existsBody(req.body.password), expected: true, error: __('Missing password') },
				{ result: web3UtilsIsAddress(req.body.username), expected: false, error: __('Username must not be an ethereum address') },
				{ result: lengthBody(req.body.username, 0, 50), expected: false, error: __('Username must be 1-50 characters') },
				{ result: lengthBody(req.body.password, 0, 100), expected: false, error: __('Password must be 1-100 characters') },
				{ result: lengthBody(req.body.twofactor, 0, 6), expected: false, error: __('Invalid 2FA code') },
			]);
		}

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': '/login.html'
			});
		}

		try {
			if (res.locals.isWeb3) {
				const { address, nonce, signature } = req.body;
				const nonceRequest = await cache.del(`nonce:${address}:${nonce}`);
				if (!nonceRequest || nonceRequest !== 1) {
					return dynamicResponse(req, res, 400, 'message', {
						'title': __('Bad Request'),
						'error': __('Login timed out'),
						'redirect': '/login.html'
					});
				}
				let recoveredAddress = (await web3EthAccountsRecover(`Nonce: ${nonce}`, signature)).toLowerCase();
				const match = await timingSafeEqual(Buffer.from(recoveredAddress), Buffer.from(address));
				if (match !== true) {
					return dynamicResponse(req, res, 400, 'message', {
						'title': __('Bad Request'),
						'error': __('Invalid login signature'),
						'redirect': '/login.html'
					});
				}
			}
			await loginAccount(req, res, next);
		} catch (err) {
			return next(err);
		}

	},

};


code:
'use strict';

const makePost = require(__dirname+'/../../models/forms/makepost.js')
	, { Permissions } = require(__dirname+'/../../lib/permission/permissions.js')
	, deleteTempFiles = require(__dirname+'/../../lib/file/deletetempfiles.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, { func: pruneFiles } = require(__dirname+'/../../schedules/tasks/prune.js')
	, config = require(__dirname+'/../../lib/misc/config.js')
	, { Files } = require(__dirname+'/../../db/')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, lengthBody, existsBody } = require(__dirname+'/../../lib/input/schema.js')
	, { recover: web3EthAccountsRecover } = require('web3-eth-accounts');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['message', 'name', 'subject', 'email', 'postpassword', 'password', 'signature'],
		allowedArrays: ['spoiler', 'strip_filename'],
		processMessageLength: true,
		numberFields: ['thread'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const { globalLimits, disableAnonymizerFilePosting, enableWeb3 } = config.get;

		const hasNoMandatoryFile = globalLimits.postFiles.max !== 0 && res.locals.board.settings.maxFiles !== 0 && res.locals.numFiles === 0;
		const disableBoardAnonymizerFilePosting = res.locals.board.settings.disableAnonymizerFilePosting && !res.locals.permissions.get(Permissions.MANAGE_BOARD_GENERAL);

		const errors = await checkSchema([
			{ result: (lengthBody(req.body.message, 1) && res.locals.numFiles === 0), expected: false, error: __('Posts must include a message or file') },
			{ result: (res.locals.anonymizer && (disableAnonymizerFilePosting || disableBoardAnonymizerFilePosting)
				&& res.locals.numFiles > 0 && !res.locals.permissions.get(Permissions.BYPASS_ANONYMIZER_RESTRICTIONS)), expected: false, error: __(`Posting files through anonymizers has been disabled ${disableAnonymizerFilePosting ? 'globally' : 'on this board'}`) },
			{ result: res.locals.numFiles > res.locals.board.settings.maxFiles, blocking: true, expected: false, error: __(`Too many files. Max files per post ${res.locals.board.settings.maxFiles < globalLimits.postFiles.max ? 'on this board ' : ''}is %s`, res.locals.board.settings.maxFiles) },
			{ result: (lengthBody(req.body.subject, 1) && (!existsBody(req.body.thread)
				&& res.locals.board.settings.forceThreadSubject)), expected: false, error: __('Threads must include a subject') },
			{ result: lengthBody(req.body.message, 1) && (!existsBody(req.body.thread)
				&& res.locals.board.settings.forceThreadMessage), expected: false, error: __('Threads must include a message') },
			{ result: lengthBody(req.body.message, 1) && (existsBody(req.body.thread)
				&& res.locals.board.settings.forceReplyMessage), expected: false, error: __('Replies must include a message') },
			{ result: hasNoMandatoryFile && !existsBody(req.body.thread) && res.locals.board.settings.forceThreadFile , expected: false, error: __('Threads must include a file') },
			{ result: hasNoMandatoryFile && existsBody(req.body.thread) && res.locals.board.settings.forceReplyFile , expected: false, error: __('Replies must include a file') },
			{ result: lengthBody(req.body.message, 0, globalLimits.fieldLength.message), expected: false, blocking: true, error: __('Message must be %s characters or less', globalLimits.fieldLength.message) },
			{ result: existsBody(req.body.message) && existsBody(req.body.thread) && lengthBody(req.body.message, res.locals.board.settings.minReplyMessageLength, res.locals.board.settings.maxReplyMessageLength),
				expected: false, error: __('Reply messages must be %s-%s characters', res.locals.board.settings.minReplyMessageLength, res.locals.board.settings.maxReplyMessageLength) },
			{ result: existsBody(req.body.message) && !existsBody(req.body.thread) && lengthBody(req.body.message, res.locals.board.settings.minThreadMessageLength, res.locals.board.settings.maxThreadMessageLength),
				expected: false, error: __('Thread messages must be %s-%s characters', res.locals.board.settings.minThreadMessageLength, res.locals.board.settings.maxThreadMessageLength) },
			{ result: lengthBody(req.body.postpassword, 0, globalLimits.fieldLength.postpassword), expected: false, error: __('Password must be %s characters or less', globalLimits.fieldLength.postpassword) },
			{ result: lengthBody(req.body.name, 0, globalLimits.fieldLength.name), expected: false, error: __('Name must be %s characters or less', globalLimits.fieldLength.name) },
			{ result: lengthBody(req.body.subject, 0, globalLimits.fieldLength.subject), expected: false, error: __('Subject must be %s characters or less', globalLimits.fieldLength.subject) },
			{ result: lengthBody(req.body.email, 0, globalLimits.fieldLength.email), expected: false, error: __('Email must be %s characters or less', globalLimits.fieldLength.email) },
			{ result: async () => {
				if (enableWeb3 === true && res.locals.board.settings.enableWeb3 === true
					&& req.body.message && req.body.signature && req.body.signature.length < 200) {
					try {
						const fixedMessage = req.body.rawMessage.replace(/\r\n/igm, '\n');
						res.locals.recoveredAddress = await web3EthAccountsRecover(fixedMessage, req.body.signature);
						return true;
					} catch (e) {
						console.warn(e);
						return false;
					}
				} else  {
					return true;
				}
			}, expected: true, error: __('Failed to verify message signature') },
		]);

		if (errors.length > 0) {
			await deleteTempFiles(req).catch(console.error);
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}`
			});
		}

		try {
			await makePost(req, res, next);
		} catch (err) {
			await deleteTempFiles(req).catch(console.error);
			if (res.locals.numFiles > 0) {
				const incedFiles = req.files.file.filter(x => x.inced === true && x.filename != null);
				if (incedFiles.length > 0) {
					const incedFileNames = incedFiles.map(x => x.filename);
					await Files.decrement(incedFileNames).catch(console.error);
					await pruneFiles(incedFileNames);
				}
			}
			return next(err);
		}

	}

};


code:
'use strict';

const { Permissions } = require(__dirname+'/../../lib/permission/permissions.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, registerAccount = require(__dirname+'/../../models/forms/register.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, config = require(__dirname+'/../../lib/misc/config.js')
	, { timingSafeEqual } = require('crypto')
	, { alphaNumericRegex, checkSchema, lengthBody, existsBody } = require(__dirname+'/../../lib/input/schema.js')
	, cache = require(__dirname+'/../../lib/redis/redis.js')
	, { recover: web3EthAccountsRecover } = require('web3-eth-accounts')
	, { isAddress: web3UtilsIsAddress } = require('web3-utils');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['username', 'password', 'passwordconfirm', 'nonce', 'signature', 'address'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;
		const { enableWeb3 } = config.get;
		res.locals.isWeb3 = req.body.address && req.body.address.length > 0;

		let errors = [];

		if (res.locals.isWeb3) {
			errors = await checkSchema([
				{ result: res.locals.permissions.get(Permissions.CREATE_ACCOUNT), blocking: true, expected: true, error: __('No permission') },
				{ result: enableWeb3 === true, expected: true, error: __('Web3 logins disabled') },
				{ result: web3UtilsIsAddress(req.body.address), expected: true, error: __('Invalid address') },
				{ result: existsBody(req.body.nonce), expected: true, error: __('Missing nonce') },
				{ result: existsBody(req.body.signature), expected: true, error: __('Missing signature') },
			]);
		} else {
			errors = await checkSchema([
				{ 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: lengthBody(req.body.username), 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: web3UtilsIsAddress(req.body.username), expected: false, error: __('Username must not be an ethereum address') },
				{ result: existsBody(req.body.password), expected: true, error: __('Missing password') },
				{ result: lengthBody(req.body.password, 0, 50), expected: false, error: __('Password must be 50 characters or less') },
				{ 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.permissions);
		}

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': '/register.html'
			});
		}

		try {
			if (res.locals.isWeb3) {
				const { address, nonce, signature } = req.body;
				const nonceRequest = await cache.del(`nonce:${address}:${nonce}`);
				if (!nonceRequest || nonceRequest !== 1) {
					return dynamicResponse(req, res, 400, 'message', {
						'title': __('Bad Request'),
						'error': __('Login timed out'),
						'redirect': '/login.html'
					});
				}
				let recoveredAddress = (await web3EthAccountsRecover(`Nonce: ${nonce}`, signature)).toLowerCase();
				const match = await timingSafeEqual(Buffer.from(recoveredAddress), Buffer.from(address));
				if (match !== true) {
					return dynamicResponse(req, res, 400, 'message', {
						'title': __('Bad Request'),
						'error': __('Invalid login signature'),
						'redirect': '/login.html'
					});
				}
			}
			await registerAccount(req, res, next);
		} catch (err) {
			return next(err);
		}

	}

};


code:
'use strict';

const { Boards } = require(__dirname+'/../../db/')
	, resignFromBoard = require(__dirname+'/../../models/forms/resign.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { alphaNumericRegex, checkSchema, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['board'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const errors = await checkSchema([
			{ result: existsBody(req.body.confirm), expected: true, error: __('Missing confirmation') },
			{ result: existsBody(req.body.board), expected: true, error: __('You did not select a board') },
			{ result: alphaNumericRegex.test(req.body.board), expected: true, error: __('URI must contain a-z 0-9 only') },
			{ result: async () => {
				res.locals.board = await Boards.findOne(req.body.board);
				return res.locals.board != null;
			}, expected: true, error: __('Board /%s/ does not exist', req.body.board) },
		]);

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': '/account.html'
			});
		}

		try {
			await resignFromBoard(req, res, next);
		} catch (err) {
			return next(err);
		}

	}

};


code:
'use strict';

const transferBoard = require(__dirname+'/../../models/forms/transferboard.js')
	, { Accounts } = require(__dirname+'/../../db/')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { alphaNumericRegex, checkSchema, lengthBody, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['username'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const errors = await checkSchema([
			{ result: existsBody(req.body.username), expected: true, error: __('Missing new owner username') },
			{ 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) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': `/${req.params.board}/manage/settings.html`
			});
		}

		try {
			await transferBoard(req, res, next);
		} catch (err) {
			return next(err);
		}

	}

};


code:
'use strict';

const verifyTwofactor = require(__dirname+'/../../models/forms/twofactor.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js')
	, { checkSchema, lengthBody, existsBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	paramConverter: paramConverter({
		trimFields: ['twofactor'],
	}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const errors = await checkSchema([
			{ result: res.locals.user.twofactor === false, expected: true, error: __('You already have 2FA setup') },
			{ result: existsBody(req.body.twofactor), expected: true, error: __('Missing 2FA code') },
			{ result: lengthBody(req.body.twofactor, 6, 6), expected: false, error: __('2FA code must be 6 characters') },
		]);

		if (errors.length > 0) {
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': '/twofactor.html'
			});
		}

		try {
			await verifyTwofactor(req, res, next);
		} catch (err) {
			return next(err);
		}

	},

};


code:
'use strict';

const uploadBanners = require(__dirname+'/../../models/forms/uploadbanners.js')
	, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
	, deleteTempFiles = require(__dirname+'/../../lib/file/deletetempfiles.js')
	, config = require(__dirname+'/../../lib/misc/config.js')
	, { checkSchema, numberBody } = require(__dirname+'/../../lib/input/schema.js');

module.exports = {

	//paramConverter: paramConverter({}),

	controller: async (req, res, next) => {

		const { __ } = res.locals;

		const { globalLimits, disableAnonymizerFilePosting } = config.get;

		const errors = await checkSchema([
			{ result: res.locals.numFiles === 0, expected: false, blocking: true, error: __('Must provide a file') },
			{ result: (res.locals.anonymizer && disableAnonymizerFilePosting && !res.locals.permissions.get(Permissions.BYPASS_ANONYMIZER_RESTRICTIONS)), expected: false, error: __('Posting files through anonymizers has been disabled globally') },
			{ result: numberBody(res.locals.numFiles, 0, globalLimits.bannerFiles.max), expected: true, error: __('Exceeded max banner uploads in one request of %s', globalLimits.bannerFiles.max) },
			{ result: numberBody(res.locals.board.banners.length+res.locals.numFiles, 0, globalLimits.bannerFiles.total), expected: true, error: __('Total number of banners would exceed global limit of %s', globalLimits.bannerFiles.total) },
		]);

		if (errors.length > 0) {
			await deleteTempFiles(req).catch(console.error);
			return dynamicResponse(req, res, 400, 'message', {
				'title': __('Bad request'),
				'errors': errors,
				'redirect': `/${req.params.board}/manage/assets.html`
			});
		}

		try {
			await uploadBanners(req, res, next);
		} catch (err) {
			await deleteTempFiles(req).catch(console.error);
			return next(err);
		}

	}

};


code:
'use strict';

const express  = require('express')
	, router = express.Router({ caseSensitive: true })
	, Boards = require(__dirname+'/../db/boards.js')
	, Posts = require(__dirname+'/../db/posts.js')
	//middlewares
	, processIp = require(__dirname+'/../lib/middleware/ip/processip.js')
	, geoIp = require(__dirname+'/../lib/middleware/ip/geoip.js')
	, calcPerms = require(__dirname+'/../lib/middleware/permission/calcpermsmiddleware.js')
	, { Permissions } = require(__dirname+'/../lib/permission/permissions.js')
	, hasPerms = require(__dirname+'/../lib/middleware/permission/haspermsmiddleware.js')
	, isLoggedIn = require(__dirname+'/../lib/middleware/permission/isloggedin.js')
	, paramConverter = require(__dirname+'/../lib/middleware/input/paramconverter.js')
	, useSession = require(__dirname+'/../lib/middleware/permission/usesession.js')
	, sessionRefresh = require(__dirname+'/../lib/middleware/permission/sessionrefresh.js')
	, csrf = require(__dirname+'/../lib/middleware/misc/csrfmiddleware.js')
	, setMinimal = require(__dirname+'/../lib/middleware/misc/setminimal.js')
	, { setBoardLanguage, setQueryLanguage } = require(__dirname+'/../lib/middleware/locale/locale.js')
	//page models
	, { manageRecent, manageReports, manageAssets, manageSettings, manageBans, manageFilters, editFilter, editCustomPage, manageMyPermissions,
		manageBoard, manageThread, manageLogs, manageCatalog, manageCustomPages, manageStaff, editStaff, editPost } = require(__dirname+'/../models/pages/manage/')
	, { globalManageSettings, globalManageReports, globalManageBans, globalManageBoards, globalManageFilters, globalEditFilter, editNews, editAccount, editRole,
		globalManageRecent, globalManageAccounts, globalManageNews, globalManageLogs, globalManageRoles } = require(__dirname+'/../models/pages/globalmanage/')
	, { changePassword, blockBypass, home, register, login, create, myPermissions, sessions, setupTwoFactor,
		board, catalog, banners, boardSettings, globalSettings, randombanner, news, captchaPage, overboard, overboardCatalog,
		captcha, thread, modlog, modloglist, account, boardlist, customPage, csrfPage, noncePage } = require(__dirname+'/../models/pages/')
	, threadParamConverter = paramConverter({ processThreadIdParam: true })
	, logParamConverter = paramConverter({ processDateParam: true })
	, filterParamConverter = paramConverter({ objectIdParams: ['filterid'] })
	, newsParamConverter = paramConverter({ objectIdParams: ['newsid'] })
	, roleParamConverter = paramConverter({ objectIdParams: ['roleid'] })
	, custompageParamConverter = paramConverter({ objectIdParams: ['custompageid'] });

//homepage
router.get('/index.html', home);

//news page
router.get('/news.html', news);

//board list
router.get('/boards.(html|json)', boardlist);

//overboard
router.get('/overboard.(html|json)', overboard); //overboard
router.get('/catalog.(html|json)', overboardCatalog); //overboard catalog view

//board pages
router.get('/:board/:page(1[0-9]{1,}|[2-9][0-9]{0,}|index).(html|json)', Boards.exists, setBoardLanguage, board); //index
router.get('/:board/thread/:id([1-9][0-9]{0,}).(html|json)', Boards.exists, setBoardLanguage, threadParamConverter, Posts.threadExistsMiddleware, thread); //thread view
router.get('/:board/catalog.(html|json)', Boards.exists, setBoardLanguage, catalog); //catalog
router.get('/:board/logs.(html|json)', Boards.exists, setBoardLanguage, modloglist);//modlog list
router.get('/:board/logs/:date(\\d{2}-\\d{2}-\\d{4}).(html|json)', Boards.exists, setBoardLanguage, logParamConverter, modlog); //daily log
router.get('/:board/custompage/:page.(html|json)', Boards.exists, setBoardLanguage, customPage); //board custom page
router.get('/:board/banners.(html|json)', Boards.exists, setBoardLanguage, banners); //banners
router.get('/:board/settings.json', Boards.exists, setBoardLanguage, boardSettings); //public board settings
router.get('/settings.json', globalSettings); //public global settings
router.get('/randombanner', randombanner); //random banner

//board manage pages
router.get('/:board/manage/catalog.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, 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, setBoardLanguage, 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, setBoardLanguage, threadParamConverter, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), csrf, Posts.threadExistsMiddleware, manageThread);
router.get('/:board/manage/editpost/:id([1-9][0-9]{0,}).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, threadParamConverter, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), csrf, Posts.postExistsMiddleware, editPost);
router.get('/:board/manage/reports.(html|json)', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), csrf, manageReports);
router.get('/:board/manage/recent.(html|json)', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), csrf, manageRecent);
router.get('/:board/manage/mypermissions.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_GENERAL), manageMyPermissions);
router.get('/:board/manage/logs.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_LOGS), csrf, manageLogs);
router.get('/:board/manage/bans.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_BANS), csrf, manageBans);
router.get('/:board/manage/settings.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_SETTINGS), csrf, manageSettings);
router.get('/:board/manage/assets.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), csrf, manageAssets);
router.get('/:board/manage/custompages.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, 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, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_CUSTOMISATION), csrf, custompageParamConverter, editCustomPage);
router.get('/:board/manage/staff.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, 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, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_STAFF), csrf, editStaff);
router.get('/:board/manage/filters.html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_SETTINGS), csrf, manageFilters);
router.get('/:board/manage/editfilter/:filterid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, setBoardLanguage, calcPerms,
	hasPerms.one(Permissions.MANAGE_BOARD_SETTINGS), csrf, filterParamConverter, editFilter);

//global manage pages
router.get('/globalmanage/reports.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_GENERAL), csrf, globalManageReports);
router.get('/globalmanage/recent.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_GENERAL), csrf, globalManageRecent);
router.get('/globalmanage/globallogs.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_LOGS), csrf, globalManageLogs);
router.get('/globalmanage/bans.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_BANS), csrf, globalManageBans);
router.get('/globalmanage/boards.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_BOARDS), globalManageBoards);
router.get('/globalmanage/news.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_NEWS), csrf, globalManageNews);
router.get('/globalmanage/accounts.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_ACCOUNTS), csrf, globalManageAccounts);
router.get('/globalmanage/roles.(html|json)', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_ROLES), csrf, globalManageRoles);
router.get('/globalmanage/filters.html', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_SETTINGS), csrf, globalManageFilters);
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/editfilter/:filterid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_SETTINGS), csrf, filterParamConverter, globalEditFilter);
router.get('/globalmanage/editaccount/:accountusername([a-zA-Z0-9]{1,50}).html', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_ACCOUNTS), csrf, editAccount);
router.get('/globalmanage/editrole/:roleid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, calcPerms,
	hasPerms.one(Permissions.MANAGE_GLOBAL_ROLES), csrf, roleParamConverter, editRole);

//captcha
router.get('/captcha', geoIp, processIp, captcha); //get captcha image and cookie
router.get('/captcha.html', captchaPage); //iframed for noscript users
router.get('/bypass.html', blockBypass); //block bypass page
router.get('/bypass_minimal.html', setMinimal, setQueryLanguage, blockBypass); //block bypass page

//accounts
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, myPermissions);
router.get('/twofactor.html', useSession, sessionRefresh, isLoggedIn, calcPerms, csrf, setupTwoFactor);
router.get('/sessions.html', useSession, sessionRefresh, isLoggedIn, calcPerms, csrf, sessions);
router.get('/nonce/:address([a-zA-Z0-9]{42}).json', noncePage); //nonce for web3 logins
router.get('/login.html', login);
router.get('/register.html', register);
router.get('/changepassword.html', changePassword);
router.get('/create.html', useSession, sessionRefresh, isLoggedIn, create); //create new board
router.get('/csrf.json', useSession, sessionRefresh, isLoggedIn, csrf, csrfPage); //just the token, for 3rd party stuff posting

module.exports = router;


code:
'use strict';

const Mongo = require(__dirname+'/db.js')
	, db = Mongo.db.collection('accounts')
	, bcrypt = require('bcrypt')
	, cache = require(__dirname+'/../lib/redis/redis.js')
	, { MONTH } = require(__dirname+'/../lib/converter/timeutils.js')
	, { Permissions } = require(__dirname+'/../lib/permission/permissions.js');

module.exports = {

	db,

	countUsers: (usernames) => {
		return db.countDocuments({
			'_id': {
				'$in': usernames
			}
		});
	},

	count: (filter) => {
		if (filter) {
			return db.countDocuments(filter);
		} else {
			return db.estimatedDocumentCount();
		}
	},

	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, permissions, web3=false) => {
		// hash the password
		let passwordHash;
		if (password) {
			passwordHash = await bcrypt.hash(password, 12);
		}
		//add to db
		const res = await db.insertOne({
			'_id': username,
			original,
			passwordHash,
			'permissions': Mongo.Binary(permissions.array),
			'ownedBoards': [],
			'staffBoards': [],
			'twofactor': null,
			web3,
		});
		cache.del(`users:${username}`);
		return res;
	},

	changePassword: async (username, newPassword) => {
		const passwordHash = await bcrypt.hash(newPassword, 12);
		const res = await db.updateOne({
			'_id': username
		}, {
			'$set': {
				'passwordHash': passwordHash
			}
		});
		cache.del(`users:${username}`);
		return res;
	},

	setAccountPermissions: async (username, permissions) => {
		const res = await db.updateOne({
			'_id': username
		}, {
			'$set': {
				'permissions': Mongo.Binary(permissions.array),
			}
		});
		cache.del(`users:${username}`);
		return res;
	},

	setNewRolePermissions: async (oldPermissions, permissions) => {
		const res = await db.updateMany({
			'permissions': Mongo.Binary(oldPermissions.array),
		}, {
			'$set': {
				'permissions': Mongo.Binary(permissions.array),
			}
		});
		cache.deletePattern('users:*');
		return res;
	},

	updateLastActiveDate: (username) => {
		return db.updateOne({
			'_id': username
		}, {
			'$set': {
				lastActiveDate: new Date()
			}
		});
	},

	updateTwofactor: (username, secret) => {
		return db.updateOne({
			'_id': username
		}, {
			'$set': {
				'twofactor': secret
			}
		});
	},

	getInactive: (duration=(MONTH*3)) => {
		return db.find({
			'permissions': {
				'$not': {
					//exempts ROOT users from being returned
					'$bitsAllSet': [Permissions.ROOT],
				},
			},
			'lastActiveDate': {
				'$lt': new Date(Date.now() - duration),
			},
		}).toArray();
	},

	find: (filter, skip=0, limit=0) => {
		return db.find(filter, {
			'projection': {
				'passwordHash': 0
			}
		}).skip(skip).limit(limit).toArray();
	},

	deleteOne: async (username) => {
		const res = await db.deleteOne({
			'_id': username
		});
		cache.del(`users:${username}`);
		return res;
	},

	deleteMany: async (usernames) => {
		const res = await db.deleteMany({
			'_id': {
				'$in': usernames
			}
		});
		cache.del(usernames.map(n => `users:${n}`));
		return res;
	},

	addOwnedBoard: async (username, board) => {
		const res = await db.updateOne({
			'_id': username
		}, {
			'$addToSet': {
				'ownedBoards': board
			}
		});
		cache.del(`users:${username}`);
		return res;
	},

	removeOwnedBoard: async (username, board) => {
		const res = await db.updateOne({
			'_id': username
		}, {
			'$pull': {
				'ownedBoards': board
			}
		});
		cache.del(`users:${username}`);
		return res;
	},

	addStaffBoard: async (usernames, board) => {
		const res = await db.updateMany({
			'_id': {
				'$in': usernames
			}
		}, {
			'$addToSet': {
				'staffBoards': board
			}
		});
		cache.del(usernames.map(n => `users:${n}`));
		return res;
	},

	removeStaffBoard: async (usernames, board) => {
		const res = await db.updateMany({
			'_id': {
				'$in': usernames
			}
		}, {
			'$pull': {
				'staffBoards': board
			}
		});
		cache.del(usernames.map(n => `users:${n}`));
		return res;
	},

	clearStaffAndOwnedBoards: async (usernames) => {
		const res = await db.updateMany({
			'_id': {
				'$in': usernames
			}
		}, {
			'$set': {
				'staffBoards': [],
				'ownedBoards': [],
			}
		});
		cache.del(usernames.map(n => `users:${n}`));
		return res;
	},

	getOwnedOrStaffBoards: (usernames) => {
		return db.find({
			'_id': {
				'$in': usernames
			},
			'$or': [
				{
					'ownedBoards.0': {
						'$exists': true
					},
				},
				{
					'staffBoards.0': {
						'$exists': true
					}
				}
			]
		}, {
			'projection': {
				'ownedBoards': 1,
				'staffBoards': 1,
			}
		}).toArray();
	},

	deleteAll: () => {
		return db.deleteMany({});
	},

};


code:
'use strict';

const Mongo = require(__dirname+'/db.js')
	, db = Mongo.db.collection('bans');

module.exports = {

	db,

	find: (ip, board) => {
		let ipQuery;
		if (typeof ip === 'object') {
			ipQuery = {
				'$in': [
					ip.cloak, //full ip
					ip.cloak.split('.').slice(0,2).join('.'), //qrange
					ip.cloak.split('.').slice(0,1).join('.'), //hrange
				],
			};
		} else {
			ipQuery = ip;
		}
		return db.find({
			'ip.cloak': ipQuery,
			'board': {
				'$in': [board, null]
			}
		}).toArray();
	},

	get: (ids, board) => {
		return db.find({
			'board': board,
			'_id': {
				'$in': ids
			},
		}).toArray();
	},

	upgrade: async (board, ids, upgradeType) => {
		const substrProjection = upgradeType === 1
			? ['$ip.cloak', 0, 16]
			: ['$ip.cloak', 0, 8];
		const aggregateCursor = await db.aggregate([
			{
				'$match': {
					'_id': {
						'$in': ids,
					},
					'board': board,
					//bypass or pruned IP bans aren't upgraded, duh!
					'ip.type': {
						'$lt': 2,
					},
					//dont allow half -> quarter
					'range': {
						'$lt': upgradeType,
					}
				}
			}, {
				'$project': {
					'_id': 1,
					'board': 1,
					'range': {
						//mongoloidDB
						'$literal': upgradeType,
					},
					'ip.cloak': {
						'$substr': substrProjection,
					},
					'ip.raw': '$ip.raw',
					'ip.type': '$ip.type',
				}
			}, {
				'$merge': {
					'into': 'bans',
				}
			}
		]);
		//changing the order of these will result in a differene explain() output! nice.
		const aggregateExplained = await aggregateCursor.explain();
		await aggregateCursor.toArray();
		return aggregateExplained;
	},

	markSeen: (ids) => {
		return db.updateMany({
			'_id': {
				'$in': ids
			}
		}, {
			'$set': {
				'seen': true,
			}
		});
	},

	appeal: (ip, ids, appeal) => {
		return db.updateMany({
			'_id': {
				'$in': ids
			},
			'ip.cloak': ip,
			'allowAppeal': true,
			'appeal': null
		}, {
			'$set': {
				'appeal': appeal,
			}
		});
	},

	getGlobalBans: () => {
		return db.find({
			'board': null
		}).sort({
			'allowAppeal': -1,
			'appeal': -1,
			'date': -1,
		}).toArray();
	},

	getBoardBans: (board) => {
		return db.find({
			'board': board,
		}).sort({
			'allowAppeal': -1,
			'appeal': -1,
			'date': -1,
		}).toArray();
	},

	denyAppeal: (board, ids) => {
		return db.updateMany({
			'board': board,
			'_id': {
				'$in': ids
			},
/*
			//allow denying appeal even if missing, to allow moving ban to bottom of list
			'allowAppeal': true,
			'appeal': {
				'$ne': null
			},
*/
		}, {
			'$set': {
				'allowAppeal': false,
			}
		});
	},

	editDuration: (board, ids, newExpireAt) => {
		return db.updateMany({
			'board': board,
			'_id': {
				'$in': ids
			},
		}, {
			'$set': {
				'expireAt': newExpireAt,
			}
		});
	},

	editNote: (board, ids, newNote) => {
		return db.updateMany({
			'board': board,
			'_id': {
				'$in': ids
			},
		}, {
			'$set': {
				'note': newNote,
			}
		});
	},

	removeMany: (board, ids) => {
		return db.deleteMany({
			'board': board,
			'_id': {
				'$in': ids
			}
		});
	},

	deleteBoard: (board) => {
		return db.deleteMany({
			'board': board
		});
	},

	insertOne: (ban) => {
		return db.insertOne(ban);
	},

	insertMany: (bans) => {
		return db.insertMany(bans);
	},

	deleteAll: () => {
		return db.deleteMany({});
	},

};