How to mock Express JWT unless function? - node.js

I'm using Express and Express-JWT.
In a Express() instance I use:
const api = express()
api.use(jwt({
// my options
}))
To mock this in tests, I use a mocks\express-jwt\index.js file containing:
const jwt = jest.fn().mockImplementation(options => (req, res, next) => {
// set dummy req.user and call
next()
})
module exports = jwt
This all works fine.
Now I want to skip JWT for the root endpoint, so I changed the jwt usage to:
api.use(jwt({
// my options
}).unless({path:['/']}))
In my mock file I added:
jwt.unless = jest.fn().mockImplementation(options => (req, res, next) => {
next()
})
However, the tests now always fail with function unless is not defined.
Anyone an idea how to mock this unless behaviour?

unless is being used as a property on the result of calling jwt.
So to mock it, add your unless mock as a property of the function returned by your jwt mock:
const jwt = jest.fn().mockImplementation(options => {
const func = (req, res, next) => {
// set dummy req.user and call
next();
};
func.unless = jest.fn().mockImplementation(options => (req, res, next) => {
next();
});
return func;
});
module.exports = jwt;

Suggested answer by Brian did not work for me, because in the func method I do some stuff for faking an authorization check.
My problem was I needed to do skip the authorization check for the method+path given in by the unless function.
My solution now is like this:
const jet = jest.fn(options => {
let mockFunc = (req, res, next) => {
// get authorization from request
let token = ...
if (!token) {
res.status(401).send('No token provided')
} else {
req.token = token
next()
}
}
mockFunc.unless = jest.fn(args => (req, res, next) => {
if (args.method == req.method && arg.path == req.path) {
// not do authorization check for excluded endpoint via unless
next()
else {
mockFunc(req, res, next)
}
}
return mockFunc
}
Thanks to Brian for pointing me in the right direction.

Related

Express - using application-level middleware conditionally on route-level

I'm trying to understand how to use an application-level middleware (or at least usually used like this) like cookie-parser on route-level and conditionally.
I tried something like:
const myMiddleware = (req, res, next) => {
if (myCondition) {
return cookieParser();
} else {
next();
}
}
app.use('/admin', myMiddleware, (req, res) => {
res.sendStatus(401)
})
But it's not working, the request will be just stuck.
Is this possible?
Traditional cookie-parser implementation:
app.use(cookieParser())
cookieParser() returns a middleware function, i.e. a function that takes in req, res, next as arguments. You just have to pass it the arguments:
const cookieParserMiddleware = cookieParser();
const myMiddleware = (req, res, next) => {
if (myCondition) {
return cookieParserMiddleware(req, res, next);
}
next();
};
app.use("/admin", myMiddleware, (req, res) => {
res.sendStatus(401);
});
Notice that I'm creating the cookieParser middleware outside myMiddleware - technically we could also just do return cookieParser()(req, res, next) but recreating the same middleware again and again on every request would be wasteful.
I've also removed the else since the if block returns from the function (guard clause).

How to use csurf within a custom middleware?

I have managed to get csurf working in my express app as a regular middleware. However, I'd like to add it to my custom authentication middleware to both avoid having to include csurf in every route and also to avoid forgetting to use it. How should I call csurf within a custom middleware?
For example, say I have this middleware using express-session to limit access to logged-in users:
export const auth = async (req, res, next) => {
const { uid } = req.session;
try {
const user = await User.query().findById(uid);
req.session.role = user.role;
next();
} catch {
throw new PrivateRouteError();
}
};
This answer has a way of doing this but I was unable to implement it. Here's what I tried:
export const auth = async (req, res, next) => {
const csrf = csurf({ cookie: true, ignoreMethods: [] });
csrf(req, res, async () => {
const { uid } = req.session;
try {
const user = await User.query().findById(uid);
req.session.role = user.role;
next();
} catch {
throw new PrivateRouteError();
}
});
};
However, the result is that csurf does not block access for a missing CSRF token and the PrivateRouteError is not caught and crashed the app (if the user has not authenticated, if they are it works fine).
Is there a neat way to bundle csurf into my middleware or should I just manually add it to all the routes that use the auth middleware?
Okay, I was clearly overthinking this last night. It's enough to get rid of the next() call and put csurf stuff after the catch block.
export const auth = async (req, res, next) => {
const { uid } = req.session;
try {
const user = await User.query().findById(uid);
req.session.role = user.role;
} catch {
throw new PrivateRouteError();
}
const csrf = csurf({ cookie: true, ignoreMethods: [] });
csrf(req, res, next);
};

NodeJS + Route with custom ACL

i am newbie in nodeJS. i have an issue with route.
SiteRoutes.js
module.exports = (app, express) => {
const router = express.Router();
const Globals = require("../../configs/Globals");
const SiteController = require("../controllers/SiteController") ;
router.post('/changeStateSite', Globals.isAdminAuthorised, (req, res, next) => {
const siteObj = new SiteController().boot(req, res);
return siteObj.statusSite();
});
app.use(config.baseApiUrl, router);
}
Globle.js
static async isAdminAuthorised(req, res, next) {
// checking the role. currently only admin role consider so we have set a static role as a admin.
}
right now Globals.isAdminAuthorised is used for the ACL, but i need to pass the custom user role in that function like Globals.isAdminAuthorised(['admin','customer']) so how can i do that?
As when i pass the same its throw the error as below:
please help me on the same.
you can make that middleware a function that returns the handler function with (req, res, next) signature, so basically it will take that array as a parameter.
// e.g roles is that array that you can pass to do authorization logic based on that
static async isAdminAuthorised(roles) {
return function (req, res, next) {
// check if the roles are included in that array for example or whatever...
}
}
then you can just execute that function inside your router there like how you wrote up there
router.post('/changeStateSite', Globals.isAdminAuthorised(['admin', 'customer']), (req, res, next) => {...}

Passport.js - Is it possible to pass a parameter into the router authenticated function?

I would like to pass a certain permission into the authenticated call on routes in Passport.js.
This is what I have now:
app.get('/mypage', app.authenticated, function (req, res, next) {
if (!req.user.hasPermission('myPermission')) {
return res.redirect('/unauthorized');
}
// do stuff
};
var middleware = function(app) {
app.authenticated = function (req, res, next) {
if (req.isAuthenticated()) {
return next();
}
if (req.method == 'GET') {
req.session.returnTo = req.originalUrl;
}
res.redirect('/login');
};
}
module.exports = middleware;
I would instead like to pass the permission into authenticated like this:
app.get('/mypage', app.authenticated('myPermission'), function (req, res, next) {
// do stuff
};
But as far as I can tell, since authenticated gets the parameters it needs automatically, I can't just add a new one.
How can I go about doing this?
You can access req.body values in any of the express middleware.
In your app.authenticated(..) middleware, prior to execution set the value :
req.body['permission'] = 'myPermission'
Use the value of req.body['permission'] for authorisation.

NodeJS - Express bypass basicAuth

I'm setting up dynamic routes and using basicAuth (when a user/pass has been configured). Here's what I have:
var basicAuth = express.basicAuth,
auth = function(req, res, next) {
if (config.hasOwnProperty(req.params.project)) {
var auth = config[req.params.project].auth;
if (auth) {
basicAuth(function(user, pass, callback) {
// Check credentials
callback(null, user === auth.user && pass === auth.pass);
})(req, res, next);
} else {
// No authentication
return true;
}
}
};
Then, my route looks like this:
app.get("/:project", auth, function (req, res) {
...
});
It's getting the config from a file which either contains the auth object with auth.user and auth.pass or is set to false. When set to false I'd like to (obviously) skip authentication.
The basicAuth is working when turned on, but I can't figure out how to dynamically bypass it.
Connect doesn't check the return value of the middleware, so returning true doesn't mean anything. You need to call the next function so that Connect knows to continue.
var basicAuth = express.basicAuth,
auth = function(req, res, next) {
if (config.hasOwnProperty(req.params.project)) {
var auth = config[req.params.project].auth;
if (auth) {
basicAuth(function(user, pass, callback) {
// Check credentials
callback(null, user === auth.user && pass === auth.pass);
})(req, res, next);
} else {
// No authentication
next();
}
}
};
Also, it looks like the basicAuth callback can be synchronous, so it's probably cleaner to do this:
basicAuth(function(user, pass) {
// Check credentials
return user === auth.user && pass === auth.pass;
})(req, res, next);
Finally, basicAuth has another alternate form, so you can just do:
basicAuth(auth.user, auth.pass)(req, res, next);

Resources