mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	refactor: async/await middleware
This commit is contained in:
		| @@ -1,44 +1,38 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
| var nconf = require('nconf'); | ||||
| var meta = require('../meta'); | ||||
| var user = require('../user'); | ||||
| const util = require('util'); | ||||
| const nconf = require('nconf'); | ||||
| const meta = require('../meta'); | ||||
| const user = require('../user'); | ||||
|  | ||||
| module.exports = function (middleware) { | ||||
| 	middleware.maintenanceMode = function maintenanceMode(req, res, callback) { | ||||
| 	middleware.maintenanceMode = async function maintenanceMode(req, res, next) { | ||||
| 		if (!meta.config.maintenanceMode) { | ||||
| 			return setImmediate(callback); | ||||
| 			return setImmediate(next); | ||||
| 		} | ||||
| 		var url = req.url.replace(nconf.get('relative_path'), ''); | ||||
|  | ||||
| 		const url = req.url.replace(nconf.get('relative_path'), ''); | ||||
| 		if (url.startsWith('/login') || url.startsWith('/api/login')) { | ||||
| 			return setImmediate(callback); | ||||
| 			return setImmediate(next); | ||||
| 		} | ||||
| 		var data; | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				user.isAdministrator(req.uid, next); | ||||
| 			}, | ||||
| 			function (isAdmin, next) { | ||||
| 				if (isAdmin) { | ||||
| 					return callback(); | ||||
| 				} | ||||
| 				res.status(meta.config.maintenanceModeStatus); | ||||
| 				data = { | ||||
| 					site_title: meta.config.title || 'NodeBB', | ||||
| 					message: meta.config.maintenanceModeMessage, | ||||
| 				}; | ||||
|  | ||||
| 				if (res.locals.isAPI) { | ||||
| 					return res.json(data); | ||||
| 				} | ||||
| 		const isAdmin = await user.isAdministrator(req.uid); | ||||
| 		if (isAdmin) { | ||||
| 			return setImmediate(next); | ||||
| 		} | ||||
|  | ||||
| 				middleware.buildHeader(req, res, next); | ||||
| 			}, | ||||
| 			function () { | ||||
| 				res.render('503', data); | ||||
| 			}, | ||||
| 		], callback); | ||||
| 		res.status(meta.config.maintenanceModeStatus); | ||||
|  | ||||
| 		const data = { | ||||
| 			site_title: meta.config.title || 'NodeBB', | ||||
| 			message: meta.config.maintenanceModeMessage, | ||||
| 		}; | ||||
|  | ||||
| 		if (res.locals.isAPI) { | ||||
| 			return res.json(data); | ||||
| 		} | ||||
| 		const buildHeaderAsync = util.promisify(middleware.buildHeader); | ||||
| 		await buildHeaderAsync(req, res); | ||||
| 		res.render('503', data); | ||||
| 	}; | ||||
| }; | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var winston = require('winston'); | ||||
| const winston = require('winston'); | ||||
|  | ||||
| var ratelimit = module.exports; | ||||
| const ratelimit = module.exports; | ||||
|  | ||||
| var allowedCalls = 100; | ||||
| var timeframe = 10000; | ||||
| const allowedCalls = 100; | ||||
| const timeframe = 10000; | ||||
|  | ||||
| ratelimit.isFlooding = function (socket) { | ||||
| 	socket.callsPerSecond = socket.callsPerSecond || 0; | ||||
| @@ -14,7 +14,7 @@ ratelimit.isFlooding = function (socket) { | ||||
|  | ||||
| 	socket.callsPerSecond += 1; | ||||
|  | ||||
| 	var now = Date.now(); | ||||
| 	const now = Date.now(); | ||||
| 	socket.elapsedTime += now - socket.lastCallTime; | ||||
|  | ||||
| 	if (socket.callsPerSecond > allowedCalls && socket.elapsedTime < timeframe) { | ||||
|   | ||||
| @@ -1,118 +1,89 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
| var nconf = require('nconf'); | ||||
| var validator = require('validator'); | ||||
| var winston = require('winston'); | ||||
| const util = require('util'); | ||||
| const nconf = require('nconf'); | ||||
| const validator = require('validator'); | ||||
| const winston = require('winston'); | ||||
|  | ||||
| var plugins = require('../plugins'); | ||||
| var meta = require('../meta'); | ||||
| var translator = require('../translator'); | ||||
| var widgets = require('../widgets'); | ||||
| var utils = require('../utils'); | ||||
| const plugins = require('../plugins'); | ||||
| const meta = require('../meta'); | ||||
| const translator = require('../translator'); | ||||
| const widgets = require('../widgets'); | ||||
| const utils = require('../utils'); | ||||
|  | ||||
| module.exports = function (middleware) { | ||||
| 	const renderHeaderFooterAsync = util.promisify(renderHeaderFooter); | ||||
|  | ||||
| 	middleware.processRender = function processRender(req, res, next) { | ||||
| 		// res.render post-processing, modified from here: https://gist.github.com/mrlannigan/5051687 | ||||
| 		var render = res.render; | ||||
| 		res.render = function renderOverride(template, options, fn) { | ||||
| 			var self = this; | ||||
| 			var req = this.req; | ||||
| 			var defaultFn = function (err, str) { | ||||
| 				if (err) { | ||||
| 					return next(err); | ||||
| 				} | ||||
| 				self.send(str); | ||||
| 			}; | ||||
| 		const render = res.render; | ||||
| 		res.render = async function renderOverride(template, options, fn) { | ||||
| 			const self = this; | ||||
| 			const req = this.req; | ||||
|  | ||||
| 			options = options || {}; | ||||
| 			if (typeof options === 'function') { | ||||
| 				fn = options; | ||||
| 				options = {}; | ||||
| 			} | ||||
| 			if (typeof fn !== 'function') { | ||||
| 				fn = defaultFn; | ||||
|  | ||||
| 			options.loggedIn = req.uid > 0; | ||||
| 			options.relative_path = nconf.get('relative_path'); | ||||
| 			options.template = { name: template, [template]: true }; | ||||
| 			options.url = (req.baseUrl + req.path.replace(/^\/api/, '')); | ||||
| 			options.bodyClass = buildBodyClass(req, res, options); | ||||
|  | ||||
| 			const buildResult = await plugins.fireHook('filter:' + template + '.build', { req: req, res: res, templateData: options }); | ||||
| 			const templateToRender = buildResult.templateData.templateToRender || template; | ||||
|  | ||||
| 			const renderResult = await plugins.fireHook('filter:middleware.render', { req: req, res: res, templateData: buildResult.templateData }); | ||||
| 			options = renderResult.templateData; | ||||
| 			options._header = { | ||||
| 				tags: await meta.tags.parse(req, renderResult, res.locals.metaTags, res.locals.linkTags), | ||||
| 			}; | ||||
| 			options.widgets = await widgets.render(req.uid, { | ||||
| 				template: template + '.tpl', | ||||
| 				url: options.url, | ||||
| 				templateData: options, | ||||
| 				req: req, | ||||
| 				res: res, | ||||
| 			}); | ||||
| 			res.locals.template = template; | ||||
| 			options._locals = undefined; | ||||
|  | ||||
| 			if (res.locals.isAPI) { | ||||
| 				if (req.route && req.route.path === '/api/') { | ||||
| 					options.title = '[[pages:home]]'; | ||||
| 				} | ||||
| 				req.app.set('json spaces', global.env === 'development' || req.query.pretty ? 4 : 0); | ||||
| 				return res.json(options); | ||||
| 			} | ||||
| 			const ajaxifyData = JSON.stringify(options).replace(/<\//g, '<\\/'); | ||||
|  | ||||
| 			var ajaxifyData; | ||||
| 			var templateToRender; | ||||
| 			async.waterfall([ | ||||
| 				function (next) { | ||||
| 					options.loggedIn = req.uid > 0; | ||||
| 					options.relative_path = nconf.get('relative_path'); | ||||
| 					options.template = { name: template }; | ||||
| 					options.template[template] = true; | ||||
| 					options.url = (req.baseUrl + req.path.replace(/^\/api/, '')); | ||||
| 					options.bodyClass = buildBodyClass(req, res, options); | ||||
| 					plugins.fireHook('filter:' + template + '.build', { req: req, res: res, templateData: options }, next); | ||||
| 				}, | ||||
| 				function (data, next) { | ||||
| 					templateToRender = data.templateData.templateToRender || template; | ||||
| 					plugins.fireHook('filter:middleware.render', { req: req, res: res, templateData: data.templateData }, next); | ||||
| 				}, | ||||
| 				function parseTags(data, next) { | ||||
| 					meta.tags.parse(req, data, res.locals.metaTags, res.locals.linkTags, function (err, tags) { | ||||
| 						options._header = { | ||||
| 							tags: tags, | ||||
| 						}; | ||||
| 						next(err, data); | ||||
| 					}); | ||||
| 				}, | ||||
| 				function (data, next) { | ||||
| 					options = data.templateData; | ||||
| 			const renderAsync = util.promisify((templateToRender, options, next) => render.call(self, templateToRender, options, next)); | ||||
|  | ||||
| 					widgets.render(req.uid, { | ||||
| 						template: template + '.tpl', | ||||
| 						url: options.url, | ||||
| 						templateData: options, | ||||
| 						req: req, | ||||
| 						res: res, | ||||
| 					}, next); | ||||
| 				}, | ||||
| 				function (data, next) { | ||||
| 					options.widgets = data; | ||||
| 			const results = await utils.promiseParallel({ | ||||
| 				header: renderHeaderFooterAsync('renderHeader', req, res, options), | ||||
| 				content: renderAsync(templateToRender, options), | ||||
| 				footer: renderHeaderFooterAsync('renderFooter', req, res, options), | ||||
| 			}); | ||||
|  | ||||
| 					res.locals.template = template; | ||||
| 					options._locals = undefined; | ||||
| 			const str = results.header + | ||||
| 				(res.locals.postHeader || '') + | ||||
| 				results.content + '<script id="ajaxify-data"></script>' + | ||||
| 				(res.locals.preFooter || '') + | ||||
| 				results.footer; | ||||
|  | ||||
| 					if (res.locals.isAPI) { | ||||
| 						if (req.route && req.route.path === '/api/') { | ||||
| 							options.title = '[[pages:home]]'; | ||||
| 						} | ||||
| 						req.app.set('json spaces', global.env === 'development' || req.query.pretty ? 4 : 0); | ||||
| 						return res.json(options); | ||||
| 					} | ||||
| 			let translated = await translate(str, req, res); | ||||
| 			translated = translated.replace('<script id="ajaxify-data"></script>', function () { | ||||
| 				return '<script id="ajaxify-data" type="application/json">' + ajaxifyData + '</script>'; | ||||
| 			}); | ||||
|  | ||||
| 					ajaxifyData = JSON.stringify(options).replace(/<\//g, '<\\/'); | ||||
|  | ||||
| 					async.parallel({ | ||||
| 						header: function (next) { | ||||
| 							renderHeaderFooter('renderHeader', req, res, options, next); | ||||
| 						}, | ||||
| 						content: function (next) { | ||||
| 							render.call(self, templateToRender, options, next); | ||||
| 						}, | ||||
| 						footer: function (next) { | ||||
| 							renderHeaderFooter('renderFooter', req, res, options, next); | ||||
| 						}, | ||||
| 					}, next); | ||||
| 				}, | ||||
| 				function (results, next) { | ||||
| 					var str = results.header + | ||||
| 						(res.locals.postHeader || '') + | ||||
| 						results.content + '<script id="ajaxify-data"></script>' + | ||||
| 						(res.locals.preFooter || '') + | ||||
| 						results.footer; | ||||
|  | ||||
| 					translate(str, req, res, next); | ||||
| 				}, | ||||
| 				function (translated, next) { | ||||
| 					translated = translated.replace('<script id="ajaxify-data"></script>', function () { | ||||
| 						return '<script id="ajaxify-data" type="application/json">' + ajaxifyData + '</script>'; | ||||
| 					}); | ||||
| 					next(null, translated); | ||||
| 				}, | ||||
| 			], fn); | ||||
| 			if (typeof fn !== 'function') { | ||||
| 				self.send(translated); | ||||
| 			} else { | ||||
| 				fn(null, translated); | ||||
| 			} | ||||
| 		}; | ||||
|  | ||||
| 		next(); | ||||
| @@ -128,20 +99,19 @@ module.exports = function (middleware) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	function translate(str, req, res, next) { | ||||
| 		var language = (res.locals.config && res.locals.config.userLang) || 'en-GB'; | ||||
| 	async function translate(str, req, res) { | ||||
| 		let language = (res.locals.config && res.locals.config.userLang) || 'en-GB'; | ||||
| 		if (res.locals.renderAdminHeader) { | ||||
| 			language = (res.locals.config && res.locals.config.acpLang) || 'en-GB'; | ||||
| 		} | ||||
| 		language = req.query.lang ? validator.escape(String(req.query.lang)) : language; | ||||
| 		translator.translate(str, language, function (translated) { | ||||
| 			next(null, translator.unescape(translated)); | ||||
| 		}); | ||||
| 		const translated = await translator.translate(str, language); | ||||
| 		return translator.unescape(translated); | ||||
| 	} | ||||
|  | ||||
| 	function buildBodyClass(req, res, templateData) { | ||||
| 		var clean = req.path.replace(/^\/api/, '').replace(/^\/|\/$/g, ''); | ||||
| 		var parts = clean.split('/').slice(0, 3); | ||||
| 		const clean = req.path.replace(/^\/api/, '').replace(/^\/|\/$/g, ''); | ||||
| 		const parts = clean.split('/').slice(0, 3); | ||||
| 		parts.forEach(function (p, index) { | ||||
| 			try { | ||||
| 				p = utils.slugify(decodeURIComponent(p)); | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var async = require('async'); | ||||
| var nconf = require('nconf'); | ||||
| var winston = require('winston'); | ||||
| const util = require('util'); | ||||
| const nconf = require('nconf'); | ||||
| const winston = require('winston'); | ||||
|  | ||||
| var meta = require('../meta'); | ||||
| var user = require('../user'); | ||||
| var privileges = require('../privileges'); | ||||
| var plugins = require('../plugins'); | ||||
| const meta = require('../meta'); | ||||
| const user = require('../user'); | ||||
| const privileges = require('../privileges'); | ||||
| const plugins = require('../plugins'); | ||||
|  | ||||
| var auth = require('../routes/authentication'); | ||||
| const auth = require('../routes/authentication'); | ||||
|  | ||||
| var controllers = { | ||||
| const controllers = { | ||||
| 	helpers: require('../controllers/helpers'), | ||||
| }; | ||||
|  | ||||
| @@ -49,6 +49,8 @@ module.exports = function (middleware) { | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	const authenticateAsync = util.promisify(middleware.authenticate); | ||||
|  | ||||
| 	middleware.authenticateOrGuest = function authenticateOrGuest(req, res, next) { | ||||
| 		authenticate(req, res, next, next); | ||||
| 	}; | ||||
| @@ -61,30 +63,21 @@ module.exports = function (middleware) { | ||||
| 		ensureSelfOrMethod(user.isPrivileged, req, res, next); | ||||
| 	}; | ||||
|  | ||||
| 	function ensureSelfOrMethod(method, req, res, next) { | ||||
| 	async function ensureSelfOrMethod(method, req, res, next) { | ||||
| 		/* | ||||
| 			The "self" part of this middleware hinges on you having used | ||||
| 			middleware.exposeUid prior to invoking this middleware. | ||||
| 		*/ | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				if (!req.loggedIn) { | ||||
| 					return setImmediate(next, null, false); | ||||
| 				} | ||||
|  | ||||
| 				if (req.uid === parseInt(res.locals.uid, 10)) { | ||||
| 					return setImmediate(next, null, true); | ||||
| 				} | ||||
|  | ||||
| 				method(req.uid, next); | ||||
| 			}, | ||||
| 			function (allowed, next) { | ||||
| 				if (!allowed) { | ||||
| 					return controllers.helpers.notAllowed(req, res); | ||||
| 				} | ||||
| 				next(); | ||||
| 			}, | ||||
| 		], next); | ||||
| 		if (!req.loggedIn) { | ||||
| 			return controllers.helpers.notAllowed(req, res); | ||||
| 		} | ||||
| 		if (req.uid === parseInt(res.locals.uid, 10)) { | ||||
| 			return setImmediate(next); | ||||
| 		} | ||||
| 		const allowed = await method(req.uid); | ||||
| 		if (!allowed) { | ||||
| 			return controllers.helpers.notAllowed(req, res); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	middleware.checkGlobalPrivacySettings = function checkGlobalPrivacySettings(req, res, next) { | ||||
| @@ -92,110 +85,73 @@ module.exports = function (middleware) { | ||||
| 		middleware.canViewUsers(req, res, next); | ||||
| 	}; | ||||
|  | ||||
| 	middleware.canViewUsers = function canViewUsers(req, res, next) { | ||||
| 	middleware.canViewUsers = async function canViewUsers(req, res, next) { | ||||
| 		if (parseInt(res.locals.uid, 10) === req.uid) { | ||||
| 			return next(); | ||||
| 		} | ||||
| 		privileges.global.can('view:users', req.uid, function (err, canView) { | ||||
| 			if (err || canView) { | ||||
| 				return next(err); | ||||
| 			} | ||||
| 			controllers.helpers.notAllowed(req, res); | ||||
| 		}); | ||||
| 		const canView = await privileges.global.can('view:users', req.uid); | ||||
| 		if (canView) { | ||||
| 			return next(); | ||||
| 		} | ||||
| 		controllers.helpers.notAllowed(req, res); | ||||
| 	}; | ||||
|  | ||||
| 	middleware.canViewGroups = function canViewGroups(req, res, next) { | ||||
| 		privileges.global.can('view:groups', req.uid, function (err, canView) { | ||||
| 			if (err || canView) { | ||||
| 				return next(err); | ||||
| 			} | ||||
| 			controllers.helpers.notAllowed(req, res); | ||||
| 		}); | ||||
| 	middleware.canViewGroups = async function canViewGroups(req, res, next) { | ||||
| 		const canView = await privileges.global.can('view:groups', req.uid); | ||||
| 		if (canView) { | ||||
| 			return next(); | ||||
| 		} | ||||
| 		controllers.helpers.notAllowed(req, res); | ||||
| 	}; | ||||
|  | ||||
| 	middleware.checkAccountPermissions = function checkAccountPermissions(req, res, next) { | ||||
| 	middleware.checkAccountPermissions = async function checkAccountPermissions(req, res, next) { | ||||
| 		// This middleware ensures that only the requested user and admins can pass | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				middleware.authenticate(req, res, next); | ||||
| 			}, | ||||
| 			function (next) { | ||||
| 				user.getUidByUserslug(req.params.userslug, next); | ||||
| 			}, | ||||
| 			function (uid, next) { | ||||
| 				privileges.users.canEdit(req.uid, uid, next); | ||||
| 			}, | ||||
| 			function (allowed, next) { | ||||
| 				if (allowed) { | ||||
| 					return next(null, allowed); | ||||
| 				} | ||||
| 		await authenticateAsync(req, res); | ||||
| 		const uid = await user.getUidByUserslug(req.params.userslug); | ||||
| 		let allowed = await privileges.users.canEdit(req.uid, uid); | ||||
| 		if (allowed) { | ||||
| 			return next(); | ||||
| 		} | ||||
|  | ||||
| 				// For the account/info page only, allow plain moderators through | ||||
| 				if (/user\/.+\/info$/.test(req.path)) { | ||||
| 					privileges.global.can('view:users:info', req.uid, next); | ||||
| 				} else { | ||||
| 					next(null, false); | ||||
| 				} | ||||
| 			}, | ||||
| 			function (allowed) { | ||||
| 				if (allowed) { | ||||
| 					return next(); | ||||
| 				} | ||||
| 				controllers.helpers.notAllowed(req, res); | ||||
| 			}, | ||||
| 		], next); | ||||
| 		if (/user\/.+\/info$/.test(req.path)) { | ||||
| 			allowed = await privileges.global.can('view:users:info', req.uid); | ||||
| 		} | ||||
| 		if (allowed) { | ||||
| 			return next(); | ||||
| 		} | ||||
| 		controllers.helpers.notAllowed(req, res); | ||||
| 	}; | ||||
|  | ||||
| 	middleware.redirectToAccountIfLoggedIn = function redirectToAccountIfLoggedIn(req, res, next) { | ||||
| 	middleware.redirectToAccountIfLoggedIn = async function redirectToAccountIfLoggedIn(req, res, next) { | ||||
| 		if (req.session.forceLogin || req.uid <= 0) { | ||||
| 			return next(); | ||||
| 		} | ||||
|  | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				user.getUserField(req.uid, 'userslug', next); | ||||
| 			}, | ||||
| 			function (userslug) { | ||||
| 				controllers.helpers.redirect(res, '/user/' + userslug); | ||||
| 			}, | ||||
| 		], next); | ||||
| 		const userslug = await user.getUserField(req.uid, 'userslug'); | ||||
| 		controllers.helpers.redirect(res, '/user/' + userslug); | ||||
| 	}; | ||||
|  | ||||
| 	middleware.redirectUidToUserslug = function redirectUidToUserslug(req, res, next) { | ||||
| 		var uid = parseInt(req.params.uid, 10); | ||||
| 	middleware.redirectUidToUserslug = async function redirectUidToUserslug(req, res, next) { | ||||
| 		const uid = parseInt(req.params.uid, 10); | ||||
| 		if (uid <= 0) { | ||||
| 			return next(); | ||||
| 		} | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				user.getUserField(uid, 'userslug', next); | ||||
| 			}, | ||||
| 			function (userslug) { | ||||
| 				if (!userslug) { | ||||
| 					return next(); | ||||
| 				} | ||||
| 				var path = req.path.replace(/^\/api/, '') | ||||
| 					.replace('uid', 'user') | ||||
| 					.replace(uid, function () { return userslug; }); | ||||
| 				controllers.helpers.redirect(res, path); | ||||
| 			}, | ||||
| 		], next); | ||||
| 		const userslug = await user.getUserField(uid, 'userslug'); | ||||
| 		if (!userslug) { | ||||
| 			return next(); | ||||
| 		} | ||||
| 		const path = req.path.replace(/^\/api/, '') | ||||
| 			.replace('uid', 'user') | ||||
| 			.replace(uid, function () { return userslug; }); | ||||
| 		controllers.helpers.redirect(res, path); | ||||
| 	}; | ||||
|  | ||||
| 	middleware.redirectMeToUserslug = function redirectMeToUserslug(req, res, next) { | ||||
| 		var uid = req.uid; | ||||
| 		async.waterfall([ | ||||
| 			function (next) { | ||||
| 				user.getUserField(uid, 'userslug', next); | ||||
| 			}, | ||||
| 			function (userslug) { | ||||
| 				if (!userslug) { | ||||
| 					return controllers.helpers.notAllowed(req, res); | ||||
| 				} | ||||
| 				var path = req.path.replace(/^(\/api)?\/me/, '/user/' + userslug); | ||||
| 				controllers.helpers.redirect(res, path); | ||||
| 			}, | ||||
| 		], next); | ||||
| 	middleware.redirectMeToUserslug = async function redirectMeToUserslug(req, res) { | ||||
| 		const userslug = await user.getUserField(req.uid, 'userslug'); | ||||
| 		if (!userslug) { | ||||
| 			return controllers.helpers.notAllowed(req, res); | ||||
| 		} | ||||
| 		const path = req.path.replace(/^(\/api)?\/me/, '/user/' + userslug); | ||||
| 		controllers.helpers.redirect(res, path); | ||||
| 	}; | ||||
|  | ||||
| 	middleware.isAdmin = async function isAdmin(req, res, next) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user