2
0

middlewares.js 9.0 KB

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