Passport.js + mongoose объединяем две коллекции

—

от автора

Добрый день! Решил поделиться своими небольшими достижениями в использовании популярной библиотеки Passport.js. Задача была такой: использовать две локальные стратегии passport.js при том, что пользователи находятся в двух разных коллекциях Mongo Atlas.

Я искал ответы в интернете и встретил подобные вопросы, но либо ответа нет вообще, либо все не то:

Но из этих двух вопросов мне понравился больше второй, на его основе и рассмотрим как же реализовать локальные стратегии Passport.js если пользователи находятся в разных коллекциях.

И так, Дано: две коллекции пользователей в Mongo Atlas, и две локальные стратеги в Passport.js.

В данной статье я не буду описывать как сделать свою локальную стратегию. Это хорошо описано в этой статье, я как раз опирался на нее, когда писал свою, рекомендую.

Задача, реализовать аутентификацию учителей по логину и паролю, учеников по Фамилии и номеру класса. Тут кстати в вопросе за одно и объясняется, почему мы используем две локальных стратегии, именно потому, что способы идентификации пользователя разные (учителя- логин/пароль, ученики — Фамилия/класс)

Давайте рассмотрим, что мы имеем.

Коллекция учителей:

teachers = { login: "Zayka-English", password: "qwerty", klass: "7Б", ... }

Коллекция ученики:

students = { surname: "Иванов", klass: "7Б", category: "Ботан", ... }

Стратегия passport.js для входа учителей:

passport.use('teachers_login', new LocalStrategy({     // Переписываем переменные из того что пришло из формы     usernameField: 'login',     passReqToCallback: true },     async function (req, username, password, done) {         // Поиск пользователя по логину         await Teachers.findOne({ login: username }, function (err, user) {             if (err) { return done(err); }             // проверяем результат поиска             if (!user) {                 return done(null, false, req.flash('msg', ['Ошибка', ': Пользователь не найден']));             } else {                 // Проверяем пароль                 if (user.password == password) {                     console.log("Пользователь выполнил вход");                     return done(null, user);                 } else {                     console.log('Неправильно введен пароль!');                     return done(null, false, req.flash('msg', ['Ошибка', ': Неправильно введен пароль']));                 }             }         });     } ))

Стратегия passport.js для входа учеников:

passport.use('students_login', new LocalStrategy({     // Переписываем переменные из того что пришло из формы     usernameField: 'surname',   	userpasswordField: 'klass',     passReqToCallback: true },     async function (req, username, password, done) {         // Поиск пользователя по Фамилии         await Students.findOne({ surname: username }, function (err, user) {             if (err) { return done(err); }             // проверяем результат поиска             if (!user) {                 return done(null, false, req.flash('msg', ['Ошибка', ': Пользователь не найден']));             } else {                 // Проверяем пароль                 if (user.password == password) {                     console.log("Пользователь выполнил вход");                     return done(null, user);                 } else {                     return done(null, false, req.flash('msg', ['Ошибка', ': Неправильно введен класс']));                 }             }         });     } ))

Ну и конечно же сериализация и десериализация:

// Записывает id пользователя в cookie passport.serializeUser(function (user, done) {     done(null, user.id); });  // Проверяет пользователя по id из cookie passport.deserializeUser(function(id, done) {   User.findById(id, function(err, user) {     done(err, user);   }); });

Сериализация и десериализация взяты с сайта passport.js и требуют поправки, чем мы сейчас и займемся. Вся проблема нашей задачи состоит в том, что во время входа пользователя сериализация сработает и запишет в cookie id пользователя, но при десериализации нам нужно реализовать поиск в нужной коллекции. В одном из ответов в интернете было предложено поочередно проверять обе коллекции. То есть сначала ищем id в коллекции с учителями, если там нет. то ищем в коллекции с учениками. Но такой подход использует несколько запросов к БД, что увеличивает время на десериализацию. А что если коллекций не две, а больше? Я где-то видел спрашивалось о трех коллекциях, или представим, что коллекций 5! В этом случае десериализация может происходить достаточно долго. Что же делать? А ответ оказался достаточно прост. Нам необходимо дать понять, в какой из коллекций искать id пользователя при десериализации. Давайте сделаем это.

Для того, что бы это реализовать для начала в наших коллекциях добавим к объектам с пользователями некоторые флаги:

Коллекция учителей:

teachers = {    login: "Zayka-English",    password: "qwerty",    klass: "7Б",    flag: "teacher", // в значении флага напишем к какой категории относится пользователь   ... }

Коллекция учеников:

students = { 	surname: "Иванов", 	klass: "7Б", 	category: "Ботан", 	flag: "student", //тут делаем аналогично 	...}

Теперь во время входа пользователя мы запишем в cookie не только id пользователя, но и флаг, который есть в нашем объекте User. Это делается при сериализации

passport.serializeUser(function (user, done) { // На данный этапе в переменной user находится объект с данными пользователя // который мы получили при его идентификации в наших стратегиях     done(null, [user.flag, user.id]); });

Теперь после того как пользователь «представился» мы внесли в cookie массив, в котором первый элемент — это flag (категория к которой относится пользователь), а второй — это id пользователя.

Теперь при десериализации нам необходимо просто проверить к какой из категорий относится пользователь и проверять его id в конкретной коллекции:

passport.deserializeUser(function(id, done) {   // На этом этапе переменная id содержит наш массив   // Если флаг "учитель", ищем id в коллекции "Учителя"   if (id[0] == "teacher"){ 	  Teacher.findById(id[1], function(err, user) {     	done(err, user);   	})   // Если иное, ищем id в коллекции "Ученики"   }else{   	Student.findById(id[1], function(err, user) {     	done(err, user);   	})   } });

При такой реализации десериализация будет происходить именно в той коллекции, к которой относится пользователь, то есть будет происходить только один запрос к БД к конкретной коллекции.

Так как у нас всего две коллекции, то выбор коллекции при десериализации мы осуществили через if/else, если у вас больше коллекций, конечно же будет уместней использовать конструкцию switch

На этом все дорогие друзья, в следующей статье я напишу как же сделать авторизацию учителя, что бы в ответе получить объект с данными учителя и с массивом объектов учеников)

Использовал при написании:

Пожалуй, лучший учебник по JavaSkript

Официальная документация по Passport.js

Статья о том, как создать свою локальную коллекцию в passport.js

ссылка на оригинал статьи https://habr.com/ru/post/548318/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *