middlewares.js 9.4 KB

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