Здесь описана простая регистрация, авторизация и аутентификация API на Node.js, для реальной работы, желательно дополнительно учитывать, что токены должны иметь ограниченный срок действия, также должно быть ограничение на количество попыток аутентификации.
Структура проекта:
models
db.js
routes
index.js
app.js
Файл app.js:
const express = require('express');
const bodyParser = require('body-parser');
const router = require('./routes');
const app = express();
const PORT = process.env.PORT || 3000
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use('/', router);
app.use(function(req, res, next){
const err = new Error('Ни хрена не найдено!');
err.status = 404;
next(err);
});
app.use(function(err, req, res, next){
res.status(err.status || 500);
res.json({
message: err.message,
error: err
})
})
const server = app.listen(PORT, function () {
console.log('Сервер пашет на порту: ' + server.address().port);
})
В этом файле все достаточно стандартно.
Файл db.js:
const MongoClient = require('mongodb').MongoClient;
const url = "строка подключения к базе данных";
const baza = 'test1';
module.exports.getUser = function(email) {
return new Promise((resolve, reject)=>{
MongoClient
.connect(url, function(err, client){
if (err) {
reject(err);
}
client
.db(baza)
.collection('users')
.find({ "email": email})
.toArray(function(err, results){
if (err) {
reject(err)
}
client.close();
resolve(results);
})
})
})
}
module.exports.getToken = function(token) {
return new Promise((resolve, reject)=>{
MongoClient
.connect(url, function(err, client){
if (err) {
reject(err);
}
client
.db(baza)
.collection('token')
.find({ "token": token})
.toArray(function(err, results){
if (err) {
reject(err)
}
client.close();
resolve(results);
})
})
})
}
module.exports.add = function(tabl, data) {
return new Promise((resolve, reject) => {
MongoClient
.connect(url, function(err, client) {
if (err) {
reject(err);
}
client
.db(baza)
.collection(tabl)
.insertOne(data, function(err, results){
if (err) {
reject(err);
}
client.close();
resolve(results.ops[0]);
})
});
})
}
module.exports.delete = function(email) {
return new Promise((resolve, reject) => {
//const id = new ObjectID(zadacaId);
MongoClient
.connect(url, function(err, client) {
if (err) {
reject(err);
}
client
.db(baza)
.collection('token')
.deleteMany({ "login": email},
function(err, results){
if (err) {
reject(err);
}
client.close();
resolve(results);
})
});
})
}
Этот файл возвращает промиссы взаимодействия с базой данных.
Все самое интересное в файле routes/index.js:
const express = require('express');
const router = express.Router();
const db = require('../models/db');
const bcrypt = require('bcryptjs');
const uuidv4 = require('uuid/v4');
let auth = function(req, res, next) {
db
.getToken(req.headers.authorization)
.then((results)=>{
if (results.length == 0) {
const err = new Error('Не авторизован!');
err.status = 401;
next(err);
} else {
next()
}
})
.catch((err)=>{
next(err);
})
}
const isValidPassword = function(user, password) {
return bcrypt.compareSync(password, user.password);
}
router.get('/', (req, res)=>{
res.json({
message: 'Добро пожаловать!'
})
});
router.get('/secret', auth, (req, res)=>{
res.json({
message: 'Секретная страница!'
})
});
router.post('/registration', (req, res, next)=>{
if(req.body.password === req.body.repeatPassword){
db
.getUser(req.body.email)
.then((results)=>{
if (results.length == 0){
data = {
email: req.body.email,
password: bcrypt.hashSync(req.body.password, bcrypt.genSaltSync(10), null)
};
db
.add('users', data)
.then((results)=>{
res.json({
message: 'Пользователь добавлен: ' + results[0]
})
})
.catch((err)=>{
next(err);
})
} else {
const err = new Error('Такой пользователь уже есть!');
err.status = 400;
next(err);
}
})
.catch((err)=>{
next(err);
})
} else {
const err = new Error('Не совпадает пароль и подтверждение пароля!');
err.status = 400;
next(err);
}
})
router.post('/login', (req, res, next)=>{
db
.getUser(req.body.email)
.then((results)=>{
if (isValidPassword(results[0], req.body.password)) {
data ={};
data.login=req.body.email;
data.token=uuidv4();
db
.delete(req.body.email)
.then((results)=>{
db
.add('token', data)
.then((results)=>{
res.json({
token: results.token
})
})
.catch((err)=>{
next(err)
})
})
.catch((err)=>{
next(err)
})
} else {
const err = new Error('Не верный логин или пароль!');
err.status = 400;
next(err);
}
})
.catch((err)=>{
next(err);
})
})
module.exports = router;
Давайте рассмотрим подробнее, что здесь делаем:
Подключаем модуль ‘bcryptjs’ – он необходим для шифрования пароля, так как в базе данных нужно хранить пароль в зашифрованном виде.
Подключаем модуль ‘uuid/v4’ – для генерации рандомного токена.
Регистрация
router.post('/registration', (req, res, next)=>{
if(req.body.password === req.body.repeatPassword){
db
.getUser(req.body.email)
.then((results)=>{
if (results.length == 0){
data = {
email: req.body.email,
password: bcrypt.hashSync(req.body.password, bcrypt.genSaltSync(10), null)
};
db
.add('users', data)
.then((results)=>{
res.json({
message: 'Пользователь добавлен: ' + results[0]
})
})
.catch((err)=>{
next(err);
})
} else {
const err = new Error('Такой пользователь уже есть!');
err.status = 400;
next(err);
}
})
.catch((err)=>{
next(err);
})
} else {
const err = new Error('Не совпадает пароль и подтверждение пароля!');
err.status = 400;
next(err);
}
})
Регистрация от нас ожидает post запрос, в теле (body) которого три поля: email, password, repeatPassword
Дальше сравниваем совпадает ли поля password и repeatPassword, если не совпадают генирим ошибку и пробрасываем ее дальше.
Далее ищем пользователя в базе данных, если пользователь найден, также генерируем ошибку и пробрасываем ее дальше.
Если все хорошо, то добавляем пользователя в базу данных, зашифровав пароль:
bcrypt.hashSync(req.body.password, bcrypt.genSaltSync(10), null)
Авторизация пользователя
router.post('/login', (req, res, next)=>{
db
.getUser(req.body.email)
.then((results)=>{
if (isValidPassword(results[0], req.body.password)) {
data ={};
data.login=req.body.email;
data.token=uuidv4();
db
.delete(req.body.email)
.then((results)=>{
db
.add('token', data)
.then((results)=>{
res.json({
token: results.token
})
})
.catch((err)=>{
next(err)
})
})
.catch((err)=>{
next(err)
})
} else {
const err = new Error('Не верный логин или пароль!');
err.status = 400;
next(err);
}
})
.catch((err)=>{
next(err);
})
})
Этот роут ожидает от нас post запрос с двумя полями email и password. В случае успешного завершения возвращает “токен” иначе ошибку.
В начале ищем пользователя в базе данных, если не находим то генерируем ошибку и пробрасываем дальше.
Если находим, то проверяем пароль:
if (isValidPassword(results[0], req.body.password)) {
Функция проверки пароля:
const isValidPassword = function(user, password) {
return bcrypt.compareSync(password, user.password);
}
Если пароль верный генерируем токен:
data.token=uuidv4();
Далее очищаем предыдущие токены, которые были в базе данных по этому пользователю и записываем новый.
Если все хорошо, то возвращаем токен пользователю.
Открытый роут
router.get('/', (req, res)=>{
res.json({
message: 'Добро пожаловать!'
})
});
При обращении на localhost:3000, нам вернется всегда ‘Добро пожаловать’, так как это открытый роутер и мы его ничем не закрывали.
Закрытый роут
router.get('/secret', auth, (req, res)=>{
res.json({
message: 'Секретная страница!'
})
});
Отличие лишь в том, что здесь добавлен миделвар auth – который проверяет можно ли пользователю просматривать страницу, и который сравнивает в headers запросе параметр ‘Authorization’ со значением в базе данных, если находит в базе данных токен, тогда разрешает, иначе возвращает ошибку ‘Не авторизован’:
let auth = function(req, res, next) {
db
.getToken(req.headers.authorization)
.then((results)=>{
if (results.length == 0) {
const err = new Error('Не авторизован!');
err.status = 401;
next(err);
} else {
next()
}
})
.catch((err)=>{
next(err);
})
}
Вроде все просто!