Sotaro KARASAWA 9 лет назад
Родитель
Сommit
e3d4366d2b

+ 2 - 17
lib/crowi/express-init.js

@@ -1,13 +1,12 @@
 'use strict';
 
 module.exports = function(crowi, app) {
-  var express        = require('express')
+  var debug = require('debug')('crowi:crowi:express-init')
+    , express        = require('express')
     , bodyParser     = require('body-parser')
     , multer         = require('multer')
-    , morgan         = require('morgan')
     , cookieParser   = require('cookie-parser')
     , methodOverride = require('method-override')
-    , errorHandler   = require('errorhandler')
     , session        = require('express-session')
     , basicAuth      = require('basic-auth-connect')
     , flash          = require('connect-flash')
@@ -98,18 +97,4 @@ module.exports = function(crowi, app) {
 
   app.use(middleware.loginChecker(crowi, app));
 
-  if (env == 'development') {
-    swig.setDefaults({ cache: false });
-    app.use(errorHandler({ dumpExceptions: true, showStack: true }));
-    app.use(morgan('dev'));
-  }
-
-  if (env == 'production') {
-    var oneYear = 31557600000;
-    app.use(morgan('combined'));
-    app.use(function (err, req, res, next) {
-      res.status(500);
-      res.render('500', { error: err });
-    });
-  }
 };

+ 43 - 0
lib/crowi/index.js

@@ -31,6 +31,9 @@ function Crowi (rootdir, env)
   this.searcher = null;
   this.mailer = {};
 
+  this.tokens = null;
+  this.csrfToken = null;
+  this.csrfSecret = null;
 
   this.models = {};
 
@@ -81,6 +84,8 @@ Crowi.prototype.init = function() {
     return self.setupMailer();
   }).then(function() {
     return self.setupSlack();
+  }).then(function() {
+    return self.setupCsrf();
   }).then(function() {
     return self.buildServer();
   });
@@ -242,7 +247,27 @@ Crowi.prototype.setupSlack = function() {
   });
 };
 
+Crowi.prototype.setupCsrf = function() {
+  var Tokens = require('csrf');
+  var tokens = this.tokens = new Tokens();
+
+  this.csrfSecret = tokens.secretSync();
+  this.csrfToken = tokens.create(this.csrfSecret);
+
+  return Promise.resolve();
+};
+
+Crowi.prototype.getTokens = function() {
+  return this.tokens;
+};
+
+Crowi.prototype.getCsrfSecret = function() {
+  return this.csrfSecret;
+};
 
