Аутентификация между серверов на node js. Теперь запустим все это вместе. Маршруты Index и Users
Node Hero: Глава 8В этой главе вы узнаете, как реализовать стратегию локальной аутентификации в Node.js приложении с использованием Passport.js и Redis.
Технологии, которые мы будем использоватьПрежде чем перейти к написанию кода, давайте рассмотрим новые технологии, которые мы будем использовать в этой главе.
Что такое Passport.js?Простая, ненавязчивая аутентификация для Node.js - passportjs.org
Passport - это middleware для проверки подлинности, которую мы собираемся использовать для управления сессиями.
Что такое Redis?Redis это опенсорс (лицензии BSD) хранилище структур данных в оперативной памяти, используемое как база данных, кэш и брокер сообщений - redis.io
Мы собираемся хранить информацию о сессии пользователя в Redis, а не в памяти процесса. Таким образом, наше приложение будет намного проще масштабировать.
Демонстрационное приложениеДля демонстрационных целей создадим приложение, которое умеет только следующее:
- предоставляет форму входа
- предоставляет две защищённые страницы:
- страницу профиля
- безопасные заметки
Вы уже научились структурировать Node.js-проекты в предыдущей главе Node Hero, поэтому давайте использовать эти знания!
Мы собираемся использовать следующую структуру:
├── app
| ├── authentication
| ├── note
| ├── user
| ├── index.js
| └── layout.hbs
├── config
| └── index.js
├── index.js
└── package.json
Как вы можете видеть, мы организуем файлы и каталоги вокруг функций. У нас будет страница пользователя, страница заметок и некоторые функции, связанные с проверкой подлинности.
(Вы можете скачать исходный код по ссылке https://github.com/RisingStack/nodehero-authentication )
Процесс аутентификации в Node.jsНаша цель - реализовать в нашем приложении следующий процесс аутентификации:
Чтобы настроить такую стратегию аутентификации, выполните следующие три действия:
Мы будем использовать Express для серверной среды - вы можете узнать больше на эту тему, перечитав главу «Ваш первый сервер на Node.js».
// file:app/index.js
const express = require("express")
const session = require("express-session")
const RedisStore = require("connect-redis")(session)
const app = express()
app.use(session({
store: new RedisStore({
url: config.redisStore.url
}),
secret: config.redisStore.secret,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
Что мы тут делаем?
Прежде всего, нам нужны все зависимости, которые требуются для управления сессией. После этого мы создали новый экземпляр из модуля express-session , который будет хранить наши сессии.
Для хранения сессий мы используем Redis, но вы можете использовать любые другие, такие как MySQL или MongoDB.
Шаг 2: Настройка PassportPassport - отличный пример библиотеки, использующей плагины. В этом уроке мы добавляем модуль passport-local , который добавляет простую локальную стратегию аутентификации с использованием имён пользователей и паролей.
Для простоты в этом примере (см. ниже) мы не используем базу данных, вместо неё используется экземпляр объекта пользователя в памяти. В реальных приложениях findUser будет искать пользователя в базе данных.
// file:app/authenticate/init.js
const passport = require("passport")
const LocalStrategy = require("passport-local").Strategy
const user = {
username: "test-user",
password: "my-password",
id: 1
}
passport.use(new LocalStrategy(
function(username, password, done) {
findUser(username, function (err, user) {
if (err) {
return done(err)
}
if (!user) {
return done(null, false)
}
if (password !== user.password) {
return done(null, false)
}
return done(null, user)
})
}
))
Как только findUser возвращается с нашим объектом пользователя, остаётся только сравнить введённый пользователем и реальный пароль, чтобы увидеть, есть ли совпадение.
Если они совпадают, мы разрешаем пользователю войти (возвращая объект пользователь в passport - return done(null, user)), если нет - возвращаем ошибку авторизации(путём возврата null - return done(null)).
Примечание переводчика: В настоящих приложениях стараются никогда не хранить пароли пользователей в открытом виде. В базу данных записывают хэш пароля и сравнивают его с хэшом значения, введённого пользователем.
Шаг 3: Добавляем защищённые разделы на сайтеЧтобы добавить защищенные разделы на сайт, мы используем шаблон middleware Express. Для этого сначала создадим middleware для аутентификации:
// file:app/user/init.js
const passport = require("passport")
app.get("/profile", passport.authenticationMiddleware(), renderProfile)
Она имеет только одну задачу - если пользователь аутентифицирован (имеет правильные cookie-файлы), она просто вызывает следующую middleware. В противном случае пользователь перенаправляется на страницу, где он может войти в систему.
Использование этой middleware также просто, как добавление любой другой middleware в определение роута.
// file:app/authentication/middleware.js
function authenticationMiddleware () {
return function (req, res, next) {
if (req.isAuthenticated()) {
return next()
}
res.redirect("/")
}
}
В этом разделе учебника по Node.js вы узнали, как добавить базовую аутентификацию в ваше приложение. Позже вы можете расширить его с помощью различных стратегий аутентификации, таких как Facebook или Twitter. Вы можете найти больше стратегий по адресу http://passportjs.org/ .
Полный рабочий пример нашего демонстрационного приложения вы можете найти на GitHub: https://github.com/RisingStack/nodehero-authentication
Следующая глава Node Hero будет посвящена юнит-тестированию Node.js приложений. Вы узнаете такие концепции, как юнит-тестирование, пирамида тестирования, дублёры и многое другое!
В этой главе вы узнаете, как реализовать стратегию локальной аутентификации в Node.js приложении с использованием Passport.js и Redis.
This article was translated to Russian by Andrey Melikhov, a front-end developer from Yandex.Money and editor of the collective blog about front-end, devSchacht . Find Andrey on: Twitter , GitHub , Medium & SoundCloud
Перевод этой статьи сделан Андреем Мелиховым, фронтенд-разработчиком из компании Яндекс.Деньги, редактором коллективного блога о фронтенде, devSchacht . Twitter | GitHub | Medium | SoundCloud
Технологии, которые мы будем использовать
Прежде чем перейти к написанию кода, давайте рассмотрим новые технологии, которые мы будем использовать в этой главе.
Что такое Passport.js?Простая, ненавязчивая аутентификация для Node.js - passportjs.org
Passport - это middleware для проверки подлинности, которую мы собираемся использовать для управления сессиями.
Что такое Redis?Redis это опенсорс (лицензии BSD) хранилище структур данных в оперативной памяти, используемое как база данных, кэш и брокер сообщений - redis.io
Мы собираемся хранить информацию о сессии пользователя в Redis, а не в памяти процесса. Таким образом, наше приложение будет намного проще масштабировать.
Демонстрационное приложениеДля демонстрационных целей создадим приложение, которое умеет только следующее:
- предоставляет форму входа
- предоставляет две защищённые страницы:
- страницу профиля
- безопасные заметки
Вы уже научились структурировать Node.js-проекты в предыдущей главе Node Hero, поэтому давайте использовать эти знания!
Мы собираемся использовать следующую структуру:
├── app | ├── authentication | ├── note | ├── user | ├── index.js | └── layout.hbs ├── config | └── index.js ├── index.js └── package.json
Как вы можете видеть, мы организуем файлы и каталоги вокруг функций. У нас будет страница пользователя, страница заметок и некоторые функции, связанные с проверкой подлинности.
Процесс аутентификации в Node.jsНаша цель - реализовать в нашем приложении следующий процесс аутентификации:
Чтобы настроить такую стратегию аутентификации, выполните следующие три действия:
Мы будем использовать Express для серверной среды - вы можете узнать больше на эту тему, перечитав главу «Ваш первый сервер на Node.js».
// file:app/index.js const express = require("express") const passport = require("passport") const session = require("express-session") const RedisStore = require("connect-redis")(session) const app = express() app.use(session({ store: new RedisStore({ url: config.redisStore.url }), secret: config.redisStore.secret, resave: false, saveUninitialized: false })) app.use(passport.initialize()) app.use(passport.session())
Что мы тут делаем?
Прежде всего, нам нужны все зависимости, которые требуются для управления сессией. После этого мы создали новый экземпляр из модуля express-session , который будет хранить наши сессии.
Для хранения сессий мы используем Redis, но вы можете использовать любые другие, такие как MySQL или MongoDB.
Шаг 2: Настройка PassportPassport - отличный пример библиотеки, использующей плагины. В этом уроке мы добавляем модуль passport-local , который добавляет простую локальную стратегию аутентификации с использованием имён пользователей и паролей.
Для простоты в этом примере (см. ниже) мы не используем базу данных, вместо неё используется экземпляр объекта пользователя в памяти. В реальных приложениях findUser будет искать пользователя в базе данных.
File:app/authenticate/init.js const passport = require("passport") const LocalStrategy = require("passport-local").Strategy const user = { username: "test-user", password: "test-password", id: 1 } passport.use(new LocalStrategy(function(username, password, done) { findUser(username, function (err, user) { if (err) { return done(err) } if (!user) { return done(null, false) } if (password !== user.password) { return done(null, false) } return done(null, user) }) }))
Как только findUser возвращается с нашим объектом пользователя, остаётся только сравнить введённый пользователем и реальный пароль, чтобы увидеть, есть ли совпадение.
Если они совпадают, мы разрешаем пользователю войти (возвращая объект пользователь в passport - return done(null, user)), если нет - возвращаем ошибку авторизации(путём возврата null - return done(null)).
Примечание переводчика: В настоящих приложениях стараются никогда не хранить пароли пользователей в открытом виде. В базу данных записывают хэш пароля и сравнивают его с хэшом значения, введённого пользователем.
Шаг 3: Добавляем защищённые разделы на сайтеЧтобы добавить защищенные разделы на сайт, мы используем шаблон middleware Express. Для этого сначала создадим middleware для аутентификации:
// file:app/user/init.js const passport = require("passport") app.get("/profile", passport.authenticationMiddleware(), renderProfile)
Она имеет только одну задачу - если пользователь аутентифицирован (имеет правильные cookie-файлы), она просто вызывает следующую middleware. В противном случае пользователь перенаправляется на страницу, где он может войти в систему.
Использование этой middleware также просто, как добавление любой другой middleware в определение роута.
// file:app/authentication/middleware.js function authenticationMiddleware () { return function (req, res, next) { if (req.isAuthenticated()) { return next() } res.redirect("/") } }
РезюмеВ этом разделе учебника по Node.js вы узнали, как добавить базовую аутентификацию в ваше приложение. Позже вы можете расширить его с помощью различных стратегий аутентификации, таких как Facebook или Twitter. Вы можете найти больше стратегий по адресу http://passportjs.org/ .
Полный рабочий пример нашего демонстрационного приложения вы можете найти на GitHub: https://github.com/RisingStack/nodehero-authentication
Следующая глава Node Hero будет посвящена юнит-тестированию Node.js приложений. Вы узнаете такие концепции, как юнит-тестирование, пирамида тестирования, дублёры и многое другое!
Gergely NemethCo-founder of RisingStack
Please enable JavaScript to view theВ этой статье я опишу полноценное решение по авторизации на основе JSON Web Token (JWT) для Node.js и Koa с хранением хэшей паролей в MongoDB. От читателя ожидаются базовые знания Node.js и принципов работы с MongoDB через Mongoose.
Несколько слов, о чем конкретно пойдет речь и почему.
Почему Коа. Не смотря на значительно большую популярность фреймворка Express, Koa предоставляет возможность писать приложения используя современный синтаксис async/await. Использование async/await вместо callback’ов является достаточно большим стимулом, чтобы присмотреться к этому фреймворку.
Почему JWT. Подход к авторизации с помощью сессий можно уже назвать устаревшим, так как он не позволяет использовать его в мобильных приложениях и там, где нет поддержки cookies. Также проблемы с сессиями могут возникнуть в кластерных системах. JWT авторизация не имеет этих недостатков, и обладает еще рядом дополнительных преимуществ. Более подробно про JWT можно прочитать
В статье будет рассмотрено полноценное решение по авторизации с использованием:
Const Koa = require("koa"); // ядро
const Router = require("koa-router"); // маршрутизация
const bodyParser = require("koa-bodyparser"); // парсер для POST запросов
const serve = require("koa-static"); // модуль, который отдает статические файлы типа index.html из заданной директории
const logger = require("koa-logger"); // опциональный модуль для логов сетевых запросов. Полезен при разработке.
const app = new Koa();
const router = new Router();
app.use(serve("public"));
app.use(logger());
app.use(bodyParser());
2. Подключаем Passport.js
. Passport.js позволяет гибко настраивать авторизацию, используя разные механизмы, которые называются Стратегиями (локальная, социальные сети д.р.). В настоящий момент библиотека насчитывает более 300 вариантов стратегий.
Const passport = require("koa-passport"); //реализация passport для Koa
const LocalStrategy = require("passport-local"); //локальная стратегия авторизации
const JwtStrategy = require("passport-jwt").Strategy; // авторизация через JWT
const ExtractJwt = require("passport-jwt").ExtractJwt; // авторизация через JWT
app.use(passport.initialize()); // сначала passport
app.use(router.routes()); // потом маршруты
const server = app.listen(3000);// запускаем сервер на порту 3000
3. Подключаем работу с JWT.
В двух словах JWT - это просто JSON в котором может храниться, например, email пользователя. Этот JSON подписывается секретным ключом, что не позволяет этот email изменить, хотя позволяет его прочитать.
Таким образом, получая с клиента JWT вы уверены, что к вам пришел именно тот пользователь, за которого он себя выдает (при условии, что его JWT не был кем-то украден, но это уже совсем другая история).
Const jwtsecret = "mysecretkey"; // ключ для подписи JWT
const jwt = require("jsonwebtoken"); // аутентификация по JWT для hhtp
const socketioJwt = require("socketio-jwt"); // аутентификация по JWT для socket.io
4. Подключаем socket.io.
В двух словах socket.io - это модуль для работы приложений, которые реагируют на изменения происходящие на сервере, например его можно использовать для чата. Если сервер и браузер поддерживают протокол WebSockets, то socket.io будет использовав его, иначе он поищет другие механизмы реализации двустороннего общения браузера с сервером.
Const socketIO = require("socket.io");
5. Подключаем MongoDB
для хранения объектов пользователей.
Const mongoose = require("mongoose"); // стандартная прослойка для работы с MongoDB const crypto = require("crypto"); // модуль node.js для выполнения различных шифровальных операций, в т.ч. для создания хэшей.
Теперь запустим все это вместе Объект пользователя (user ) будет состоять из его имени, e-mail и хэша пароля.Для превращения пароля, получаемого из POST запроса в хэш, который будет храниться в базе применяется концепция виртуальных полей. Виртуальное поле - это поле, которое есть в модели Mongoose, но которого нет в базе MongoDB.
Mongoose.Promise = Promise; // Просим Mongoose использовать стандартные Промисы
mongoose.set("debug", true); // Просим Mongoose писать все запросы к базе в консоль. Удобно для отладки кода
mongoose.connect("mongodb://localhost/test"); // Подключаемся к базе test на локальной машине. Если базы нет, она будет создана автоматически.
Создаем схему и модель для Пользователя:
Const userSchema = new mongoose.Schema({
displayName: String,
email: {
type: String,
required: "Укажите e-mail",
unique: "Такой e-mail уже существует"
},
passwordHash: String,
salt: String,
}, {
timestamps: true
});
userSchema.virtual("password")
.set(function (password) {
this._plainPassword = password;
if (password) {
this.salt = crypto.randomBytes(128).toString("base64");
this.passwordHash = crypto.pbkdf2Sync(password, this.salt, 1, 128, "sha1");
} else {
this.salt = undefined;
this.passwordHash = undefined;
}
})
.get(function () {
return this._plainPassword;
});
userSchema.methods.checkPassword = function (password) {
if (!password) return false;
if (!this.passwordHash) return false;
return crypto.pbkdf2Sync(password, this.salt, 1, 128, "sha1") == this.passwordHash;
};
const User = mongoose.model("User", userSchema);
Для более глубокого понимания механизма работы с хэшами паролей можно почитать про команду pbkdf2Sync
в доке по Node.js
Шаг 1.
Новый пользователь регистрируется, и создается запись о нем в базе MongoDB.
Шаг 2.
Пользователь логинится с паролем на сайте и при успешном вводе логина и пароля получает JWT.
Шаг3.
Пользователь заходит на произвольный ресурс, отсылает свой JWT, по которому и авторизуется уже без ввода пароля.
Механизм настройки Passport.js состоит из двух этапов:
Этап 1.
Настройка Стратегий. Стратегия при успешной авторизации возвращает объект user, описанный ранее в схеме userSchema.
Этап 2.
Использование полученного на этапе 1 объекта user для последующих действий, например, создания для него JWT.
Passport.use(new LocalStrategy({
usernameField: "email",
passwordField: "password",
session: false
},
function (email, password, done) {
User.findOne({email}, (err, user) => {
if (err) {
return done(err);
}
if (!user || !user.checkPassword(password)) {
return done(null, false, {message: "Нет такого пользователя или пароль неверен."});
}
return done(null, user);
});
}));
Настраиваем Passport JWT Strategy. Более подробно, как работает стратегия можно прочитать на .
// Ждем JWT в Header const jwtOptions = { jwtFromRequest: ExtractJwt.fromAuthHeader(), secretOrKey: jwtsecret }; passport.use(new JwtStrategy(jwtOptions, function (payload, done) { User.findById(payload.id, (err, user) => { if (err) { return done(err) } if (user) { done(null, user) } else { done(null, false) } }) }));
Этап 2 Мы создадим REST API, который будет работать с объектом user.API будет состоять из трех endpoints, соответствующих трем Шагам процесса авторизации, описанному выше.
Post запрос на /user – создает нового пользователя. Обычно этот API вызывается при регистрации нового пользователя. В теле запроса мы ожидаем JSON с именем, почтой и паролем пользователя.
Router.post("/user", async(ctx, next) => {
try {
ctx.body = await User.create(ctx.request.body);
}
catch (err) {
ctx.status = 400;
ctx.body = err;
}
});
Post запрос на /login создает JWT для пользоваться. В теле запроса мы ожидаем получить JSON в котором будет почта и пароль пользователя. В продакшене логично JWT выдавать также и при регистрации пользователя.
Router.post("/login", async(ctx, next) => {
await passport.authenticate("local", function (err, user) {
if (user == false) {
ctx.body = "Login failed";
} else {
//--payload - информация которую мы храним в токене и можем из него получать
const payload = {
id: user.id,
displayName: user.displayName,
email: user.email
};
const token = jwt.sign(payload, jwtsecret); //здесь создается JWT
ctx.body = {user: user.displayName, token: "JWT " + token};
}
})(ctx, next);
});
GET запрос на /custom проверяет наличие валидного JWT.
Router.get("/custom", async(ctx, next) => {
await passport.authenticate("jwt", function (err, user) {
if (user) {
ctx.body = "hello " + user.displayName;
} else {
ctx.body = "No such user";
console.log("err", err)
}
})(ctx, next)
});
Теперь сделаем финальный аккорд по настройке авторизации для socket.io. Проблема тут в том, что протокол WebSockets работает поверх tcp, а не http и механизмы REST API к нему не применимы. К счастью, для него есть модуль socketio-jwt, который позволяет достаточно лаконично описать авторизацию через JWT.
Let io = socketIO(server);
io.on("connection", socketioJwt.authorize({
secret: jwtsecret,
timeout: 15000
})).on("authenticated", function (socket) {
console.log("Это мое имя из токена: " + socket.decoded_token.displayName);
socket.on("clientEvent", (data) => {
console.log(data);
})
});
Более подробно про авторизацию через JWT для socket.io можно почитать .
Полную версию кода с описание того, как его протестировать можно посмотреть в
Так же как аутентификация важна для API (* Application Programming Interface - программный интерфейс приложения. Здесь и далее примеч. пер.), она является важной особенностью определенных веб-приложений - тех, у которых имеются страницы и секреты, к которым должен быть доступ только у пользователей, прошедших регистрацию и аутентификацию.
В данном руководстве в процессе изучения реализации регистрации пользователей вы создадите веб-приложение.
Установка приложенияСоздайте новую папку, в которой вы будете работать. В соответствии с темой данного руководства я назвал мою site-auth . Инициализируйте проект в только что созданной папке. Ниже показано, как вы можете это сделать.
Npm init -y
Флаг -y сообщает npm (* менеджер пакетов для JavaScript) о необходимости использования опций по умолчанию.
Отредактируйте часть файла package.json так, чтобы она выглядела, как моя.
#package.json { "name": "site-auth", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": , "author": "izuchukwu1", "license": "ISC", "dependencies": { "bcryptjs": "^2.4.3", "body-parser": "^1.17.1", "connect-flash": "^0.1.1", "cookie-parser": "^1.4.3", "express": "^4.15.2", "express-handlebars": "^3.0.0", "express-messages": "^1.0.1", "express-session": "^1.15.2", "joi": "^13.0.1", "mongoose": "^4.11.12", "morgan": "^1.8.1", "passport": "^0.4.0", "passport-local": "^1.0.0" } }
После этого запустите команду для установки пакетов.
Npm install
Создайте файл в вашей рабочей папке под названием app.js.
Начните с запрашивания зависимостей, которые вы установили, и необходимых файлов.
#app.js const express = require("express"); const morgan = require("morgan") const path = require("path"); const cookieParser = require("cookie-parser"); const bodyParser = require("body-parser"); const expressHandlebars = require("express-handlebars"); const flash = require("connect-flash"); const session = require("express-session"); const mongoose = require("mongoose") const passport = require("passport") require("./config/passport")
Эти зависимости были установлены после выполнения команды npm install. Для того чтобы их использовать в вашем приложении вам необходимо запросить и сохранить их в соответствующие константы.
В данном руководстве вы будете использовать в качестве базы данных MongoDB. Вам необходимо будет сохранить информацию о пользователе в базу данных. Для работы с MongoDB вы будете использовать Mongoose - инструмент для моделирования в Node.js (* ODM (Object Document Mapper - объектно-документный отобразитель)).Настройка Mongoose не вызывает трудностей. Выполняется следующим образом:
#app.js mongoose.Promise = global.Promise mongoose.connect("mongodb://localhost:27017/site-auth")
На данном этапе давайте настроим наше промежуточное ПО (* программное обеспечение).
// 1 const app = express() app.use(morgan("dev")) // 2 app.set("views", path.join(__dirname, "views")) app.engine("handlebars", expressHandlebars({ defaultLayout: "layout" })) app.set("view engine", "handlebars") // 3 app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) app.use(cookieParser()) app.use(express.static(path.join(__dirname, "public"))) app.use(session({ cookie: { maxAge: 60000 }, secret: "codeworkrsecret", saveUninitialized: false, resave: false })); app.use(passport.initialize()) app.use(passport.session()) // 4 app.use(flash()) app.use((req, res, next) => { res.locals.success_mesages = req.flash("success") res.locals.error_messages = req.flash("error") next() }) // 5 app.use("/", require("./routes/index")) app.use("/users", require("./routes/users")) // 6 // catch 404 and forward to error handler app.use((req, res, next) => { res.render("notFound") }); // 7 app.listen(5000, () => console.log("Server started listening on port 5000!"))
views (* представления). Внутри нее создайте две другие папки под названиями layouts и partials . Сформируйте подобную древовидную структуру в папке views. Для этого создайте необходимые файлы в их соответствующих папках.
├── dashboard.handlebars ├── index.handlebars ├── layouts │ └── layout.handlebars ├── login.handlebars ├── notFound.handlebars ├── partials │ └── navbar.handlebars └── register.handlebars
После этого пришло время писать код.
#dashboard.handlebars User DashBoard
Это - инструментальная панель, которая должна быть видна только зарегистрированным пользователям. В данном руководстве это будет ваша секретная страница.
#index.handlebars Site Authentication!
Welcome aboard.
Для приложения необходим макет. Ниже приведен макет, который будете использовать.
#layout/layout.handlebars Site Authentication {{#if success_messages }} {{success_messages}} {{/if}} {{#if error_messages }} {{error_messages}} {{/if}} {{> navbar}} {{{body}}}
Вам понадобится страница входа для зарегистрированных пользователей.
#views/login.handlebars
Please sign in
Email address
Password
Sign in
Файл notFound .handlebars будет использоваться в качестве страницы для показа ошибок.
#views/notFound.handlebars Error
Ваша страница для регистрации должна выглядеть следующим образом.
Please sign up
Email address
Username
Password
Confirm Password
Sign up
Последний файл для ваших представлений с кодом навигационной панели приводится ниже.
#partials/navbar.handlebars Site Authentication
После этого вы готовы к рассмотрению некоторых сложных частей приложения.
Проверка данныхВам необходима будет модель User. Исходя из кода для представлений выше, вы можете прийти к заключению, что для модели User необходимы свойства email, username и password. Создайте папку под названием models и файл в ней под названием user.js .
#models/user.js // 1 const mongoose = require("mongoose") const Schema = mongoose.Schema const bcrypt = require("bcryptjs") // 2 const userSchema = new Schema({ email: String, username: String, password: String }, { // 3 timestamps: { createdAt: "createdAt", updatedAt: "updatedAt" } }) // 4 const User = mongoose.model("user", userSchema) module.exports = User
Не стоит сохранять пароли пользователей в незашифрованном виде. Вот что следует сделать, когда пользователь вводит пароль в незашифрованном виде при регистрации. Пароль в незашифрованном виде должен быть захеширован при помощи соли (* в криптографии - случайное число или текст, которые добавляются к данным, шифруемым с помощью пароля), которая будет сгенерирована вашим приложением (используя модуль bcryptjs). Этот захешированный пароль затем сохраняется в базу данных.
Звучит замечательно, неправда ли? Давайте реализуем это в файле user.js
#models/user.js module.exports.hashPassword = async (password) => { try { const salt = await bcrypt.genSalt(10) return await bcrypt.hash(password, salt) } catch(error) { throw new Error("Hashing failed", error) } }
Вы только что создали метод, который будет вызван при регистрации пользователя. В метод передается незашифрованный пароль, введенный пользователем. Как я упоминал ранее, пароль в незашифрованном виде должен быть захеширован при помощи соли, которая будет сгенерирована вашим приложением. Захешированный пароль будет возвращен как пароль для пользователя.
Маршруты Index и UsersСоздайте новую папку под названием routes . В этой новой папке создайте два новых файла: index.js и users.js .
Файл index.js будет очень простым. В нем будет вызываться на исполнение представление index вашего приложения. Помните, как вы настроили промежуточное ПО для ваших маршрутов в вашем файле app.js , когда вы сделали следующее:
App.use("/", require("./routes/index")) app.use("/users", require("./routes/users"))
Итак, ваш маршрут в файле index.js, просто исполняющий страницу index, должен выглядеть следующим образом:
#routes/index.js const express = require("express") const router = express.Router() router.get("/", (req, res) => { res.render("index") }) module.exports = router
Теперь переходим к маршруту users. Пока что в этом файле для маршрута будут выполняться четыре действия:
Вот как выглядит код.
#routes/users.js const express = require("express"); const router = express.Router() const Joi = require("joi") const passport = require("passport") const User = require("../models/user") //validation schema const userSchema = Joi.object().keys({ email: Joi.string().email().required(), username: Joi.string().required(), password: Joi.string().regex(/^{6,30}$/).required(), confirmationPassword: Joi.any().valid(Joi.ref("password")).required() }) router.route("/register") .get((req, res) => { res.render("register") }) .post(async (req, res, next) => { try { const result = Joi.validate(req.body, userSchema) if (result.error) { req.flash("error", "Data entered is not valid. Please try again.") res.redirect("/users/register") return } const user = await User.findOne({ "email": result.value.email }) if (user) { req.flash("error", "Email is already in use.") res.redirect("/users/register") return } const hash = await User.hashPassword(result.value.password) delete result.value.confirmationPassword result.value.password = hash const newUser = await new User(result.value) await newUser.save() req.flash("success", "Registration successfully, go ahead and login.") res.redirect("/users/login") } catch(error) { next(error) } }) module.exports = router
Давайте рассмотрим подробнее, что происходит при поступлении запросов по методу POST .
Значения, введенные в форме для регистрации, доступны через свойство req.body и выглядят подобным образом:
Value: { email: "[email protected]", username: "izu", password: "chinedu", confirmationPassword: "chinedu" },
Это свойство проверяется с помощью схемы userSchema , которую вы создали ранее. Введенные пользователем значения присваиваются константе под названием result.
В случае ошибки при проверке данных сообщение об ошибке отображается пользователю и происходит перенаправление пользователя на страницу регистрации.
В ином случае мы пытаемся установить, существует ли пользователь с тем же адресом электронной почты, поскольку нам не хотелось бы иметь двух или более пользователей с одинаковым адресом. В случае совпадения адресов электронной почты пользователь информируется, что его адрес уже используется.
В ином случае следующим этапом является хеширование пароля. В этот момент вы и вызываете метод hashPassword , который определили в вашем файле user.js. Новый захешированный пароль присваивается константе hash.
Нет необходимости в сохранении confirmationPassword в базу данных. Поэтому это свойство удаляется. Доступный в переменной пароль по прежнему незашифрован. Поскольку не следует сохранять незашифрованный пароль в вашей базе данных, важно заново присвоить в качестве значения пароля созданный ранее хеш. Это осуществляется при помощи следующей строки кода:
Result.value.password = hash
Экземпляр нового пользователя сохраняется в базу данных. Выводится флэш-сообщение, сообщающее, что регистрация прошла успешно, и пользователь перенаправляется на страницу входа в приложение.
Запустите ваш сервер, выполнив в консоли команду:
Node app.js
Перейдите в вашем браузере на страницу http://localhost:5000 , и вы должны будете увидеть ваше новое приложение.
ЗаключениеТеперь вы знаете, как реализовать возможность регистрации в Node-приложениях. Вы поняли важность проверки пользовательских данных и процесс ее осуществления при помощи Joi. Также вы использовали модуль bcryptjs для подсаливания и хеширования вашего пароля.