Nodejs correct way of intract with DB - node.js

I am working in Nodejs.
For a 'GET' route I used a method shown below:
var express = require('express');
var mongoose = require('mongoose');
var passport = require('passport');
var config = require('../config/database');
require('../config/passport')(passport);
var express = require('express');
var jwt = require('jsonwebtoken');
var router = express.Router();
var User = require("../models/user");
var Book = require("../models/book");
var router = express.Router();
router.post('/books', passport.authenticate('jwt', { session: false}), function(req, res) {
var token = getToken(req.headers);
if (token) {
console.log(req.body);
var newBook = new Book({
isbn: req.body.isbn,
title: req.body.title,
author: req.body.author,
publisher: req.body.publisher
});
newBook.save(function(err) {
if (err) {
return res.json({success: false, msg: 'Save book failed.'});
}
res.json({success: true, msg: 'Successful created new book.'});
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});
router.get('/books', passport.authenticate('jwt', { session: false}), function(req, res) {
var token = getToken(req.headers);
if (token) {
Book.find(function (err, books) {
if (err) return next(err);
res.json(books);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized Access...'});
}
});
getToken = function (headers) {
if (headers && headers.authorization) {
var parted = headers.authorization.split(' ');
if (parted.length === 2) {
return parted[1];
} else {
return null;
}
} else {
return null;
}
};
module.exports = router;
Is it correct method? because I saw some tutorials where they used GET,POST,PUT and DELETE
Can anyone please suggest a correct approach?
Check my updated full code above
is this correct way
or can you please suggest some other way
if i am wrong on this

So, I'll start by saying "correct" is somewhat in the eye of the beholder. There are a lot of different ways to do things depending on your goals and what you're comfortable with. Right now, you're re-doing a fair bit of stuff that you don't need to.
Personally, I tend to favor the DRY principle where possible (DRY: don't repeat yourself), so I would probably structure my code a little differently than you did.
For example, if you know that all your Books routes need to be authenticated, then you can just do something like:
// all routes on the '/books' path have to be logged in with a JWT
router.use('/books', passport.authenticate('jwt', { session: false}));
After that, req.user will be populated with the user based on however you configured your JWT strategy, and you know that any of your Books routes will be authenticated, so you can skip all of the getToken stuff you're doing.
Finally, you only showed parameterless routes, but if you end up having some routes that need parameters (e.g. if you want to PUT a specific book to update it), you'll gain a lot of reusability with router.param usage. For a simple but complete example (also switching to ES2015 syntax for more brevity):
const jwt = require('jsonwebtoken');
const router = express.Router();
const User = require("../models/user");
const Book = require("../models/book");
const router = express.Router();
router.use('/books', passport.authenticate('jwt', { session: false }));
router.param('bookId', (req, res, next, id) => {
Book.findById(id, (e, book) => {
if (e) return next(e);
// Simplified for 'not found', here req.book will just be null. you can make custom error if you want
req.book = book;
return next();
});
});
router.route('/books/:bookId')
.get((req, res) => {
if (req.book) return res.json(book);
return res.status(404).json({ msg: 'Book not found' });
});
// similarly you can add delete and put methods here if you want, and not have to req-query for the book.
router.route('/books')
.post((req, res) => {
const { isbn, title, author, publisher } = req.body
const newBook = new Book({ isbn, title, author, publisher });
newBook.save((err) => {
if (err) return next(err); // note, custom errors can be handy here
// this returns HTTP 201, which is a success, and the newBook which includes its ID
return res.status(201).json(newBook);
});
}).get((req, res) => {
Book.find((err, books) => {
if (err) return next(err);
res.json(books);
});
});
module.exports = router;

Yes, this is the correct way.
GET, POST, PUT, DELETE and PATCH are CRUD principles that you should follow with the API in your application. For example, you should use GET to get content and POST when you want to add something to the database. However, these are methods for the API and not the database interactions. Those depend on the database API that you are using.

Related

Switching Profile routes based on user role in registration (Express.js)

A bit of a long read
I am currently building the backend for a MERN project, with quite an interesting structure (I would be changing the specifics because it is a private project).
Database: There are 4 database schemas at the moment, 1 user schema with 3 different roles: student, teacher, and sponsor.
const mongoose = require('mongoose')
const UserSchema = new mongoose.Schema({
username : {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
role: {
type: String,
enum: ['student', 'teacher', 'sponsor'],
required: true,
},
dateCreated: {
type: Date,
default: Date.now
}
})
module.exports = User = mongoose.model('user', UserSchema)
**The 3 types of user roles have their own unique but quite similar profile schema (TeacherProfile, StudentProfile e.t.c) which all reference the user shcema by ID **.
const mongoose = require('mongoose');
const studentProfileSchema = new mongoose.Schema({
user: {
// create a reference to the user schema
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},.........
I have an authentication middleware that takes care of the jwt logic.
Now things get interesting at the routes
I have a user route that takes care of user registration
An auth route for login and authentication,
And 3 routes for the profiles
What I desire to build is a middleware logic that would switch between the 3 project routes once a user registers, so he/she would be returned the profile that desribes choosen role during registration.
keep in mind that there are calls to the database, which i have to wrap inside of an async block
This is an example of one of such routes:
const express = require('express');
const router = express.Router();
const config = require('config');
// validator
const {check, validationResult} = require('express-validator');
// auth middleware
const auth = require('../../middleware/authMiddleware');
// db collections
const SponsorProfile = require('../../models/SponsorProfile');
const User = require('../../models/User');
// #route GET api/SponsorProfile/me
// #desc GET current user profile
// #access Private
router.get('/me', auth, async (req, res) => {
try {
// fetch profile object
const sponsorProfile = await SponsorProfile.findOne({user:req.user.id});
// check if profile exists
if(!sponsorProfile) {
return res.status(400).json({msg: 'Hello Sponsor, You have not created a profile'})
}
res.json(sponsorProfile)
} catch (error) {
console.error(error.message);
res.status(500).json({msg:'This is our fault not yours'})
}
})
module.exports= router;
Now this is what I tried:
I built a master router that uses all the profile routers as sub-routers starting from the student to the sponsor.
const express = require('express');
const profilesRouter = express.Router();
profilesRouter.use('/', require('./studentProfile'));
profilesRouter.use('/', require('./teacherProfile'));
profilesRouter.use('/', require('./sponsorProfile'));
module.exports = profilesRouter;
It is then called in server.js like this:
app.use('/api/profilesRouter', require('./routes/api/profilesRouter'));
The appraoch was to place a middleware function in the first 2 routers and leave the third one empty so there will be a switch, if the criterias in the first two passes.
async function shouldRouterChange(req, res, next) {
let userRole = await User.findOne({user:req.role}).select('-password');
console.log(userRole)
if ( userRole === 'mentor') {
return next('router');
}
return next();
}
function shouldRouterChange(req, res, next) {
if (req.user.role === 'teacher') {
return next('router');
}
return next();
}
// #route GET api/studentProfile/me
// #desc GET current user profile
// #access Private
router.get('/me', [auth, shouldRouterChange], async (req, res) => {
try {
// check if profile exists
const studentProfile = await StudentProfile.findOne({user:req.user.id}).populate('user', ['role']);
if(!studentProfile) {
return res.status(400).json({msg: 'Hello student, You have not created a profile'})
}
res.json(studentProfile)
} catch (error) {
console.error(error.message);
res.status(500).json({msg:'This is our fault not yours'})
}
})
module.exports= router;
Obviously that did not work
Then I tried the middleware function like this
async function shouldRouterChange(req, res, next) {
let userRole = await User.findOne({user:req.role}).select('-password');
console.log(userRole)
if ( userRole === 'mentor') {
return next('router');
}
return next();
}
No Way
Then this:
async function shouldRouterChange(req, res, next) {
try {
let userRole = await studentProfile.findOne({user:req.user.id}).populate('user', ['role']);
// conditional
console.log(userRole)
if (userRole.role==='mentor') {
return next('router')
}
return next()
} catch (error) {
console.error(error.message)
res.status(500).json({msg: 'Server Error'})
}
}
I debugged as best as I could and realized that:
The whole switching structure actually works nicely
The problem lies in the middleware structure
The first middleware structure might be corerct, apart from the conditional.
The conditional equates to null, or undefined (it does not get the user role properly).
The whole middleware logic might have to be called inside the router.get() logic that returns the profile.
My question is how can i make the conditional correct, and consequently structure the middleware to work properly (maybe without doing too much change on my app structure, won't mind anyways)
I have solved the error, and it works like a charm
To use router level middleware for switching routers (can also switch single routes)
Build a master router for all those routers (now sub-routers):
const express = require('express');
const profilesRouter = express.Router();
profilesRouter.use('/', require('./studentProfile'));
profilesRouter.use('/', require('./teacherProfile'));
profilesRouter.use('/', require('./sponsorProfile'));
module.exports = profilesRouter;
Add the master router path to server.js:
app.use('/api/profileRouter', require('./routes/api/profileRouter'));
Build the middleware logic, to switch the routers conditionally:
Middleware to switch from student to teacher router file:
const jwt = require('jsonwebtoken');
const config = require('config');
// Check user profile
function teacherSwitch(req, res, next) {
const token = req.header('x-auth-token');
// if no token is returned
if (!token) {
return res.status(401).json({ msg: 'No token, permission denied' });
}
try {
// decode token
const decoded = jwt.verify(token, config.get('jwtSecret'));
// set role to the same value as in the request
req.user.role = decoded.user.role;
// check if role is teacher
if (decoded.user.role !== 'student') {
return next('router');
}
return next();
} catch (error) {
res.status(401).json({ msg: 'Wrong token, authentication failed' });
}
}
module.exports = teacherSwitch;
Our logic here is to switch based on the role chosen by the user when registering. This means that role is a DB field in the user schema.
All we have to do is to add the role in our authentication payload.
A token is created on user registration, and that token uses the user id as payload, so if we add the role to that payload, we can now access it in the "req" object and build our conditional around it.
This payload is located in my auth route using the auth middleware:
// create jwt payload
const payload = await {
user: {
id: user.id,
role: user.role,
},
};
// if user login is successful, return token
jwt.sign(payload, config.get('jwtSecret'),
{ expiresIn: 36000 }, (error, token) => {
if (error) throw error;
return res.json({ token });
});
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
Also, the "next(router)" in the middleware function is the magic that switches to the next router in the router stack found in profilesRouter.
It initiates a switch which is then called when we finally return next.
return next().
Call the teacherSwitch middleware in the first sub-router (student router)
router.get('/me', [auth, teacherSwitch], async (req, res) => {
try {
// check if profile exists
const studentProfile = await StudentProfile.findOne({ user: req.user.id });
if (!teacherProfile) {
return res.status(400).json({ msg: 'Hello student, You have not created a profile' });
}
return res.json(studentProfile);
} catch (error) {
console.error(error.message);
return res.status(500).json({ msg: 'This is our fault not yours' });
}
});
Create another middleware to switch to the 3rd sub-router conditionally
function sponsorSwitch(req, res, next) {
const token = req.header('x-auth-token');
// if no token is returned
if (!token) {
return res.status(401).json({ msg: 'No token, permission denied' });
}
try {
// decode token
const decoded = jwt.verify(token, config.get('jwtSecret'));
// set role to the same value as in the request
req.user.role = decoded.user.role;
// check if role is partner
if (decoded.user.role !== 'teacher') {
return next('router');
}
return next();
} catch (error) {
res.status(401).json({ msg: 'Wrong token, authentication failed' });
}
}
6 Call it in the second sub-router.
router.get('/me', [auth, partnerSwitch], async (req, res) => ...
And that is it, as long as the condition is not met, it will switch and switch until the condition is passed.
Works just like a regular switch statement.
What I was missing here was that to be able to use the role to switch, I had to call it in as part of the auth payload, this way it becomes part of the request object in the req, res cycle, and hence can be manipulated as you like.
We never need to call another middleware in the last router, because logically the switching only gets there when everything fails, just like the default in a regular switch statement.
N.B: It is actually important to call the middleware function in the last router, as this would make this work perfectly in the frontend (I used react), not doing this caused bug where react could not return the last route if it had to, which is not what we want.
Got some very valuable help from John
An used this site to manually decode my JWT tokens, to know exactly what was going on.

How to create a secret string with JWT_KEY in Node.JS app

I'm developing a Node.JS & MongoDB app inserting articles and categories, with a signup and login users system. I need to add/fix a secret string on order to make Jsonwebtoken (JWT_KEY) work properly.
My authentication or authorization fails when I try to add an article with details (title, attached picture ect.) threw Postman, probably because I made a mistake installing or using the jsonwebtoken library. It maybe a mistake in the nodemon.json file that is should be hidden at the end (user, password, JWT_KEY), but maybe in another part of my code.
The Postman process connects with the article.js routes file, that seems to be fine. The relevant part is the createArticle POST, since the rest work fine so far:
const express = require('express');
const router = express.Router();
const upload = require('../middlewares/upload');
const checkAuth = require('../middlewares/checkAuth');
const {
getAllArticles,
createArticle,
getArticle,
updateArticle,
deleteArticle
} = require('../controllers/articles');
router.get('/', getAllArticles);
router.get('/:articleId', getArticle);
router.post('/', checkAuth, upload.single('image'), createArticle);
router.patch('/:articleId', checkAuth, updateArticle);
router.delete('/:articleId', checkAuth, deleteArticle);
module.exports = router;
Here is the authChek.js middleware that is responsible of the authorization process:
const jwt = require('jsonwebtoken');
const checkAuth = (req, res, next) => {
try {
const token = req.headers.authorization.split('')[1];
jwt.verify(token, process.env.JWT_KEY);
next();
} catch(error) {
res.status(401).json({
message: 'Auth failed'
})
}
}
module.exports = checkAuth;
The verify seems ok and should work fine connecting to nodemon. If it's all fine, Postman should return back a message that the authorization succeeded - but it returns failed auth. Here, in the article.js controller, the POST method seems fine to and should not catch an error of 500, 401 or 409:
const mongoose = require('mongoose');
const Article = require('../models/article');
const Category = require('../models/category');
module.exports = {
createArticle: (req, res) => {
const { path: image } = req.file;
const { title, description, content, categoryId } = req.body;
Category.findById(categoryId).then((category) => {
if (!category) {
return res.status(404).json({
message: 'Category not found'
})
}
const article = new Article({
_id: new mongoose.Types.ObjectId(),
title,
description,
content,
categoryId,
image: image.replace('\\','/')
});
return article.save();
}).then(() => {
res.status(200).json({
message: 'Created article'
})
}).catch(error => {
res.status(500).json({
error
})
});
}
}
Another file using the JWT_KEY is the users.js controller, in the login part. Look at the area of the if & result. It may fail to connect properly to the .env part of the nodemon.json file. See here "process.env.JWT_KEY":
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
module.exports = {
login: (req, res) => {
const { email, password } = req.body;
User.find({ email }).then((users) => {
if (users.length === 0) {
return res.status(401).json ({
message: 'Authentication failed'
});
}
const [ user ] = users;
bcrypt.compare(password, user.password, (error, result) => {
if (error) {
return res.status(401).json({
message: 'Authentication failed'
});
}
if (result) {
const token = jwt.sign({
id: user._id,
email: user.email,
},
process.env.JWT_KEY,
{
expiresIn: "1H"
});
return res.status(200).json({
message: 'Authentication successful',
token
})
}
res.status(401).json({
message: 'Authentication failed'
});
})
})
}
}
Is there something to fix here? Or how can I check if my JWT_KEY in nodemon.json is written properly or wrong? If the string is generated by a library or taken from somewhere else, I don't know where to search for it in my app or around the web.

NodeJS, getting username of logged in user within route

I am getting in to NodeJS and have followed some video tutorials for making stuff, to understand NodeJS and Express. It turned more in to copying as little were explained, so tried to make my own thing using what I learned and so on.
Making a simple login function with PassportJS, ExpressJs and Mongoose.
The login and stuff works, and I can get the username of the currently logged in user and display it if I define it within the main app.js using this:
app.get("/stuff", (req,res) => {
res.render("stuff.html", {username:req.user.username});
});
Now if I want to make nice and structured by using router, I cannot get it to work. It throws error saying username is undefined, making page unable to render. The router itself works if I don't pass any variable or use variables I know will work (e.g. var x = "Hello"; res.render … {msg:x});).
Part of the app.js that handle routes:
var stuff = require("./routes/stuff");
app.use("/stuff", stuff);
module.exports.app;
I've tried to cont x = require("…") basically everything that is in the app.js in this stuff.js file, but to no avail, so removed everything but express + routes to get fresh start.
How do I pass the username that is working in app.js in to the routed file? Preferably make it automatically do to every page if possible, using app.get("*")… or something.
Entire stuff.js:
/* Routes */
const express = require("express");
const router = express.Router();
/* Stuff */
router.get("/", function(req, res, next) {
res.render("stuff.html", {username:req.user.username});
console.log(req.user.username);
next();
});
/* Bottom */
module.exports = router;
Login section of app.js:
app.post('/login',
passport.authenticate('local',
{
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: 'Wrong login'
}
), function(req,res) {
console.log("Hello " + req.user.username);
});
passport.serializeUser(function(user,done) {
done(null, user.id);
});
passport.deserializeUser(function(id,done) {
User.getUserById(id, function(err, user) {
done(err,user);
});
});
passport.use(new LocalStrategy(function(username,password,callback) {
User.getUserByUsername(username, function(err,user) {
if(err) throw err;
if(!user) {
return callback(null, false, {msg: "shit"});
}
User.comparePassword(password, user.password, function(err,isMatch) {
if(err) return callback(err);
if(isMatch) {
return callback(null, user);
} else {
return callback(null, false, {msg:"Something"});
}
});
});
}));
The users.js file for handling registering new users, if that's relevant:
const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/users");
const db = mongoose.connection;
mongoose.Promise = global.Promise;
const bcrypt = require("bcryptjs");
/* Data schema */
const userSchema = mongoose.Schema({
name: {
type: String
},
username: {
type: String,
index: true
},
password: {
type: String
},
email: {
type: String
}
});
var User = module.exports = mongoose.model("User", userSchema);
module.exports.createUser = function(newUser, callback) {
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(newUser.password, salt, function(err, hash) {
newUser.password = hash;
newUser.save(callback);
});
});
}
module.exports.getUserById = function(id, callback) {
User.findById(id, callback);
}
module.exports.getUserByUsername = function(username, callback) {
var query = {username: username};
User.findOne(query, callback);
}
module.exports.comparePassword = function(testPw, hash, callback) {
bcrypt.compare(testPw, hash, function(err,isMatch) {
callback(null,isMatch);
});
}
As far as I understand you are trying to pass your username to the preferably every file including your router file. What I do for this to use middleware in app.js to pass every page. Or you can simply implement passport implementation in the other page as well which could be useless i guess.
app.use(function(req,res,next){
res.locals.currentUser=req.user
next()
}
Then, you can use use your currentUser in every page when you try to render.
I encountered the same issue, probably after following the same tutorials...
I found that the function you need in the app.js is:
app.get('*', function(req, res,next){
res.locals.user = req.user || null;
next();
})
it should be in the app.js already. Now, in all of the other page you should be able to use req.user.username

Call multiple modules in route Express, Nodejs

This is the first time I create an API. I've tried to delete the user items once, the user is removed. I was able to delete the user but i didn't succeed to delete the items.
User.js
express = require('express');
User = require('./user');
Item = require('../item');
router = express.Router();
User.findByIdAndRemove(req.params.id, function(err, user) {
if (err) {
return res.status(500).send('User not found by id.');
}
Item.deleteMany(user._id, function(err, item) {
if (err) {
return res.status(500).send('Item is not found');
}
return res.status(200).send(user, item);
});
});
Is there a way to achieve this? because I have a feeling that I'm doing it the wrong way.
Thanks!
It looks like you are not defining the actual routes -- you need
router.route('/').post(function(req, res){ ... });
You should also include the body parser to get the parameters out of the request
var bodyParser = require('body-parser');
app.use(bodyParser.json());
var parseUrlencoded = bodyParser.urlencoded({extended: false});
The code you have for User methods will look more like the below block. You can change '/' to the URL path you would rather have the api respond to and can change the code from being in .post to .delete depending on what method you want to respond to
route.route('/')
.post(parseUrlencoded, function(req, res) {
// code to respond to 'post' methods
if (!req.params.id) {
return res.send('id not sent')
}
User.findByIdAndRemove(req.params.id, function(err, user) {
if (err) {
return res.status(500).send('User not found by id.');
}
Item.deleteMany(user._id, function(err, item) {
if (err) {
return res.status(500).send('Item is not found');
}
return res.status(200).send(user, item);
});
});
})
.delete(parseUrlencoded, function(req, res) {
// code to respond to 'delete' method
})

Testing Express / Passport middleware using Jasmine — passport.authenticate never completes

I'm trying to unit test a simple piece of Express middleware, a cascading athenticator that checks first for a JWT token using a passport-jwt-strategy, and then if that fails, using a passport-openid-strategy. Each of the strategies is already well tested so what I am trying to test is their integration.
The module I am testing looks like this:
"use strict";
let passport = require('passport');
let Strategies = require('./strategies');
let setupDone = false;
// set up passport
let setup = function (app) {
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (obj, done) {
done(null, obj);
});
passport.use('jwt', Strategies.jwt);
passport.use('openid', Strategies.openId);
app.use(passport.initialize());
app.use(passport.session());
setupDone = true;
};
let authenticate = function (req, res, next) {
if (!setupDone) throw new Error('You must have run setup(app) before you can use the middleware');
console.log(' cascadingAuthentication');
// first try the token option
passport.authenticate('jwt', function (jwterr, user, info) {
console.log(' jwt auth', jwterr, user, info);
if (jwterr || !user) {
passport.authenticate('openid', function (oautherr, user, info) {
if (oautherr || !user) {
return next(oautherr);
} else {
next();
}
});
} else {
req.user = user;
next();
}
});
};
module.exports = {
setup: setup,
authenticate: authenticate
}
My Jasmine test looks like this
"use strict";
let CascadingAuthentication = require('../../lib/middleware/cascadingAuthentication');
let TokenUtils = require('../support/tokenUtils');
let email = 'testing#test.tes';
describe('cascadingAuthentication', function () {
describe('when there is a token in the header', function () {
let req;
let res = {};
let app = {
use: function (used) { console.log('app.use called with', typeof used); }
};
beforeEach(function (done) {
let token = TokenUtils.makeJWT(email);
req = {
app: app,
header: {
Authorization: `Bearer ${token}`
}
}
CascadingAuthentication.setup(app);
CascadingAuthentication.authenticate(req, res, function () {
done();
});
});
it('populates req.user', function () {
expect(req.user).toEqual(jasmine.any(Object));
});
});
});
The issue I have is that, when I run the test, I see the first console.log(' cascadingAuthentication') but I never see the second console.log('jwt auth', err, user, info). The code just dies inside passport.authenticate without ever calling the callback, without raising an error, or without providing any kind of feedback at all.
I'm running my tests via gulp using Jasmine.
My questions are: in order,
Can you see anything obvious that I have done that I might have just missed?
Is there anything else I ought to mock out in my req, res, or app that might make this test work?
Is there any way to debug this interactively; stepping through the code under test as it runs, rather than just adding console.log statements (which seems a little 1980s to me).
Digging through passport's source I have worked out there were two problems with my code.
The first is that passport.authenticate returns a middleware function, it doesn't actually execute that function. So the solution was simply to call the returned function.
So my authenticate method now looks like:
let authenticate = function(req, res, next) {
if (!setupDone) throw new Error('You must have run setup(app) before you can use the middleware');
// first try the token option
passport.authenticate('jwt', function(jwterr, user, info) {
if (jwterr || !user) {
passport.authenticate('openid', function(autherr, user, info) {
if (autherr || !user) {
return next(autherr);
} else {
next();
}
})(req, res, next);
} else {
req.user = user;
next();
}
})(req, res, next);
};
(The above example is trimmed for use in the question)
The other issue was in my test I used header instead of headers in my mock req object, and also authorization ought to have had a lower case a.
With those two fixes the test now passes.
I fiddled with this for quite some time and eventually landed on the following setup (to test passport.authenticate('local', () => {})).
auth-router.js
const express = require('express');
const passport = require('passport');
const login = (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
next(err);
return;
}
if (!user) {
const error = new Error(info.message);
error.status = 404;
next(error);
return;
}
// Add the found user record to the request to
// allow other middlewares to access it.
req.user = user;
next();
})(req, res, next);
};
const router = express.Router();
router.post('/auth/login', login);
module.exports = {
login,
router
};
auth-router.spec.js
const passport = require('passport');
describe('login', () => {
it('should login and add the user to the request object', (done) => {
spyOn(passport, 'authenticate').and.callFake((strategy, callback) => {
const err = null;
const user = {};
const info = {};
callback(err, user, info);
return (req, res, next) => {};
});
const auth = require('./auth'); // my middleware function
const req = { body: {} };
const res = {};
const next = () => {
expect(req.user).toBeDefined();
done();
};
auth.login(req, res, next);
});
});

Resources