著者:しょっさん
プログラミング言語「JavaScript」の実行環境「Node.js」と「Express」フレームワークを使って、基本となるWebアプリの開発手法を習得しましょう。第4回は「蔵書管理アプリケーション」のサンプルプログラムで認証機能を実現す
る方法を解説します。
シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。


図3 「models/user.js」ファイルにLibrariesテーブルとの関係性を追記
user.associate = function (models) {
// associations can be defined here
user.hasMany(models.Library, { foreignKey: 'user_id' });
};
return user;return user;
図4 モデルファイル「models/library.js」の修正
'use strict';
module.exports = (sequelize, DataTypes) => {
var Library = sequelize.define('Library', {
book_title: DataTypes.STRING,
author: DataTypes.STRING,
publisher: DataTypes.STRING,
image_url: DataTypes.STRING(2048),
user_id: DataTypes.INTEGER
},
{
underscored: true
});
Library.associate = function (models) {
// associations can be defined here
Library.hasMany(models.Comment, { foreignKey: 'book_id'});
};
return Library;
};
図5 マイグレーションファイルの追記内容
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
foreignKey: true,
references: {
model: 'users',
key: 'id',
},
onUpdate: 'RESTRICT',
onDelete: 'RESTRICT',
},
図6 作成したseed(日付-demo-user.js)
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
const models = require('../models');
return models.user.bulkCreate([
{
id: 1,
email: 'tak@oshiire.to',
password: '$2b$10$t73WMpPlvyhkWuL.ALWe..OKbU1q1ssR4K5ezVTXLlvaDMtUuAqve',
salt: '$2b$10$t73WMpPlvyhkWuL.ALWe..'
},
{
id: 2,
email: 'sho@oshiire.to',
password: '$2b$10$t73WMpPlvyhkWuL.ALWe..OKbU1q1ssR4K5ezVTXLlvaDMtUuAqve',
salt: '$2b$10$t73WMpPlvyhkWuL.ALWe..'
},
{
id: 3,
email: 'shosan@oshiire.to',
password: '$2b$10$t73WMpPlvyhkWuL.ALWe..OKbU1q1ssR4K5ezVTXLlvaDMtUuAqve',
salt: '$2b$10$t73WMpPlvyhkWuL.ALWe..'
}
]);
},
down: (queryInterface, Sequelize) => {
return queryInterface.bulkDelete('users', null, {});
}
};
図8 passportストラテジの定義
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
const hashPassword = (password, salt) => {
var bcrypt = require('bcrypt');
var hashed = bcrypt.hashSync(password, salt);
return hashed;
};
passport.use(new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password'
},
(username, password, done) => {
models.user.findOne({ where: { email: username } }).then(user => {
if (!user)
return done(null, false, { message: 'Incorrect email.' });
if (hashPassword(password, user.salt) !== user.password)
return done(null, false, { message: 'Incorrect password.' });
return done(null, user.get());
});
}
));
図9 セッション連携の関数「serializeUser」と「deserializeUser」
var session = require('express-session');
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
models.user.findById(id).then(user => {
if (user) {
done(null, user.get());
} else {
done(user.errors, null);
}
});
});
図10 ミドルウエアで認証済みか否かの判定
app.use((req, res, next) => {
if (req.isAuthenticated())
return next();
if (req.url === '/' || req.url === '/login')
return next();
res.redirect('/');
});
app.use('/', index);
app.use('/books', books);
図11 テストコード(supertest-spec.js)の主要部分
/* eslint-env jasmine */
// routing テスト
const request = require('supertest-session');
const app = require('../app');
//let's set up the data we need to pass to the login method
const userCredentials = {
email: 'tak@oshiire.to',
password: 'password'
};
const wrongEmailCredentials = {
email: 'foo',
password: 'password'
};
const wrongPasswordCredentials = {
email: 'tak@oshiire.to',
password: 'foo'
};
//now let's login the user before we run any tests
const authenticatedUser = request(app);
describe('POST /login', () => {
it('should redirect to / with unloggined user', (done) => {
request(app)
.get('/books/')
.expect(302)
.expect('Location', '/', done);
});
it('should success login with correct user', (done) => {
request(app)
.post('/login')
.send(userCredentials)
.expect(302)
.expect('Location', '/books/', done);
});
it('should deny login with wrong email', (done) => {
request(app)
.post('/login')
.send(wrongEmailCredentials)
.expect(302)
.expect('Location', '/', done);
});
it('should deny login with wrong password', (done) => {
request(app)
.post('/login')
.send(wrongPasswordCredentials)
.expect(302)
.expect('Location', '/', done);
});
});
describe('with Login', () => {
beforeAll((done) => {
authenticatedUser
.post('/login')
.send(userCredentials)
.expect(302, done);
});
describe('GET /', () => {
it('respond with http', (done) => {
authenticatedUser
.get('/')
.set('Accept', 'text/html')
.expect(200, done);
});
});
:
(中略)
:
});
describe('GET /logout', () => {
beforeAll((done) => {
authenticatedUser
.post('/login')
.send(userCredentials)
.expect(302, done);
});
it('should go back to login', (done) => {
authenticatedUser
.get('/logout')
.set('Accept', 'text/html')
.expect(302)
.expect('Location', '/', done);
});
});