+Crowi.prototype.getCsrfToken = function() {
+  return this.csrfToken;
+};
 
 Crowi.prototype.start = function() {
   var self = this
@@ -267,12 +292,30 @@ Crowi.prototype.start = function() {
 
 Crowi.prototype.buildServer = function() {
   var express  = require('express')
+    , errorHandler   = require('errorhandler')
+    , morgan         = require('morgan')
     , app = express()
+    , env = this.node_env
     ;
 
   require('./express-init')(this, app);
   require('../routes')(this, app);
 
+  if (env == 'development') {
+    //swig.setDefaults({ cache: false });
+    app.use(errorHandler({ dumpExceptions: true, showStack: true }));
+    app.use(morgan('dev'));
+  }
+
+  if (env == 'production') {
+    var oneYear = 31557600000;
+    app.use(morgan('combined'));
+    app.use(function (err, req, res, next) {
+      res.status(500);
+      res.render('500', { error: err });
+    });
+  }
+
   return new Promise.resolve(app);
 };
 

+ 30 - 29
lib/routes/index.js

@@ -15,20 +15,21 @@ module.exports = function(crowi, app) {
     , search    = require('./search')(crowi, app)
     , loginRequired = middleware.loginRequired
     , accessTokenParser = middleware.accessTokenParser
+    , csrf      = middleware.csrfVerify(crowi, app)
     ;
 
   app.get('/'                        , loginRequired(crowi, app) , page.pageListShow);
 
   app.get('/installer'               , middleware.applicationNotInstalled() , installer.index);
-  app.post('/installer/createAdmin'  , middleware.applicationNotInstalled() , form.register , installer.createAdmin);
+  app.post('/installer/createAdmin'  , middleware.applicationNotInstalled() , form.register , csrf, installer.createAdmin);
   //app.post('/installer/user'         , middleware.applicationNotInstalled() , installer.createFirstUser);
 
   app.get('/login/error/:reason'     , login.error);
   app.get('/login'                   , middleware.applicationInstalled()    , login.login);
   app.get('/login/invited'           , login.invited);
-  app.post('/login/activateInvited'  , form.invited                         , login.invited);
-  app.post('/login'                  , form.login                           , login.login);
-  app.post('/register'               , form.register                        , login.register);
+  app.post('/login/activateInvited'  , form.invited                         , csrf, login.invited);
+  app.post('/login'                  , form.login                           , csrf, login.login);
+  app.post('/register'               , form.register                        , csrf, login.register);
   app.get('/register'                , middleware.applicationInstalled()    , login.register);
   app.post('/register/google'        , login.registerGoogle);
   app.get('/google/callback'         , login.googleCallback);
@@ -38,32 +39,32 @@ module.exports = function(crowi, app) {
 
   app.get('/admin'                      , loginRequired(crowi, app) , middleware.adminRequired() , admin.index);
   app.get('/admin/app'                  , loginRequired(crowi, app) , middleware.adminRequired() , admin.app.index);
-  app.post('/_api/admin/settings/app'   , loginRequired(crowi, app) , middleware.adminRequired() , form.admin.app, admin.api.appSetting);
+  app.post('/_api/admin/settings/app'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.app, admin.api.appSetting);
   app.post('/_api/admin/settings/sec'   , loginRequired(crowi, app) , middleware.adminRequired() , form.admin.sec, admin.api.appSetting);
-  app.post('/_api/admin/settings/mail'  , loginRequired(crowi, app) , middleware.adminRequired() , form.admin.mail, admin.api.appSetting);
-  app.post('/_api/admin/settings/aws'   , loginRequired(crowi, app) , middleware.adminRequired() , form.admin.aws, admin.api.appSetting);
-  app.post('/_api/admin/settings/google', loginRequired(crowi, app) , middleware.adminRequired() , form.admin.google, admin.api.appSetting);
-  app.post('/_api/admin/settings/fb'    , loginRequired(crowi, app) , middleware.adminRequired() , form.admin.fb , admin.api.appSetting);
+  app.post('/_api/admin/settings/mail'  , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.mail, admin.api.appSetting);
+  app.post('/_api/admin/settings/aws'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.aws, admin.api.appSetting);
+  app.post('/_api/admin/settings/google', loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.google, admin.api.appSetting);
+  app.post('/_api/admin/settings/fb'    , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.fb , admin.api.appSetting);
 
   // search admin
   app.get('/admin/search'              , loginRequired(crowi, app) , middleware.adminRequired() , admin.search.index);
-  app.post('/admin/search/build'       , loginRequired(crowi, app) , middleware.adminRequired() , admin.search.buildIndex);
+  app.post('/admin/search/build'       , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.search.buildIndex);
 
   // notification admin
   app.get('/admin/notification'              , loginRequired(crowi, app) , middleware.adminRequired() , admin.notification.index);
-  app.post('/admin/notification/slackSetting', loginRequired(crowi, app) , middleware.adminRequired() , form.admin.slackSetting, admin.notification.slackSetting);
+  app.post('/admin/notification/slackSetting', loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.slackSetting, admin.notification.slackSetting);
   app.get('/admin/notification/slackAuth'    , loginRequired(crowi, app) , middleware.adminRequired() , admin.notification.slackAuth);
-  app.post('/_api/admin/notification.add'    , loginRequired(crowi, app) , middleware.adminRequired() , admin.api.notificationAdd);
-  app.post('/_api/admin/notification.remove' , loginRequired(crowi, app) , middleware.adminRequired() , admin.api.notificationRemove);
+  app.post('/_api/admin/notification.add'    , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.api.notificationAdd);
+  app.post('/_api/admin/notification.remove' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.api.notificationRemove);
 
   app.get('/admin/users'                , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.index);
-  app.post('/admin/user/invite'         , form.admin.userInvite ,  loginRequired(crowi, app) , middleware.adminRequired() , admin.user.invite);
-  app.post('/admin/user/:id/makeAdmin'  , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.makeAdmin);
+  app.post('/admin/user/invite'         , form.admin.userInvite ,  loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.invite);
+  app.post('/admin/user/:id/makeAdmin'  , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.makeAdmin);
   app.post('/admin/user/:id/removeFromAdmin', loginRequired(crowi, app) , middleware.adminRequired() , admin.user.removeFromAdmin);
-  app.post('/admin/user/:id/activate'   , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.activate);
-  app.post('/admin/user/:id/suspend'    , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.suspend);
-  app.post('/admin/user/:id/remove'     , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.remove);
-  app.post('/admin/user/:id/removeCompletely' , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.removeCompletely);
+  app.post('/admin/user/:id/activate'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.activate);
+  app.post('/admin/user/:id/suspend'    , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.suspend);
+  app.post('/admin/user/:id/remove'     , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.remove);
+  app.post('/admin/user/:id/removeCompletely' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.removeCompletely);
 
   app.get('/me'                       , loginRequired(crowi, app) , me.index);
   app.get('/me/password'              , loginRequired(crowi, app) , me.password);
@@ -97,16 +98,16 @@ module.exports = function(crowi, app) {
   app.get('/_api/pages.get'           , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.get);
   app.get('/_api/pages.updatePost'    , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.getUpdatePost);
   app.post('/_api/pages.seen'         , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.seen);
-  app.post('/_api/pages.rename'       , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.rename);
-  app.post('/_api/pages.remove'       , loginRequired(crowi, app) , page.api.remove); // (Avoid from API Token)
-  app.post('/_api/pages.revertRemove' , loginRequired(crowi, app) , page.api.revertRemove); // (Avoid from API Token)
+  app.post('/_api/pages.rename'       , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, page.api.rename);
+  app.post('/_api/pages.remove'       , loginRequired(crowi, app) , csrf, page.api.remove); // (Avoid from API Token)
+  app.post('/_api/pages.revertRemove' , loginRequired(crowi, app) , csrf, page.api.revertRemove); // (Avoid from API Token)
   app.get('/_api/comments.get'        , accessTokenParser(crowi, app) , loginRequired(crowi, app) , comment.api.get);
-  app.post('/_api/comments.add'       , form.comment, accessTokenParser(crowi, app) , loginRequired(crowi, app) , comment.api.add);
+  app.post('/_api/comments.add'       , form.comment, accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, comment.api.add);
   app.get( '/_api/bookmarks.get'      , accessTokenParser(crowi, app) , loginRequired(crowi, app) , bookmark.api.get);
-  app.post('/_api/bookmarks.add'      , accessTokenParser(crowi, app) , loginRequired(crowi, app) , bookmark.api.add);
-  app.post('/_api/bookmarks.remove'   , accessTokenParser(crowi, app) , loginRequired(crowi, app) , bookmark.api.remove);
-  app.post('/_api/likes.add'          , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.like);
-  app.post('/_api/likes.remove'       , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.unlike);
+  app.post('/_api/bookmarks.add'      , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, bookmark.api.add);
+  app.post('/_api/bookmarks.remove'   , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, bookmark.api.remove);
+  app.post('/_api/likes.add'          , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, page.api.like);
+  app.post('/_api/likes.remove'       , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, page.api.unlike);
 
   app.get( '/_api/revisions.get'      , accessTokenParser(crowi, app) , loginRequired(crowi, app) , revision.api.get);
   app.get( '/_api/revisions.list'     , accessTokenParser(crowi, app) , loginRequired(crowi, app) ,revision.api.list);
@@ -114,10 +115,10 @@ module.exports = function(crowi, app) {
   //app.get('/_api/revision/:id'     , user.useUserData()         , revision.api.get);
   //app.get('/_api/r/:revisionId'    , user.useUserData()         , page.api.get);
 
-  app.post('/_/edit'                 , form.revision             , loginRequired(crowi, app) , page.pageEdit);
+  app.post('/_/edit'                 , form.revision             , loginRequired(crowi, app) , csrf, page.pageEdit);
   app.get('/trash/$'                 , loginRequired(crowi, app) , page.deletedPageListShow);
   app.get('/trash/*/$'               , loginRequired(crowi, app) , page.deletedPageListShow);
   app.get('/*/$'                     , loginRequired(crowi, app) , page.pageListShow);
   app.get('/*'                       , loginRequired(crowi, app) , page.pageShow);
-  //app.get('/*/edit'                , routes.edit);
+
 };

+ 18 - 1
lib/util/middlewares.js

@@ -23,6 +23,21 @@ exports.loginChecker = function(crowi, app) {
   };
 };
 
+exports.csrfVerify = function(crowi, app) {
+  return function(req, res, next) {
+    var token = req.body._csrf || req.query._csrf || null;
+    if (req.skipCsrfVerify) {
+      return next();
+    }
+
+    if (crowi.getTokens().verify(crowi.getCsrfSecret(), token)) {
+      return next();
+    }
+
+    return res.sendStatus(403);
+  };
+};
+
 exports.swigFunctions = function(crowi, app) {
   return function(req, res, next) {
     require('../util/swigFunctions')(crowi, app, req, res.locals);
@@ -138,7 +153,7 @@ exports.loginRequired = function(crowi, app) {
 
 exports.accessTokenParser = function(crowi, app) {
   return function(req, res, next) {
-    var accessToken = req.query.access_token;
+    var accessToken = req.query.access_token || req.body.access_token || null;
     if (!accessToken) {
       return next();
     }
@@ -148,6 +163,8 @@ exports.accessTokenParser = function(crowi, app) {
     User.findUserByApiToken(accessToken)
     .then(function(userData) {
       req.user = userData;
+      req.skipCsrfVerify = true;
+
       next();
     }).catch(function(err) {
       next();

+ 5 - 0
lib/util/swigFunctions.js

@@ -5,6 +5,11 @@ module.exports = function(crowi, app, req, locals) {
     , User = crowi.model('User')
   ;
 
+  // token getter
+  locals._csrf = function() {
+    return crowi.getCsrfToken();
+  };
+
   locals.facebookLoginEnabled = function() {
     var config = crowi.getConfig()
     return config.crowi['facebook:appId'] && config.crowi['facebook:secret'];

+ 1 - 1
lib/views/500.html

@@ -1 +1 @@
-Error: {{ error }}
+Error: {{ error.message }}

+ 1 - 0
lib/views/_form.html

@@ -49,6 +49,7 @@
           {% endfor %}
         </select>
         {% endif %}
+        <input type="hidden" name="_csrf" value="{{ _csrf() }}">
         <input type="submit" class="btn btn-primary" id="edit-form-submit" value="ページを更新" />
       </div>
     </div>

+ 6 - 1
lib/views/admin/app.html

@@ -54,6 +54,7 @@
 
         <div class="form-group">
           <div class="col-xs-offset-3 col-xs-6">
+            <input type="hidden" name="_csrf" value="{{ _csrf() }}">
             <button type="submit" class="btn btn-primary">更新</button>
           </div>
         </div>
@@ -105,6 +106,7 @@
 
         <div class="form-group">
           <div class="col-xs-offset-3 col-xs-6">
+            <input type="hidden" name="_csrf" value="{{ _csrf() }}">
             <button type="submit" class="btn btn-primary">更新</button>
           </div>
         </div>
@@ -149,6 +151,7 @@
 
         <div class="form-group">
           <div class="col-xs-offset-3 col-xs-6">
+            <input type="hidden" name="_csrf" value="{{ _csrf() }}">
             <button type="submit" class="btn btn-primary">更新</button>
           </div>
         </div>
@@ -197,6 +200,7 @@
 
         <div class="form-group">
           <div class="col-xs-offset-3 col-xs-6">
+            <input type="hidden" name="_csrf" value="{{ _csrf() }}">
             <button type="submit" class="btn btn-primary">更新</button>
           </div>
         </div>
@@ -225,6 +229,7 @@
 
         <div class="form-group">
           <div class="col-xs-offset-3 col-xs-6">
+            <input type="hidden" name="_csrf" value="{{ _csrf() }}">
             <button type="submit" class="btn btn-primary">更新</button>
           </div>
         </div>
@@ -253,6 +258,7 @@
 
         <div class="form-group">
           <div class="col-xs-offset-3 col-xs-6">
+            <input type="hidden" name="_csrf" value="{{ _csrf() }}">
             <button type="submit" class="btn btn-primary">更新</button>
           </div>
         </div>
@@ -297,7 +303,6 @@
           $button.attr('disabled', 'disabled');
           var jqxhr = $.post($form.attr('action'), $form.serialize(), function(data)
             {
-              console.log(data);
               if (data.status) {
                 showMessage($id, '更新しました');
               } else {

+ 3 - 0
lib/views/admin/notification.html

@@ -65,6 +65,7 @@
           </div>
         </div>
       </fieldset>
+      <input type="hidden" name="_csrf" value="{{ _csrf() }}">
       </form>
 
       {% if hasSlackConfig %}
@@ -108,6 +109,7 @@
               </p>
             </td>
             <td>
+              <input type="hidden" name="_csrf" value="{{ _csrf() }}">
               <input type="submit" value="Add" class="btn btn-primary">
             </td>
           </tr>
@@ -124,6 +126,7 @@
             <td>
               <form class="admin-remove-updatepost">
                 <input type="hidden" name="id" value="{{ notif._id.toString() }}">
+                <input type="hidden" name="_csrf" value="{{ _csrf() }}">
                 <input type="submit" value="Delete" class="btn btn-default">
               </form>
             </td>

+ 1 - 0
lib/views/admin/search.html

@@ -51,6 +51,7 @@
           </div>
         </div>
       </fieldset>
+      <input type="hidden" name="_csrf" value="{{ _csrf() }}">
       </form>
 
     </div>

+ 8 - 0
lib/views/admin/users.html

@@ -48,6 +48,7 @@
           </div>
           <button type="submit" class="btn btn-primary">招待する</button>
         </div>
+        <input type="hidden" name="_csrf" value="{{ _csrf() }}">
       </form>
 
       {% set createdUser = req.flash('createdUser') %}
@@ -126,28 +127,33 @@
                   <li class="dropdown-button">
                   {% if sUser.status == 1 %}
                   <form action="/admin/user/{{ sUser._id.toString() }}/activate" method="post">
+                    <input type="hidden" name="_csrf" value="{{ _csrf() }}">
                     <button type="submit" class="btn btn-block btn-info">承認する</button>
                   </form>
                   {% endif  %}
                   {% if sUser.status == 2 %}
                   <form action="/admin/user/{{ sUser._id.toString() }}/suspend" method="post">
+                    <input type="hidden" name="_csrf" value="{{ _csrf() }}">
                     <button type="submit" class="btn btn-block btn-warning">アカウント停止</button>
                   </form>
                   {% endif  %}
                   {% if sUser.status == 3 %}
                   <form action="/admin/user/{{ sUser._id.toString() }}/activate" method="post">
+                    <input type="hidden" name="_csrf" value="{{ _csrf() }}">
                     <button type="submit" class="btn btn-block btn-default">元に戻す</button>
                   </form>
                   </li>
                   <li class="dropdown-button">
                   {# label は同じだけど、こっちは論理削除 #}
                   <form action="/admin/user/{{ sUser._id.toString() }}/remove" method="post">
+                    <input type="hidden" name="_csrf" value="{{ _csrf() }}">
                     <button type="submit" class="btn btn-block btn-danger">削除する</button>
                   </form>
                   {% endif  %}
                   {% if sUser.status == 5 %}
                   {# label は同じだけど、こっちは物理削除 #}
                   <form action="/admin/user/{{ sUser._id.toString() }}/removeCompletely" method="post">
+                    <input type="hidden" name="_csrf" value="{{ _csrf() }}">
                     <button type="submit" class="btn btn-block btn-danger">削除する</button>
                   </form>
                   {% endif  %}
@@ -161,6 +167,7 @@
                     {% if sUser.admin %}
                       {% if sUser.username != user.username %}
                       <form action="/admin/user/{{ sUser._id.toString() }}/removeFromAdmin" method="post">
+                        <input type="hidden" name="_csrf" value="{{ _csrf() }}">
                         <button type="submit" class="btn btn-block btn-danger">管理者からはずす</button>
                       </form>
                       {% else %}
@@ -168,6 +175,7 @@
                       {% endif %}
                     {% else %}
                       <form action="/admin/user/{{ sUser._id.toString() }}/makeAdmin" method="post">
+                        <input type="hidden" name="_csrf" value="{{ _csrf() }}">
                         <button type="submit" class="btn btn-block btn-primary">管理者にする</button>
                       </form>
                     {% endif %}

+ 1 - 0
lib/views/installer.html

@@ -65,6 +65,7 @@
       パスワードは6文字以上の半角英数字または記号
       </p>
 
+      <input type="hidden" name="_csrf" value="{{ _csrf() }}">
       <input type="submit" class="btn btn-primary btn-lg btn-block" value="作成">
     </form>
 

+ 5 - 0
lib/views/login.html

@@ -49,6 +49,7 @@
         <input type="password" class="form-control" placeholder="Password" name="loginForm[password]">
       </div>
 
+      <input type="hidden" name="_csrf" value="{{ _csrf() }}">
       <input type="submit" class="btn btn-primary btn-lg btn-block" value="Login">
     </form>
 
@@ -60,6 +61,7 @@
         <p>Google でログイン</p>
         <form role="form" action="/login/google" method="get">
           <button type="submit" class="btn btn-block btn-google"><i class="fa fa-google-plus-square"></i> Login</button>
+          <input type="hidden" name="_csrf" value="{{ _csrf() }}">
         </form>
       </div>
       {% endif %}
@@ -67,6 +69,7 @@
       <div class="col-md-6">
         <p>Facebook でログイン</p>
         <form role="form">
+          <input type="hidden" name="_csrf" value="{{ _csrf() }}">
           <button type="button" id="btn-login-facebook" class="btn btn-block btn-facebook"><i class="fa fa-facebook-square"></i> Login</button>
         </form>
       </div>
@@ -163,6 +166,7 @@
       パスワードは6文字以上の半角英数字または記号
       </p>
 
+      <input type="hidden" name="_csrf" value="{{ _csrf() }}">
       <input type="submit" class="btn btn-primary btn-lg btn-block" value="新規登録">
     </form>
 
@@ -173,6 +177,7 @@
       <div class="col-md-6">
         <p>Google で登録</p>
         <form role="form" method="post" action="/register/google">
+          <input type="hidden" name="_csrf" value="{{ _csrf() }}">
           <button type="submit" class="btn btn-block btn-google"><i class="fa fa-google-plus-square"></i> Login</button>
         </form>
       </div>

+ 1 - 0
lib/views/modal/widget_delete.html

@@ -19,6 +19,7 @@
         </div>
         <div class="modal-footer">
           <p><small class="pull-left" id="delete-errors"></small></p>
+          <input type="hidden" name="_csrf" value="{{ _csrf() }}">
           <input type="hidden" name="path" value="{{ page.path }}">
           <input type="hidden" name="page_id" value="{{ page._id.toString() }}">
           <input type="hidden" name="revision_id" value="{{ page.revision._id.toString() }}">

+ 1 - 0
lib/views/modal/widget_rename.html

@@ -40,6 +40,7 @@
         </div>
         <div class="modal-footer">
           <p><small class="pull-left" id="newPageNameCheck"></small></p>
+          <input type="hidden" name="_csrf" value="{{ _csrf() }}">
           <input type="hidden" name="path" value="{{ page.path }}">
           <input type="hidden" name="page_id" value="{{ page._id.toString() }}">
           <input type="hidden" name="revision_id" value="{{ page.revision._id.toString() }}">

+ 1 - 0
lib/views/modal/widget_unportalize.html

@@ -33,6 +33,7 @@
         </div>
         <div class="modal-footer">
           <p><small class="pull-left" id="newPageNameCheck"></small></p>
+          <input type="hidden" name="_csrf" value="{{ _csrf() }}">
           <input type="hidden" name="path" value="{{ page.path }}">
           <input type="hidden" class="form-control" name="new_path" id="newPageName" value="{{ unportalizedPath }}">
           <input type="hidden" name="page_id" value="{{ page._id.toString() }}">

+ 3 - 2
lib/views/page.html

@@ -14,14 +14,14 @@
 
 
     {% if page %}
-      <a href="#" title="Bookmark" class="bookmark-link" id="bookmark-button" data-bookmarked="0"><i class="fa fa-star-o"></i></a>
+    <a href="#" title="Bookmark" class="bookmark-link" id="bookmark-button" data-csrftoken="{{ _csrf() }}" data-bookmarked="0"><i class="fa fa-star-o"></i></a>
     {% endif %}
     <h1 class="title" id="revision-path">{{ path|insertSpaceToEachSlashes }}</h1>
   </header>
   {% else %}
   {# trash/* #}
   <header id="page-header">
-    <a href="#" title="Bookmark" class="bookmark-link" id="bookmark-button" data-bookmarked="0"><i class="fa fa-star-o"></i></a>
+    <a href="#" title="Bookmark" class="bookmark-link" id="bookmark-button" data-csrftoken="{{ _csrf() }}" data-bookmarked="0"><i class="fa fa-star-o"></i></a>
     <h1 class="title">{{ path|insertSpaceToEachSlashes }}</h1>
   </header>
   {% endif %}
@@ -65,6 +65,7 @@
   {% if page.isDeleted() %}
   <div class="alert alert-danger">
     <form role="form" class="pull-right" id="revert-delete-page-form" onsubmit="return false;">
+      <input type="hidden" name="_csrf" value="{{ _csrf() }}">
       <input type="hidden" name="path" value="{{ page.path }}">
       <input type="hidden" name="page_id" value="{{ page._id.toString() }}">
       <input type="submit" class="btn btn-danger btn-inverse btn-sm" value="Put Back!">

+ 2 - 2
lib/views/page_list.html

@@ -15,7 +15,7 @@
 <div class="header-wrap">
   <header class="portal-header {% if page %}has-page{% endif %}">
     {% if page %}
-      <a href="#" title="Bookmark" class="bookmark-link" id="bookmark-button" data-bookmarked="0"><i class="fa fa-star-o"></i></a>
+      <a href="#" title="Bookmark" class="bookmark-link" id="bookmark-button" data-csrftoken="{{ _csrf() }}" data-bookmarked="0"><i class="fa fa-star-o"></i></a>
 
     {% endif %}
     <h1 class="title">
@@ -100,7 +100,7 @@
   {% include 'modal/widget_unportalize.html' %}
 
   <div class="tab-content">
-  {% if not page.isLatestRevision() %}
+  {% if page and not page.isLatestRevision() %}
   <div class="alert alert-warning">
     <strong>注意: </strong> これは現在の版ではありません。 <i class="fa fa-magic"></i> <a href="{{ page.path }}">最新のポータルを表示</a>
   </div>

+ 2 - 1
lib/views/widget/page_side_content.html

@@ -2,7 +2,7 @@
 <ul class="fitted-list">
   <li data-toggle="tooltip" data-placement="bottom" title="共有用リンク" class="input-group">
     <span class="input-group-addon">共有用</span>
-    <input class="copy-link form-control" type="text" value="{{ config.crowi['app:title'] }} {{ path }}  {{ baseUrl }}/{{ page._id.toString() }}">
+    <input class="copy-link form-control" type="text" value="{{ config.crowi['app:title']|default('Crowi') }} {{ path }}  {{ baseUrl }}/{{ page._id.toString() }}">
   </li>
   <li data-toggle="tooltip" data-placement="bottom" title="Markdown形式のリンク" class="input-group">
     <span class="input-group-addon">Markdown</span>
@@ -19,6 +19,7 @@
           <textarea class="comment-form-comment form-control" id="comment-form-comment" name="commentForm[comment]"></textarea>
         </div>
         <div class="comment-submit">
+          <input type="hidden" name="_csrf" value="{{ _csrf() }}">
           <input type="hidden" name="commentForm[page_id]" value="{{ page._id.toString() }}">
           <input type="hidden" name="commentForm[revision_id]" value="{{ revision._id.toString() }}">
           <span class="text-danger" id="comment-form-message"></span>

+ 1 - 0
lib/views/widget/page_side_header.html

@@ -33,6 +33,7 @@
         <p class="liker-count">
         <span id="like-count">{{ page.liker.length }}</span>
         <button
+          data-csrftoken="{{ _csrf() }}"
           data-liked="{% if page.isLiked(user) %}1{% else %}0{% endif %}"
           class="btn btn-default btn-sm {% if page.isLiked(user) %}active{% endif %}"
           id="like-button"><i class="fa fa-thumbs-o-up"></i> いいね!</button>

+ 1 - 1
package.json

@@ -48,7 +48,7 @@
     "connect-redis": "~2.1.0",
     "consolidate": "~0.11.0",
     "cookie-parser": "~1.3.4",
-    "csurf": "^1.9.0",
+    "csrf": "~3.0.3",
     "debug": "~2.2.0",
     "del": "~2.2.0",
     "diff": "~2.2.2",

+ 6 - 4
resource/js/crowi.js

@@ -647,14 +647,15 @@ $(function() {
 
     $bookmarkButton.click(function() {
       var bookmarked = $bookmarkButton.data('bookmarked');
+      var token = $bookmarkButton.data('csrftoken');
       if (!bookmarked) {
-        $.post('/_api/bookmarks.add', {page_id: pageId}, function(res) {
+        $.post('/_api/bookmarks.add', {_csrf: token, page_id: pageId}, function(res) {
           if (res.ok && res.bookmark) {
             MarkBookmarked();
           }
         });
       } else {
-        $.post('/_api/bookmarks.remove', {page_id: pageId}, function(res) {
+        $.post('/_api/bookmarks.remove', {_csrf: token, page_id: pageId}, function(res) {
           if (res.ok) {
             MarkUnBookmarked();
           }
@@ -685,14 +686,15 @@ $(function() {
     var $likeCount = $('#like-count');
     $likeButton.click(function() {
       var liked = $likeButton.data('liked');
+      var token = $likeButton.data('csrftoken');
       if (!liked) {
-        $.post('/_api/likes.add', {page_id: pageId}, function(res) {
+        $.post('/_api/likes.add', {_csrf: token, page_id: pageId}, function(res) {
           if (res.ok) {
             MarkLiked();
           }
         });
       } else {
-        $.post('/_api/likes.remove', {page_id: pageId}, function(res) {
+        $.post('/_api/likes.remove', {_csrf: token, page_id: pageId}, function(res) {
           if (res.ok) {
             MarkUnLiked();
           }