現場のSails.js #4 セキュリティ関連


# 概要

弊社では一部の社内プロジェクトでSails.jsを利用しています。その中で得られたベストプラクティスやらバッドプラクティスをまとめていこうと思います。
第4回はセキュリティに関する設定を色々と。

# 動作環境
node.js v0.12.7
Sails.js v0.11.2
sails-mysql v0.11.0

## blueprintの無効化
Sails.jsには`blueprint`という便利な機能があり、例えばControllerに何も書かなくてもで`GET /hoge`というリクエストで自動的に`Hoge`モデルの一覧をGETすることが出来たり、`/api/controllers/HogeController.js#find`というメソッドに自動的にルーティングしてくれたりします。
blueprintはプロトタイプを作成するときには非常に便利なのですが、実際の運用を考えると意図しないAPIを公開する可能性があり、セキュリティリスクが高まります。そのため、サービスの公開前にはOFFにすることをオススメします。

```js/config/blueprint.js
module.exports.blueprints = {
  actions: false,
  rest: false,
};
```

そして`/config/routes.js`に必要なルーティングを全て記述します。

```js/config/routes.js
module.exports.routes = {
  /***************************************************************************
   *  Views
   ***************************************************************************/
  'GET /': 'ArticleController.top',

  /***************************************************************************
   *  API
   ***************************************************************************/
  'POST /api/article/:article/favorite': 'api/ArticleController.favorite',
};
```

## policyの設定
Sails.jsは`policy`という機能でControllerのメソッドへのアクセスを制限出来るようになっているため、ログインを伴うアプリの場合は積極的に活用しましょう。

``` js:/config/policies.js
module.exports.policies = {
  '*': true,

  /***************************************************************************
   *  Views
   ***************************************************************************/
  'ArticleController': {
    '*': true,
    'favorite': 'sessionAuthView'
  },

  /***************************************************************************
   *  APIs
   ***************************************************************************/
  'api/ArticleController': {
    '*': false,
    'favorite': 'sessionAuthApi',
  }
};
```

API/ViewControllerで未ログイン時に行いたい処理が違うので、以下のように`sessionAuthView`/`sessionAuthApi`という2つのpolicyファイルを使用します。

``` js:api/policies/sessionAuthView.js
module.exports = function(req, res, next) {

  // User is allowed, proceed to the next policy,
  // or if this is the last policy, the controller
  if (req.user) {
    return next();
  }

  // User is not allowed
  // Viewへのアクセスの場合はリダイレクト
  sails.log.warn('Unauthorized user access denied, ' + req.originalUrl);
  return res.redirect('/');
};
```


``` js:api/policies/sessionAuthApi.js
module.exports = function(req, res, next) {

  // User is allowed, proceed to the next policy,
  // or if this is the last policy, the controller
  if (req.user) {
    return next();
  }

  // User is not allowed
  // APIの場合は403を返す
  return res.send(403);
};
```

## Waterline#create, #update
セキュリティとは若干違いますが、システムの品質のためにService層で気をつけている箇所があります。Sails.jsのO/R Mapper、Waterlineは`create`や`update`に渡したObjectを、再帰的に作成・更新する機能があります。
例えば、チャットサービスで`Room`と`Message`という2つのモデルが存在する場合に以下のコードを実行できます。

```js
Room.create({
  name: 'hoge',
  messages: [
    { text: 'Hello' }
  ]
}).exec();

// リレーションのあるレコードもまとめて生成される
// => Room: {id: 1, name: 'hoge'}、Message: {id: 1, room: 1, text: 'Hello'}
```

実際のシステムでも活用している機能ですが、コードのバグにより意図しないレコードを生成してしまう可能性があります。そのため、O/R MapperへのアクセスはService層で隠蔽し、Serviceに渡されたパラメータをフィルタしてWaterlineに渡すようにしています。

```ts:ArticleService.ts
class ArticleService {
  create(params: Object): Promise<any> {
    return new Promise((resolve, reject) => {
      Article.create(_.pick(params, ['title', 'body]).then(resolve, reject);
    });
  }
}

export = new SELECK.Services.ArticleService();
```

# まとめ
Sails.jsのようなフルスタックのフレームワークは便利機能がたくさんあります。ただし、あまり考えずにシステムが作れてしまう分、よく理解していないと思わぬセキュリティホールやバグを埋め込んでしまう可能性があります。しっかりとフレームワークを理解し、大事な部分では「便利機能を使わない」という選択肢も検討しましょう。