Добро пожаловать в девятую часть руководства по созданию веб-приложения с помощью Node.js. В рамках серии уроков будет рассказано про основные особенности и трудности, которые возникают при работе с Node.js.
Предыдущие части:
В одной из предыдущих частей данной серии я написал небольшой хак ,
приводящий строку соединения mongodb к формату,
который понимает connect-mongodb. Я связался с автором через GitHub, и
он достаточно быстро доработал библиотеку. Теперь она понимает формат
строки соединения connect-mongodb. То есть, теперь функция
mongoStoreConnectionArgs
нам не нужна.
Чтобы установить конкретную версию пакета, необходимо выполнить:
npm install connect-mongodb@0.1.1
А код, устанавливающий соединение, теперь выглядит так (app.js
):
// Где-то в начале файла
mongoStore = require('connect-mongodb@0.1.1')
// Функцию mongoStoreConnectionArgs можно удалить
// Код установки соединения с mongodb
// в блоке настройки приложения
app.use(express.session({
store: mongoStore(app.set('db-uri'))
}));
Реализация данной функциональности требует некоторой доработки на стороне сервера. Обычно реализуется следующая логика:
Это схема разработана с целью предотвращения кражи куки и описана в документе Барри Джаспэна (Barry Jaspan) — Improved Persistent Login Cookie Best Practice.
В файле models.js
я добавил модель LoginToken
:
mongoose.model('LoginToken', {
properties: ['email', 'series', 'token'],
indexes: [
'email',
'series',
'token'
],
methods: {
randomToken: function() {
return Math.round((new Date().valueOf() * Math.random())) + '';
},
save: function() {
// Автоматически сохраняем ключи
this.token = this.randomToken();
this.series = this.randomToken();
this.__super__();
}
},
getters: {
id: function() {
return this._id.toHexString();
}
}
});
exports.LoginToken = function(db) {
return db.model('LoginToken');
};
// Использовать следующим образом:
// app.LoginToken = LoginToken = require('./models.js').LoginToken(db);
Это основная вещь. Она автоматически создает ключи при сохранении модели.
Теперь добавим простой Jade-шаблон в views/sessions/new.jade
:
div
label(for='remember_me') Remember me:
input#remember_me(type='checkbox', name='remember_me')
Далее необходимо доработать функцию-обработчик POST-запросов для сессий:
при необходимости должен создаваться LoginToken
:
app.post('/sessions', function(req, res) {
User.find({ email: req.body.user.email }).first(function(user) {
if (user && user.authenticate(req.body.user.password)) {
req.session.user_id = user.id;
// Запомнить меня
if (req.body.remember_me) {
var loginToken = new LoginToken({ email: user.email });
loginToken.save(function() {
res.cookie('logintoken', loginToken.cookieValue, {
expires: new Date(Date.now() + 2 * 604800000),
path: '/'
});
});
}
res.redirect('/documents');
} else {
req.flash('error', 'Incorrect credentials');
res.redirect('/sessions/new');
}
});
});
При выходе ключи должны удаляться:
app.del('/sessions', loadUser, function(req, res) {
if (req.session) {
LoginToken.remove({ email: req.currentUser.email }, function() {});
res.clearCookie('logintoken');
req.session.destroy(function() {});
}
res.redirect('/sessions/new');
});
API для работы с куками (cookie) в Express выклядит следующим образом:
// Создать куку
res.cookie('key', 'value');
// Прочитать куку
req.cookies.key;
// Удалить куку
res.clearCookie('key');
Имена для кук всегда строчные. Обратите внимание, что все операции записи
куки выполняются через объект ответа (res
), а операции чтения выполняются
через объект запроса (req
).
Далее необходимо добавить проверку наличия LoginToken
в функцию среднего
слоя (middleware) loadUser
:
function authenticateFromLoginToken(req, res, next) {
var cookie = JSON.parse(req.cookies.logintoken);
LoginToken.find({ email: cookie.email,
series: cookie.series,
token: cookie.token })
.first(function(token) {
if (!token) {
res.redirect('/sessions/new');
return;
}
User.find({ email: token.email }).first(function(user) {
if (user) {
req.session.user_id = user.id;
req.currentUser = user;
token.token = token.randomToken();
token.save(function() {
res.cookie('logintoken', token.cookieValue, {
expires: new Date(Date.now() + 2 * 604800000),
path: '/' });
next();
});
} else {
res.redirect('/sessions/new');
}
});
});
}
function loadUser(req, res, next) {
if (req.session.user_id) {
User.findById(req.session.user_id, function(user) {
if (user) {
req.currentUser = user;
next();
} else {
res.redirect('/sessions/new');
}
});
} else if (req.cookies.logintoken) {
authenticateFromLoginToken(req, res, next);
} else {
res.redirect('/sessions/new');
}
}
Обратите внимание, что весь код, относящийся к проверке наличия
LoginToken
, оформлен в отдельную функцию. Это способствует
сохранению читабельности кода функции loadUser
.