middlewares.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. const debug = require('debug')('growi:lib:middlewares');
  2. const logger = require('@alias/logger')('growi:lib:middlewares');
  3. const pathUtils = require('@commons/util/path-utils');
  4. const md5 = require('md5');
  5. const entities = require('entities');
  6. exports.csrfKeyGenerator = function(crowi, app) {
  7. return function(req, res, next) {
  8. var csrfKey = (req.session && req.session.id) || 'anon';
  9. if (req.csrfToken === null) {
  10. req.csrfToken = crowi.getTokens().create(csrfKey);
  11. }
  12. next();
  13. };
  14. };
  15. exports.loginChecker = function(crowi, app) {
  16. const User = crowi.model('User');
  17. return async function(req, res, next) {
  18. let user = null;
  19. try {
  20. // session に user object が入ってる
  21. if (req.session.user && '_id' in req.session.user) {
  22. user = await User.findById(req.session.user._id).populate(User.IMAGE_POPULATION);
  23. }
  24. req.user = req.session.user = user;
  25. res.locals.user = req.user;
  26. next();
  27. }
  28. catch (err) {
  29. next(err);
  30. }
  31. };
  32. };
  33. exports.loginCheckerForPassport = function(crowi, app) {
  34. return function(req, res, next) {
  35. res.locals.user = req.user;
  36. next();
  37. };
  38. };
  39. exports.csrfVerify = function(crowi, app) {
  40. return function(req, res, next) {
  41. var token = req.body._csrf || req.query._csrf || null;
  42. var csrfKey = (req.session && req.session.id) || 'anon';
  43. debug('req.skipCsrfVerify', req.skipCsrfVerify);
  44. if (req.skipCsrfVerify) {
  45. debug('csrf verify skipped');
  46. return next();
  47. }
  48. if (crowi.getTokens().verify(csrfKey, token)) {
  49. debug('csrf successfully verified');
  50. return next();
  51. }
  52. logger.warn('csrf verification failed. return 403', csrfKey, token);
  53. return res.sendStatus(403);
  54. };
  55. };
  56. exports.swigFunctions = function(crowi, app) {
  57. return function(req, res, next) {
  58. require('../util/swigFunctions')(crowi, app, req, res.locals);
  59. next();
  60. };
  61. };
  62. exports.swigFilters = function(crowi, app, swig) {
  63. // define a function for Gravatar
  64. const generateGravatarSrc = function(user) {
  65. const email = user.email || '';
  66. const hash = md5(email.trim().toLowerCase());
  67. return `https://gravatar.com/avatar/${hash}`;
  68. };
  69. // define a function for uploaded picture
  70. const getUploadedPictureSrc = function(user) {
  71. if (user.image) {
  72. return user.image;
  73. }
  74. else if (user.imageAttachment != null) {
  75. return user.imageAttachment.filePathProxied;
  76. }
  77. else {
  78. return '/images/icons/user.svg';
  79. }
  80. };
  81. return function(req, res, next) {
  82. swig.setFilter('path2name', function(string) {
  83. var name = string.replace(/(\/)$/, '');
  84. if (name.match(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/)) { // /.../hoge/YYYY/MM/DD 形式のページ
  85. return name.replace(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/, '$1');
  86. }
  87. if (name.match(/.+\/([^/]+\/\d{4}\/\d{2})$/)) { // /.../hoge/YYYY/MM 形式のページ
  88. return name.replace(/.+\/([^/]+\/\d{4}\/\d{2})$/, '$1');
  89. }
  90. if (name.match(/.+\/([^/]+\/\d{4})$/)) { // /.../hoge/YYYY 形式のページ
  91. return name.replace(/.+\/([^/]+\/\d{4})$/, '$1');
  92. }
  93. return name.replace(/.+\/(.+)?$/, '$1'); // ページの末尾を拾う
  94. });
  95. swig.setFilter('normalizeDateInPath', function(path) {
  96. var patterns = [
  97. [/20(\d{2})(\d{2})(\d{2})(.+)/g, '20$1/$2/$3/$4'],
  98. [/20(\d{2})(\d{2})(\d{2})/g, '20$1/$2/$3'],
  99. [/20(\d{2})(\d{2})(.+)/g, '20$1/$2/$3'],
  100. [/20(\d{2})(\d{2})/g, '20$1/$2'],
  101. [/20(\d{2})_(\d{1,2})_(\d{1,2})_?(.+)/g, '20$1/$2/$3/$4'],
  102. [/20(\d{2})_(\d{1,2})_(\d{1,2})/g, '20$1/$2/$3'],
  103. [/20(\d{2})_(\d{1,2})_?(.+)/g, '20$1/$2/$3'],
  104. [/20(\d{2})_(\d{1,2})/g, '20$1/$2'],
  105. ];
  106. for (var i = 0; i < patterns.length ; i++) {
  107. var mat = patterns[i][0];
  108. var rep = patterns[i][1];
  109. if (path.match(mat)) {
  110. return path.replace(mat, rep);
  111. }
  112. }
  113. return path;
  114. });
  115. swig.setFilter('datetz', function(input, format) {
  116. // timezone
  117. const swigFilters = require('swig-templates/lib/filters');
  118. return swigFilters.date(input, format, app.get('tzoffset'));
  119. });
  120. swig.setFilter('nl2br', function(string) {
  121. return string
  122. .replace(/\n/g, '<br>');
  123. });
  124. swig.setFilter('removeTrailingSlash', function(string) {
  125. return pathUtils.removeTrailingSlash(string);
  126. });
  127. swig.setFilter('presentation', function(string) {
  128. // 手抜き
  129. return string
  130. .replace(/\s(https?.+(jpe?g|png|gif))\s/, '\n\n\n![]($1)\n\n\n');
  131. });
  132. swig.setFilter('gravatar', generateGravatarSrc);
  133. swig.setFilter('uploadedpicture', getUploadedPictureSrc);
  134. swig.setFilter('picture', function(user) {
  135. if (!user) {
  136. return '/images/icons/user.svg';
  137. }
  138. if (user.isGravatarEnabled === true) {
  139. return generateGravatarSrc(user);
  140. }
  141. else {
  142. return getUploadedPictureSrc(user);
  143. }
  144. });
  145. swig.setFilter('encodeHTML', function(string) {
  146. return entities.encodeHTML(string);
  147. });
  148. swig.setFilter('preventXss', function(string) {
  149. return crowi.xss.process(string);
  150. });
  151. swig.setFilter('slice', function(list, start, end) {
  152. return list.slice(start, end);
  153. });
  154. next();
  155. };
  156. };
  157. exports.adminRequired = function() {
  158. return function(req, res, next) {
  159. // check the user logged in
  160. // make sure that req.user isn't username/email string to login which is set by basic-auth-connect
  161. if (req.user != null && (req.user instanceof Object) && '_id' in req.user) {
  162. if (req.user.admin) {
  163. next();
  164. return;
  165. }
  166. return res.redirect('/');
  167. }
  168. return res.redirect('/login');
  169. };
  170. };
  171. /**
  172. * require login handler
  173. *
  174. * @param {any} crowi
  175. * @param {any} app
  176. * @param {boolean} isStrictly whethere strictly restricted (default true)
  177. */
  178. exports.loginRequired = function(crowi, app, isStrictly = true) {
  179. return function(req, res, next) {
  180. var User = crowi.model('User');
  181. // when the route is not strictly restricted
  182. if (!isStrictly) {
  183. var config = req.config;
  184. var Config = crowi.model('Config');
  185. // when allowed to read
  186. if (Config.isGuestAllowedToRead(config)) {
  187. return next();
  188. }
  189. }
  190. // check the user logged in
  191. // make sure that req.user isn't username/email string to login which is set by basic-auth-connect
  192. if (req.user != null && (req.user instanceof Object) && '_id' in req.user) {
  193. if (req.user.status === User.STATUS_ACTIVE) {
  194. // Active の人だけ先に進める
  195. return next();
  196. }
  197. else if (req.user.status === User.STATUS_REGISTERED) {
  198. return res.redirect('/login/error/registered');
  199. }
  200. else if (req.user.status === User.STATUS_SUSPENDED) {
  201. return res.redirect('/login/error/suspended');
  202. }
  203. else if (req.user.status === User.STATUS_INVITED) {
  204. return res.redirect('/login/invited');
  205. }
  206. }
  207. // is api path
  208. var path = req.path || '';
  209. if (path.match(/^\/_api\/.+$/)) {
  210. return res.sendStatus(403);
  211. }
  212. req.session.jumpTo = req.originalUrl;
  213. return res.redirect('/login');
  214. };
  215. };
  216. exports.accessTokenParser = function(crowi, app) {
  217. return function(req, res, next) {
  218. // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
  219. var accessToken = req.query.access_token || req.body.access_token || null;
  220. if (!accessToken) {
  221. return next();
  222. }
  223. var User = crowi.model('User');
  224. debug('accessToken is', accessToken);
  225. User.findUserByApiToken(accessToken)
  226. .then(function(userData) {
  227. req.user = userData;
  228. req.skipCsrfVerify = true;
  229. debug('Access token parsed: skipCsrfVerify');
  230. next();
  231. }).catch(function(err) {
  232. next();
  233. });
  234. };
  235. };
  236. // this is for Installer
  237. exports.applicationNotInstalled = function() {
  238. return function(req, res, next) {
  239. const config = req.config;
  240. if (Object.keys(config.crowi).length !== 0) {
  241. req.flash('errorMessage', 'Application already installed.');
  242. return res.redirect('admin'); // admin以外はadminRequiredで'/'にリダイレクトされる
  243. }
  244. return next();
  245. };
  246. };
  247. exports.applicationInstalled = function() {
  248. return function(req, res, next) {
  249. var config = req.config;
  250. if (Object.keys(config.crowi).length === 0) {
  251. return res.redirect('/installer');
  252. }
  253. return next();
  254. };
  255. };
  256. exports.awsEnabled = function() {
  257. return function(req, res, next) {
  258. var config = req.config;
  259. if (config.crowi['aws:region'] !== '' && config.crowi['aws:bucket'] !== '' && config.crowi['aws:accessKeyId'] !== '' && config.crowi['aws:secretAccessKey'] !== '') {
  260. req.flash('globalError', 'AWS settings required to use this function. Please ask the administrator.');
  261. return res.redirect('/');
  262. }
  263. return next();
  264. };
  265. };