[Sails.js] sails.io.socketを使ったリクエストでログインユーザーを取得する

# やりたいこと
Sails.jsを使ったWEBアプリで、Passport.jsを使って通常のHTTPリクエストでユーザー認証を行う場合、WebSocket通信でログインユーザー情報を取得する方法が見つからなくてハマったのでメモしておきます。
ちなみにSails.jsの中の実装とかは見ておらず、とりあえず上記の目標を達成できる方法を見つけたって感じなので邪道かもしれません。ベターな方法を知ってる方がいたらご教示いただきたいです。

# 問題
Passportでユーザー認証すると、HTTPリクエストの場合、ログイン状態のチェックは
`req.isAuthenticated()`
ログインユーザー情報は
`req.user`
で確認できます。

しかし、HTTPでユーザー認証したあと、websocket通信でログイン情報を確認しようとした時、上記の２つのプロパティはundefinedでした。

# 解決策？
Sailsのドキュメントを見ると、HTTPとWebsocketでsessionを共有する仕組みになってはいるらしい。ということでreq.sessionの中身を見て見る。

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

  // websocket通信
  if (req.isSocket) {
    sails.log(req.session);
  }

  if (req.isAuthenticated()) {
    return next();
  }
```

すると、sessionにはpassportプロパティが入っていることが判明。やや邪道な感じではあるが、これを使ってログインチェックとユーザー情報の取得ができそう。

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

  // websocket通信の時は、直接sessionをチェックして認証状態を確認
  if (req.isSocket) {
    if (req.session.passport && req.session.passport.user) {
      return next();
    }
    res.statusCode(401);
    return res.send();
  }

  // HTTPリクエストならPassportが用意してくれるメソッドを使える
  if (req.isAuthenticated()) {
    return next();
  }
  req.addFlash('warning', 'ログインしてください。');
  return res.redirect('/');
};
```

以下、今回のpassportログイン部分の実装です。

## 環境情報
Node.js v7.2.0
Sails.js v1.0
passport v0.3.2
passport-local v1.0.0

## ログイン
Userモデルはemailとpasswordを持っているとする。

```api/models/User.js
var bcrypt = require('bcrypt');

module.exports = {

  attributes: {
    email: {
      type: 'string',
      required: true,
      unique: true,
      isEmail: true
    },
    password: {
      type: 'string',
      minLength: 8,
      required: true
    }
  },

  customToJSON: function() {
    // パスワードが漏れないようにjsonからdeleteする
    let obj = _.clone(this);
    delete obj.password;
    return obj;
  },

  beforeCreate: function(user, callback) {
    // passwordはbcryptで暗号化
    bcrypt.genSalt(10, function(err, salt) {
      if (err) {
        sails.error(err);
        return;
      }
      bcrypt.hash(user.password, salt, function(err, hash){
        if (err) {
          callback(err);
        } else {
          user.password = hash;
        }

        callback();
      });
    });
  }

};
```

次にpassportの設定。

```javascript:config/passport.js
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var bcrypt = require('bcrypt');

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findOne({id: id}, function(err, user) {
    done(err, user);
  });
});

passport.use(new LocalStrategy(
  {
    usernameField: 'email',
    passwordField: 'password'
  },
  function(email, password, done) {
    User.findOne({email: email}, function(err, user) {
      if (err) {
        return done(err);
      }
      if (!user) {
        return done(null, false, {message: 'このEmailは登録されていません。'});
      }

      bcrypt.compare(password, user.password, function (err, res) {
        if (!res) {
          return done(null, false, {message: 'パスワードが間違っています。', err: err});
        }
        var returnUser = {
          email: user.email,
          createdAt: user.createdAt,
          id: user.id
        };
        return done(null, returnUser, {message: 'ログインしました。'});
      });
    });
  }
));
```

ログインアクション

```javascript:api/controller/AuthController.js
var passport = require('passport');

module.exports = {
  login: function(req, res) {
    passport.authenticate('local', function(err, user, info) {
      if ((err) || (!user)) {
        return res.badRequest({
          message: info.message,
          err: err
        });
      }

      req.logIn(user, function(err) {
        if (err) {
          res.badRequest(err);
        }
        return res.redirect('/home');
      });
    })(req, res);
  },
```

# まとめ
Passport-sails-hook的なものが用意されてたら迷わないしもっと綺麗にかけるだろうな。
せめて[SailsドキュメントのPassportに関する記述](http://sailsjs.com/documentation/concepts/policies/sails-passport)に、この辺の注意事項を追記してプルリク送ってみようかな。