middlewares.js 8.1 KB

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