Sequelize?
node.jsのためのORM。RubyのActiveRecordに相当するもの。
他にもbookshelfなどがある。
$ npm i sequelize --save
bcrypt?
bcryptはBlowfishという暗号化アルゴリズムの実装。
ハッシュ化の必要性やSHAファミリなどのハッシュ化ではなぜ駄目なのかについてはこの辺を参照。
- https://yuskamiya.tumblr.com/post/100503173956/bcryptblowfish暗号について調べたので文書化してみました
- http://tech-blog.tsukaby.com/archives/704
node.jsのbcrypt実装はネイティブモジュールでコンパイラのバージョンが古いとビルドできないことがあるので注意。g++4.8以上ならビルドできることを確認している。clangでも7.0.0以降ならまず大丈夫。
$ npm i bcrypt --save
モデル作成
まずモデルを作る。RailsのActiveRecordほど使いやすくはないが一応Sequelizeにもマイグレーションツールがある。
$ npm i sequelize-cli -D
Userモデルをサクッと作る。
$ ./node_modules/.bin/sequelize model:create \
--name User \
--attributes 'name:string, email:string, password_hash:string'
パスワードのスキーマ
生成されたUserモデルでpassword
の属性を追加する。
ここでDataTypes.VIRTUAL
のpassword
はcreateやupdateの際に値を引数に受け取るが、実際にデータベースには保存されない属性になる。
この後の実装で代わりにハッシュ化したものをpassword_hash
に保存する。
password_hash: {
type: DataTypes.STRING,
},
password: {
type: DataTypes.VIRTUAL,
validate: {
min: 8,
max: 32,
},
},
パスワードのハッシュ化
bcrypt.hash()
を使う。bcrypt.hashSync()
は同期メソッドなので使わない。
createやupdateの前にフックする関数を作り、ここでバーチャル属性のpassword
に入っている値をbcryptでハッシュ化してpassword_hash
に突っ込む。
var hashPasswordHook = function(user, options, callback) {
bcrypt.hash(user.get('password'), 10, function(err, hash) {
if (err) {
return callback(err);
}
user.set('password_hash', hash);
return callback(null, options);
});
};
パスワードの認証メソッド
bcrypt.compare()
を使う。bcrypt.compareSync()
は同期メソッドなので使わない。
instanceMethods
にauthenticate
とか適当な名前で生やす。また、先ほどのハッシュ化関数をcreateとupdateの前に走らせるため、hooks
に指定しておく。
var User = sequelize.define('User',
{
//attributes...
},
{
hooks: {
beforeCreate: hashPasswordHook,
beforeUpdate: hashPasswordHook,
},
instanceMethods: {
authenticate: function(password, callback) {
bcrypt.compare(password, this.password_hash, function(err, isValid) {
if (err) {
return callback(err);
} else {
return callback(null, isValid);
}
});
},
},
})
認証の実装
expressの実装例。
express-formなどを使ってあらかじめreq.params.email
の値にバリデーションをかけておく。
auth: function(req, res) {
User.findOne({ where: { email: req.params.email } }).then(function(user) {
if (!user) {
// response
// 404 NOT FOUND
// { user: null }
res.status(404).json({ user });
}
user.authenticate(req.params.password, function(err, isValid) {
if (err) {
res.status(500).json({ err });
}
res.json({ user });
});
});
}