middlewares.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. var debug = require('debug')('crowi:lib:middlewares');
  2. var md5 = require('md5');
  3. exports.csrfKeyGenerator = function(crowi, app) {
  4. return function(req, res, next) {
  5. var csrfKey = (req.session && req.session.id) || 'anon';
  6. if (req.csrfToken === null) {
  7. req.csrfToken = crowi.getTokens().create(csrfKey);
  8. }
  9. next();
  10. }
  11. }
  12. exports.loginChecker = function(crowi, app) {
  13. return function(req, res, next) {
  14. var User = crowi.model('User');
  15. // session に user object が入ってる
  16. if (req.session.user && '_id' in req.session.user) {
  17. User.findById(req.session.user._id, function(err, userData) {
  18. if (err) {
  19. next();
  20. } else {
  21. req.user = req.session.user = userData;
  22. res.locals.user = req.user;
  23. next();
  24. }
  25. });
  26. } else {
  27. req.user = req.session.user = false;
  28. res.locals.user = req.user;
  29. next();
  30. }
  31. };
  32. };
  33. exports.loginCheckerForPassport = function(crowi, app) {
  34. return function(req, res, next) {
  35. if (req.user == null) {
  36. req.user = false;
  37. }
  38. res.locals.user = req.user;
  39. next();
  40. };
  41. };
  42. exports.csrfVerify = function(crowi, app) {
  43. return function(req, res, next) {
  44. var token = req.body._csrf || req.query._csrf || null;
  45. var csrfKey = (req.session && req.session.id) || 'anon';
  46. debug('req.skipCsrfVerify', req.skipCsrfVerify);
  47. if (req.skipCsrfVerify) {
  48. debug('csrf verify skipped');
  49. return next();
  50. }
  51. if (crowi.getTokens().verify(csrfKey, token)) {
  52. debug('csrf successfully verified');
  53. return next();
  54. }
  55. debug('csrf verification failed. return 403', csrfKey, token);
  56. return res.sendStatus(403);
  57. };
  58. };
  59. exports.swigFunctions = function(crowi, app) {
  60. return function(req, res, next) {
  61. require('../util/swigFunctions')(crowi, app, req, res.locals);
  62. next();
  63. };
  64. };
  65. exports.swigFilters = function(app, swig) {
  66. // define a function for Gravatar
  67. const generateGravatarSrc = function(user) {
  68. const email = user.email || '';
  69. const hash = md5(email.trim().toLowerCase());
  70. return `https://gravatar.com/avatar/${hash}`;
  71. };
  72. // define a function for uploaded picture
  73. const getUploadedPictureSrc = function(user) {
  74. if (user.image) {
  75. return user.image;
  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. var 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('removeLastSlash', function(string) {
  125. if (string == '/') {
  126. return string;
  127. }
  128. return string.substr(0, string.length - 1);
  129. });
  130. swig.setFilter('presentation', function(string) {
  131. // 手抜き
  132. return string
  133. .replace(/[\n]+#/g, '\n\n\n#')
  134. .replace(/\s(https?.+(jpe?g|png|gif))\s/, '\n\n\n![]($1)\n\n\n');
  135. });
  136. swig.setFilter('gravatar', generateGravatarSrc);
  137. swig.setFilter('uploadedpicture', getUploadedPictureSrc);
  138. swig.setFilter('picture', function(user) {
  139. if (!user) {
  140. return '/images/icons/user.svg';
  141. }
  142. if (user.isGravatarEnabled === true) {
  143. return generateGravatarSrc(user);
  144. }
  145. else {
  146. return getUploadedPictureSrc(user);
  147. }
  148. });
  149. next();
  150. };
  151. };
  152. exports.adminRequired = function() {
  153. return 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. /**
  167. * require login handler
  168. *
  169. * @param {any} crowi
  170. * @param {any} app
  171. * @param {boolean} isStrictly whethere strictly restricted (default true)
  172. */
  173. exports.loginRequired = function(crowi, app, isStrictly = true) {
  174. return function(req, res, next) {
  175. var User = crowi.model('User')
  176. // when the route is not strictly restricted
  177. if (!isStrictly) {
  178. var config = req.config;
  179. var Config = crowi.model('Config');
  180. // when allowed to read
  181. if (Config.isGuesstAllowedToRead(config)) {
  182. return next();
  183. }
  184. }
  185. // check the user logged in
  186. // make sure that req.user isn't username/email string to login which is set by basic-auth-connect
  187. if (req.user != null && (req.user instanceof Object) && '_id' in req.user) {
  188. if (req.user.status === User.STATUS_ACTIVE) {
  189. // Active の人だけ先に進める
  190. return next();
  191. } else if (req.user.status === User.STATUS_REGISTERED) {
  192. return res.redirect('/login/error/registered');
  193. } else if (req.user.status === User.STATUS_SUSPENDED) {
  194. return res.redirect('/login/error/suspended');
  195. } else if (req.user.status === User.STATUS_INVITED) {
  196. return res.redirect('/login/invited');
  197. }
  198. }
  199. // is api path
  200. var path = req.path || '';
  201. if (path.match(/^\/_api\/.+$/)) {
  202. return res.sendStatus(403);
  203. }
  204. req.session.jumpTo = req.originalUrl;
  205. return res.redirect('/login');
  206. };
  207. };
  208. exports.accessTokenParser = function(crowi, app) {
  209. return function(req, res, next) {
  210. // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
  211. var accessToken = req.query.access_token || req.body.access_token || null;
  212. if (!accessToken) {
  213. return next();
  214. }
  215. var User = crowi.model('User')
  216. debug('accessToken is', accessToken);
  217. User.findUserByApiToken(accessToken)
  218. .then(function(userData) {
  219. req.user = userData;
  220. req.skipCsrfVerify = true;
  221. debug('Access token parsed: skipCsrfVerify');
  222. next();
  223. }).catch(function(err) {
  224. next();
  225. });
  226. };
  227. };
  228. // this is for Installer
  229. exports.applicationNotInstalled = function() {
  230. return function(req, res, next) {
  231. var config = req.config;
  232. if (Object.keys(config.crowi).length !== 1) {
  233. req.flash('errorMessage', 'Application already installed.');
  234. return res.redirect('admin'); // admin以外はadminRequiredで'/'にリダイレクトされる
  235. }
  236. return next();
  237. };
  238. };
  239. exports.checkSearchIndicesGenerated = function(crowi, app) {
  240. return function(req, res, next) {
  241. const searcher = crowi.getSearcher();
  242. // build index
  243. if (searcher) {
  244. searcher.buildIndex()
  245. .then((data) => {
  246. if (!data.errors) {
  247. debug('Index created.');
  248. }
  249. return searcher.addAllPages();
  250. })
  251. .catch((error) => {
  252. if (error.message != null && error.message.match(/index_already_exists_exception/)) {
  253. debug('Creating index is failed: ', error.message);
  254. }
  255. else {
  256. console.log(`Error while building index of Elasticsearch': ${error.message}`);
  257. }
  258. });
  259. }
  260. return next();
  261. };
  262. }
  263. exports.applicationInstalled = function() {
  264. return function(req, res, next) {
  265. var config = req.config;
  266. if (Object.keys(config.crowi).length === 1) { // app:url is set by process
  267. return res.redirect('/installer');
  268. }
  269. return next();
  270. };
  271. };
  272. exports.awsEnabled = function() {
  273. return function (req, res, next) {
  274. var config = req.config;
  275. if (config.crowi['aws:region'] !== '' && config.crowi['aws:bucket'] !== '' && config.crowi['aws:accessKeyId'] !== '' && config.crowi['aws:secretAccessKey'] !== '') {
  276. req.flash('globalError', 'AWS settings required to use this function. Please ask the administrator.');
  277. return res.redirect('/');
  278. }
  279. return next();
  280. };
  281. };