How do I make it so that if a response has been sent back, then no more responses should be sent? Actually, the issue is that if a response is sent back, then express (or nodejs) shouldn't continue running through the rest of the code.
I've tried doing next() but terminal throws the error of next() being undefined. res.end() doesn't seem to work either?
routing.js:
router.post('/user/create', function(req, res, next) {
user.createUser(req, res);
});
user.js createUser
user.prototype.createUser = function(req, res, next) {
var body = req.body;
checkAllInput(body, res, next);
// do some more checks then finally create user
}
user.js createUser
function checkAllInput(body, res, next) {
checkError.checkUsername(body, res, next);
checkError.checkPassword(body, res, next);
}
checkError.js
userClass.prototype.checkUsername = function(username, res) {
if (!username || username === "bob) {
res.status(406).send("username");
}
}
userClass.prototype.checkPassword = function(password, res) {
if (!password || password === "hello") {
res.status(406).send("password");
}
}
Call createUser in routing, which then calls checkAllInput which calls checkUsername but should stop if username sends a response back.
You need to return, so the code stops there. Otherwise it will keep on going.
userClass.prototype.checkUsername = function(username, res) {
if (!username || username === "bob) {
return res.status(406).send("username");
}
}
userClass.prototype.checkPassword = function(password, res) {
if (!password || password === "hello") {
return res.status(406).send("password");
}
}
next() isn't inherent in the code, it has to be defined somewhere, and even if it is, it still doesn't stop the code as it is asynchronous.
I'm assuming you're using Express. You might want to do this with middleware.
//middleWare.js
exports.checkUserModel = function (req, res, next) {
var body = req.body,
username = body.username,
password = body.password,
if (!username || username === "bob) {
return res.status(406).send("username required");
}
if (!password || password === "hello") {
return res.status(406).send("password required");
}
next();
}
//router.js
var middleWare = require('./middleWare');
var user = require('./controllers/users');
app.post('/user/createUser', middleWare.checkUserModel, user.create);
You want to use the express middleware like so :
checkUsername = function(req, res, next) {
if (checkUserNameIsValid) {
//check the password
next()
}
else{
///otherwise return
res.status(406).send("username");
}
}
checkPassword = function(req, res, next) {
if (checkIfPasswordIsValid) {
//create the user when password is valid too
next();
}
else {
//else return response
res.status(406).send("password required");
}
}
createUserIfPasswordAndUserNameIsOk = function(req, res, next){
//create the user here
}
Add in sequence you want to handle the request.
router.post('/user/create', checkUserName, checkPassword, createUserIfPasswordAndUserNameIsOk );
So what will happen the express router will first call checkUserName and if you don't call next() then it returns. So if you call next() in it it will call the next method in for the current requested resource which is checkPassword. And so on.
Take a look at Having a hard time trying to understand 'next/next()' in express.js thread.
Note that you don't have to use return. Also take a look at #Brian answer
Related
I have an auth protect middleware that checks if req.params.id === req.userId(the one returned by bcrypt verify function). I have a protect function which upon bcrypt.verify returns the decoded.id.
The Id returned from req.user._id despite being the same as decoded.id returns "not authorized in the verifyandAuth middleware, however, if I replace req.user._id by decoded.id(in verifyandAuth), the if function works and the middleware goes through without giving the "not authorized error". Can anybody please tell me why that's happening? (req.user._id and decoded.id upon console.log show the same id, as such, there's no mistake there).
Protect Middleware
export const protect = async (req, res, next) => {
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
)
try {
{
token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token, "kris");
req.userId = decoded.id;
req.user = await User.findById(decoded.id).select("-password");
next();
}
} catch (error) {
res.status(400).json(error.message);
}
if (!token) {
return res.status(400).json("Invalid Token");
}
};
auth Middleware
export const verifyandAuth = (req, res, next) => {
protect(req, res, () => {
console.log(req.user._id, req.params.id);
if (req.user._id === req.params.id || req.isAdmin) {
next();
} else {
res.status(400).json("Not authorised");
}
});
};
I am trying to set up viewpages to show the authenticated user's info such as user's name or email on the page when they are logged in.
To do so, I am using the res.locals function to set the user data at the global level for the pages to access.
const jwt = require("jsonwebtoken")
const User = require("../models/User")
const checkUser = (req, res, next) => {
const token = req.cookies.jwt
if (token) {
jwt.verify(token, "Company_Special_Code", async (err, decodedToken) => {
if (err) {
res.locals.user = null // Set it to null if the user does not exist
next();
} else {
let user = await User.findById(decodedToken.id)
res.locals.user = user
next();
}
})
} else {
res.locals.user = null
next();
}
}
module.exports = {
checkUser
}
The first code, where I call next() function every time the code reaches an endpoint, allows the pages to access the user info without any errors.
However, if I call the next() function only once at the very bottom of the checkUser() function, it causes error claiming that the user is not defined at the view page level. The code is as follows:
const jwt = require("jsonwebtoken")
const User = require("../models/User")
const checkUser = (req, res, next) => {
const token = req.cookies.jwt
if (token) {
jwt.verify(token, "Company_Special_Code", async (err, decodedToken) => {
if (err) {
res.locals.user = null // Set it to null if the user does not exist
} else {
let user = await User.findById(decodedToken.id)
res.locals.user = user
}
})
} else {
res.locals.user = null
}
next();
}
module.exports = {
checkUser
}
If I coded the function correctly, the checkUser() function should get to the next() function at the bottom regardless of the status of jwt token or if there was an error during the token verification process. I would really appreciate your help if you can tell me what I am getting it wrong here...
Your jwt.verify is has an asynchronous callback and next() at the bottom is being called before that returns. So you either need to put next() into that callback, or use jsonwebtoken synchronously. Something like this:
const checkUser = (req, res, next) => {
const token = req.cookies.jwt
if (token) {
try {
const decodedToken = jwt.verify(token, "Company_Special_Code")
// This only runs if the token was decoded successfully
let user = await User.findById(decodedToken.id)
res.locals.user = user
} catch (error) {
res.locals.user = null // Set it to null if the user does not exist
}
} else {
res.locals.user = null
}
next();
}
When you use an async callback like that, javascript will continue processing the rest of the script while that callback is running on the side (more or less). So next() is being called unaware of the need to wait for the callback or anything it might handle.
Your validation part misses the correct error handling in middleware. If token is invalid, then why should user get access to controller, you can send error from middleware itself. If you are not sending error from middleware and calling next(), then will defeat purpose of your authentication middleware.
Update your code as follows,
const jwt = require("jsonwebtoken")
const User = require("../models/User")
// The routes, which does not requires aurthentication
const usecuredRoutes = []
const checkUser = (req, res, next) => {
if(usecuredRoutes.indexOf(req.path) === -1){
const token = req.cookies.jwt
if (token) {
jwt.verify(token, "Company_Special_Code", async (err, decodedToken) => {
if (err) {
res.locals.user = null // Set it to null if the user does not exist
// Token is invalid, then telll user that he is don't have access to the resource
res.status(403).send('Unauthorized')
} else {
let user = await User.findById(decodedToken.id)
res.locals.user = user
next();
}
})
} else {
// Token does not exists, then telll user that he is don't have access to the resource
res.status(403).send('Unauthorized')
}
} else {
next()
}
}
module.exports = {
checkUser
}
I am facing weird issue. I believe its very simple to experienced people.
Making local web server on Node.js+Express+NeDB
Client on login makes POST request to /login form and is redirected on successful login.
{"id":1,"created":1568146217901,"username":"sprice","name":"Steve Price","email":"email#gmail.com","password":"Password","level":"1","_id":"3JDE7p6tAl1vD11k"}
Login post
// POST services
app.post('/login', (req, res) => {
const loginData = req.body;
db.users.findOne({ username: loginData.uname }, function (err, doc) {
req.session.userId = doc.id;
if(doc.id === 1) console.log("True"); // < Output: True
console.log(req.session.userId); // < Output: 1
});
req.session.userId = 1; // < Just for test
res.redirect('/plan');
});
Before going into /plan page there is an auth check:
const redirectLogin = (req, res, next) => {
const { userId } = req.session;
console.log(userId); // < Output: undefined
if (!userId) {
res.render('pages/index');
} else {
next()
}
}
So my weird question, if i assign value from DB which is integer and to test that i have if statement i get req.session.userId = 1, but when client is redirected its req.session.userId = undefined.
But if i assign value manually with that test line it works and server issues cookie and i can visit my site with no problems...
Am i doing something wrong?
You are missing the fact that the callback to .findOne() is asynchronous and non-blocking. That means that your request handler returns first and THEN, sometime later (after the res.redirect() has already been sent), the callback gets called. So, the callback is too late for the redirect. Instead, you need to do the res.redirect() inside the callback:
// POST services
app.post('/login', (req, res) => {
const loginData = req.body;
db.users.findOne({ username: loginData.uname }, function (err, doc) {
if (err) {
console.log(err);
res.sendStatus(500);
return;
}
req.session.userId = doc.id;
if(doc.id === 1) console.log("True"); // < Output: True
console.log(req.session.userId); // < Output: 1
res.redirect('/plan');
});
});
Depending upon what session store you're using, you may also have to save any changes to your session before you redirect.
I am following a middleware chaining example from this question.
I have a route app.put('/users/:id', isAuthenticated, (req, res) => {db.updateUser(req.params.id, req.body)}. I am trying to write a middleware function that verifies that the ID provided in the URL matches the ID retrieved from the JWT included with the request.
I already have a function isAuthenticated that verifies the JWT and sets res.locals.userId to the UID retrieved; so I would like to simply make use of that in this new function canModifyTarget but for some reason the request hangs forever:
// This function works fine
isAuthenticated: function(req, res, next) {
let token;
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
token = req.headers.authorization.split(' ')[1];
admin.auth().verifyIdToken(token).then((decodedToken) => {
res.locals.userId = decodedToken.uid;
return next();
}).catch((error) => {
return res.status(HttpStatus.UNAUTHORIZED).send();
})
}
}
// Switching out isAuthenticated for this in the route causes a permanent hang
canModifyTarget: function(req, res, next) {
console.log('This is printed');
return (req, res, next) => {
console.log('This is NOT printed');
isAuthenticated(req, res, () => {
if (req.params.id === res.locals.userId) {
return next();
}
return res.status(HttpStatus.FORBIDDEN).send();
})
}
}
middlewares should be callback functions that call "next()" once finished.
Your first function, when executed, is calling next() (eventually, after your promise is resolved)
Your second function isn't calling next(), it is just returning a function definition.
Define it like this
canModifyTarget: function(req, res, next) {
isAuthenticated(req, res, () => {
if (req.params.id === res.locals.userId) {
return next();
}
return res.status(HttpStatus.FORBIDDEN).send();
})
}
}
and if the third parameter of isAuthenticated is a callback, it should work
Also, you should define an "else" case in your isAuthenticated function, otherwise it will hang as well (maybe throw an exception or something?)
If you need to reference them, store them in variables rather than directly defining them in your module.exports:
const isAuthenticated = function(req, res, next) {
// code here
}
const canModifyTarget: function(req, res, next) {
// code here
}
module.exports = {
isAuthenticated,
canModifyTarget,
};
I think simpler is to define canModifyTarget as one more middleware. I.e:
function canModifyTarget(req, res, next) {
console.log('This is NOT printed');
if (req.params.id === res.locals.userId) {
return next();
}
return res.status(HttpStatus.FORBIDDEN).send();
}
and then just apply it after isAuthenticated middleware:
app.put(
'/users/:id',
isAuthenticated,
canModifyTarget,
(req, res) => {db.updateUser(req.params.id, req.body)}
);
Hope it helps.
I am just writing a solution where I needed to unify two kind of auth middlewares: password-based and apikey-based into one middleware: unifiedOrgAuth middleware.
So, basically this would enable me to just put unifiedOrgAuth middleware on those routes which need either the password-based or apikey-based auth.
The key thing was to pass the next function from the umbrella middleware to the underlying middleware by just calling the underlying middleware with the next function of the umbrella middleware:
unified auth middleware:
function unifiedOrgAuthMiddleware(
path: string,
perm: Permission
): express.RequestHandler {
return async (req: RequestWithOrg, _res: Response, next: NextFunction) => {
const cookies = req.cookies;
if (cookies && cookies.Authorization) {
(userAuthMiddleware(path, perm))(req, _res, next);
return;
}
const apiKey = req.header('X-API-KEY');
if (apiKey && apiKey.length > 0) {
(apiAuthMiddleware(path, perm))(req, _res, next);
return;
}
return next(new Error401Exception());
// Make linter happy.
};
}
Here are the underlying middlewares:
password-based auth middleware:
function userAuthMiddleware(
path: string,
perm: Permission
): express.RequestHandler {
return async (req, _res, next) => {
try {
const cookies = req.cookies;
if (!(cookies && cookies.Authorization)) {
next(new Error401Exception());
// Make linter happy.
return;
}
if (!validCookies(cookies)) {
next(new Error401Exception());
// Make linter happy.
return;
}
} catch (error) {
next(new Error401Exception());
// Make linter happy.
return;
}
next();
};
}
api-based auth middleware:
function apiAuthMiddleware(
path: string,
perm: Permission
): express.RequestHandler {
return async (req: RequestWithOrg, _res: Response, next: NextFunction) => {
const apiKey = req.header('X-API-KEY');
if (!apiKey) {
next(new Error401Exception());
// Make linter happy.
return;
}
if (!validApiKey(apiKey)) {
next(new Error401Exception());
// Make linter happy.
return;
}
next();
};
}
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);
});
});