Просмотр исходного кода

Merge pull request #39 from crowi/feature-api

Feature api
Sotaro KARASAWA 10 лет назад
Родитель
Сommit
129be25dad

+ 2 - 1
lib/form/index.js

@@ -4,7 +4,8 @@ exports.invited = require('./invited');
 exports.revision = require('./revision');
 exports.me = {
   user: require('./me/user'),
-  password: require('./me/password')
+  password: require('./me/password'),
+  apiToken: require('./me/apiToken'),
 };
 exports.admin = {
   app: require('./admin/app'),

+ 9 - 0
lib/form/me/apiToken.js

@@ -0,0 +1,9 @@
+'use strict';
+
+var form = require('express-form')
+  , field = form.field;
+
+module.exports = form(
+  field('apiTokenForm.confirm').required()
+);
+

+ 38 - 4
lib/models/user.js

@@ -25,6 +25,7 @@ module.exports = function(crowi) {
     username: { type: String },
     email: { type: String, required: true },
     password: String,
+    apiToken: String,
     status: { type: Number, required: true, default: STATUS_ACTIVE },
     createdAt: { type: Date, default: Date.now },
     admin: { type: Boolean, default: 0 }
@@ -58,6 +59,13 @@ module.exports = function(crowi) {
     return hasher.digest('hex');
   }
 
+  function generateApiToken (user) {
+    var hasher = crypto.createHash('sha256');
+    hasher.update((new Date).getTime() + user._id);
+
+    return hasher.digest('base64');
+  }
+
   userSchema.methods.isPasswordSet = function() {
     if (this.password) {
       return true;
@@ -69,10 +77,6 @@ module.exports = function(crowi) {
     return this.password == generatePassword(password);
   };
 
-  userSchema.methods.setPassword = function(password) {
-    return this.password == generatePassword(password);
-  };
-
   userSchema.methods.setPassword = function(password) {
     this.password = generatePassword(password);
     return this;
@@ -100,6 +104,22 @@ module.exports = function(crowi) {
     });
   };
 
+  userSchema.methods.updateApiToken = function(callback) {
+    var self = this;
+
+    self.apiToken = generateApiToken(this);
+    return new Promise(function(resolve, reject) {
+      self.save(function(err, userData) {
+        if (err) {
+          return reject(err);
+        } else {
+          return resolve(userData);
+        }
+      });
+
+    });
+  };
+
   userSchema.methods.updateImage = function(image, callback) {
     this.image = image;
     this.save(function(err, userData) {
@@ -270,6 +290,20 @@ module.exports = function(crowi) {
     });
   };
 
+  userSchema.statics.findUserByApiToken = function(apiToken) {
+    var self = this;
+
+    return new Promise(function(resolve, reject) {
+      self.findOne({apiToken: apiToken}, function (err, userData) {
+        if (err) {
+          return reject(err);
+        } else {
+          return resolve(userData);
+        }
+      });
+    });
+  };
+
   userSchema.statics.findUserByFacebookId = function(fbId, callback) {
     this.findOne({userId: fbId}, function (err, userData) {
       callback(err, userData);

+ 7 - 3
lib/routes/index.js

@@ -10,6 +10,7 @@ module.exports = function(crowi, app) {
     , user      = require('./user')(crowi, app)
     , attachment= require('./attachment')(crowi, app)
     , loginRequired = middleware.loginRequired
+    , accessTokenParser = middleware.accessTokenParser
     ;
 
   app.get('/'                        , loginRequired(crowi, app) , page.pageListShow);
@@ -38,8 +39,7 @@ module.exports = function(crowi, app) {
   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/fb'    , loginRequired(crowi, app) , middleware.adminRequired() , form.admin.fb , admin.api.appSetting);
 
   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);
@@ -52,8 +52,10 @@ module.exports = function(crowi, app) {
 
   app.get('/me'                       , loginRequired(crowi, app) , me.index);
   app.get('/me/password'              , loginRequired(crowi, app) , me.password);
+  app.get('/me/apiToken'              , loginRequired(crowi, app) , me.apiToken);
   app.post('/me'                      , form.me.user              , loginRequired(crowi, app) , me.index);
   app.post('/me/password'             , form.me.password          , loginRequired(crowi, app) , me.password);
+  app.post('/me/apiToken'             , form.me.apiToken          , loginRequired(crowi, app) , me.apiToken);
   app.post('/me/picture/delete'       , loginRequired(crowi, app) , me.deletePicture);
   app.post('/me/auth/facebook'        , loginRequired(crowi, app) , me.authFacebook);
   app.post('/me/auth/google'          , loginRequired(crowi, app) , me.authGoogle);
@@ -72,7 +74,9 @@ module.exports = function(crowi, app) {
   app.post('/_api/page/:id/unlike'    , loginRequired(crowi, app) , page.api.unlike);
   app.get( '/_api/page/:id/bookmark'  , loginRequired(crowi, app) , page.api.isBookmarked);
   app.post('/_api/page/:id/bookmark'  , loginRequired(crowi, app) , page.api.bookmark);
-  //app.get('/_api/page/*'           , user.useUserData()         , page.api.get);
+
+  // HTTP RPC Styled API (に徐々に移行していいこうと思う)
+  app.get('/_api/pages.get'           , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.get);
   //app.get('/_api/revision/:id'     , user.useUserData()         , revision.api.get);
   //app.get('/_api/r/:revisionId'    , user.useUserData()         , page.api.get);
 

+ 22 - 0
lib/routes/me.js

@@ -165,6 +165,28 @@ module.exports = function(crowi, app) {
     }
   };
 
+  actions.apiToken = function(req, res) {
+    var apiTokenForm = req.body.apiTokenForm;
+    var userData = req.user;
+
+    if (req.method == 'POST' && req.form.isValid) {
+      userData.updateApiToken()
+      .then(function(userData) {
+          req.flash('successMessage', 'API Token を更新しました');
+          return res.redirect('/me/apiToken');
+      })
+      .catch(function(err) {
+          //req.flash('successMessage',);
+          req.form.errors.push('API Token の更新に失敗しました');
+          return res.render('me/api_token', {
+          });
+      });
+    } else {
+      return res.render('me/api_token', {
+      });
+    }
+  };
+
   actions.updates = function(req, res) {
     res.render('me/update', {
     });

+ 29 - 0
lib/routes/page.js

@@ -204,6 +204,35 @@ module.exports = function(crowi, app) {
     });
   };
 
+  /**
+   * @api pages.get
+   * @param page /page/path
+   * @param page_id XXXXX
+   */
+  api.get = function(req, res){
+    var pagePath = req.query.page;
+    var revision = req.query.revision;
+    var options = {};
+
+    Page.findPage(pagePath, req.user, revision, options, function(err, pageData) {
+      var result = {};
+      if (err) {
+        result = {
+          ok: false,
+          message: err.toString()
+        };
+      }
+      if (pageData) {
+        result = {
+          ok: true,
+          page: pageData
+        };
+      }
+
+      return res.json(result);
+    });
+  };
+
   /**
    * page bookmark
    */

+ 19 - 0
lib/util/middlewares.js

@@ -104,6 +104,25 @@ exports.loginRequired = function(crowi, app) {
   };
 };
 
+exports.accessTokenParser = function(crowi, app) {
+  return function(req, res, next) {
+    var accessToken = req.query.access_token;
+    if (!accessToken) {
+      return next();
+    }
+
+    var User = crowi.model('User')
+
+    User.findUserByApiToken(accessToken)
+    .then(function(userData) {
+      req.user = userData;
+      next();
+    }).catch(function(err) {
+      next();
+    });
+  };
+};
+
 // this is for Installer
 exports.applicationNotInstalled = function() {
   return function(req, res, next) {

+ 83 - 0
lib/views/me/api_token.html

@@ -0,0 +1,83 @@
+{% extends '../layout/2column.html' %}
+
+
+{% block html_title %}APIの設定 · {{ path }}{% endblock %}
+
+{% block content_head %}
+<header  id="page-header">
+  <h1 class="title" id="">ユーザー設定</h1>
+</header>
+{% endblock %}
+
+{% block content_main %}
+<div class="content-main">
+
+  <ul class="nav nav-tabs">
+    <li><a href="/me"><i class="fa fa-gears"></i> ユーザー情報</a></li>
+    <li><a href="/me/password"><i class="fa fa-key"></i> パスワード設定</a></li>
+    <li class="active"><a href="/me/apiToken"><i class="fa fa-rocket"></i> API設定</a></li>
+  </ul>
+
+  <div class="tab-content">
+
+  {% set message = req.flash('successMessage') %}
+  {% if message.length %}
+  <div class="alert alert-success">
+    {{ message }}
+  </div>
+  {% endif %}
+
+  {% if req.form.errors.length > 0 %}
+  <div class="alert alert-danger">
+    <ul>
+    {% for error in req.form.errors %}
+      <li>{{ error }}</li>
+    {% endfor %}
+    </ul>
+  </div>
+  {% endif %}
+
+  <div id="form-box">
+
+    <form action="/me/apiToken" method="post" class="form-horizontal" role="form">
+    <fieldset>
+      <legend>API Token 設定</legend>
+      <div class="form-group {% if not user.password %}has-error{% endif %}">
+        <label for="" class="col-xs-2 control-label">現在のAPI Token</label>
+        <div class="col-xs-6">
+          {% if user.apiToken %}
+            <input class="form-control" type="text" value="{{ user.apiToken }}">
+          {% else %}
+          <p class="form-control-static">
+            API Token が設定されていません。更新するボタンから発行してください。
+          </p>
+          {% endif %}
+        </div>
+      </div>
+
+      <div class="form-group">
+        <div class="col-xs-offset-2 col-xs-10">
+
+          <p class="alert alert-warning">
+          API Token を更新すると、自動的に新しい Token が生成されます。<br>
+          現在の Token を利用している処理は動かなくなります。
+          </p>
+
+          <button type="submit" value="1" name="apiTokenForm[confirm]" class="btn btn-primary">API Tokenを更新する</button>
+        </div>
+      </div>
+
+    </fieldset>
+    </form>
+  </div>
+
+
+  </div>
+</div>
+{% endblock content_main %}
+
+{% block content_footer %}
+{% endblock %}
+
+{% block footer %}
+{% endblock %}

+ 1 - 0
lib/views/me/index.html

@@ -14,6 +14,7 @@
   <ul class="nav nav-tabs">
     <li class="active"><a href="/me"><i class="fa fa-gears"></i> ユーザー情報</a></li>
     <li><a href="/me/password"><i class="fa fa-key"></i> パスワード設定</a></li>
+    <li><a href="/me/apiToken"><i class="fa fa-rocket"></i> API設定</a></li>
   </ul>
 
   <div class="tab-content">

+ 1 - 0
lib/views/me/password.html

@@ -14,6 +14,7 @@
   <ul class="nav nav-tabs">
     <li><a href="/me"><i class="fa fa-gears"></i> ユーザー情報</a></li>
     <li class="active"><a href="/me/password"><i class="fa fa-key"></i> パスワード設定</a></li>
+    <li><a href="/me/apiToken"><i class="fa fa-rocket"></i> API設定</a></li>
   </ul>
 
   <div class="tab-content">

+ 13 - 13
package.json

@@ -27,12 +27,12 @@
     "npm": "3.3.x"
   },
   "dependencies": {
-    "async": "~0.9.0",
-    "aws-sdk": "~2.0.0-rc.19",
+    "async": "~1.5.0",
+    "aws-sdk": "~2.2.26",
     "basic-auth-connect": "~1.0.0",
     "bluebird": "~3.0.5",
     "body-parser": "~1.14.1",
-    "bower": "~1.6.5",
+    "bower": "~1.7.1",
     "cli": "~0.6.0",
     "connect-flash": "~0.1.1",
     "connect-redis": "~2.1.0",
@@ -45,6 +45,16 @@
     "express-session": "~1.12.0",
     "facebook-node-sdk": "=0.1.10",
     "googleapis": "=0.4.7",
+    "gulp": "~3.9.0",
+    "gulp-concat": "~2.6.0",
+    "gulp-cssmin": "~0.1.7",
+    "gulp-jshint": "~1.12.0",
+    "gulp-rename": "~1.2.2",
+    "gulp-sass": "~2.1.0",
+    "gulp-spawn-mocha": "~2.2.1",
+    "gulp-uglify": "~1.4.2",
+    "gulp-watch": "~4.3.5",
+    "jshint-stylish": "~2.1.0",
     "kerberos": "0.0.17",
     "method-override": "~2.3.1",
     "mongoose": "4.2.5",
@@ -57,16 +67,6 @@
     "socket.io": "~1.3.0",
     "socket.io-client": "~1.3.0",
     "swig": "~1.4.0",
-    "gulp": "~3.9.0",
-    "gulp-concat": "~2.6.0",
-    "gulp-cssmin": "~0.1.7",
-    "gulp-jshint": "~1.12.0",
-    "gulp-rename": "~1.2.2",
-    "gulp-sass": "~2.1.0",
-    "gulp-spawn-mocha": "~2.2.1",
-    "gulp-uglify": "~1.4.2",
-    "gulp-watch": "~4.3.5",
-    "jshint-stylish": "~2.1.0",
     "time": "~0.11.0"
   },
   "devDependencies": {