middlewares.js 8.4 KB

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