middlewares.js 9.2 KB

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