ExpressJS Handle multiple get route with the same endpoint - node.js

I build an application API with expressJS. In this application there is a public part and an private part.
Both part need to display a list of user. But only the private part can display the full list.
I use express-authorize middlewar to handle autorization. So i have defined two authorization :
'public:users:list'
and
'private:users:list'
In express i have tried to write routes like this :
router.route('/users')
.get([passport.authenticate('jwt', { session: false }), authorization.authorizer.isPermitted('private:users:list')], (req, res) => {
getUserList(req, res);
})
.get([passport.authenticate('jwt', { session: false }), authorization.authorizer.isPermitted('public:users:list')], (req, res) => {
req.query.filter.roles.name = 'user'
getUserList(req, res);
});
But this does not work. Request fail as unauthorized on the first get and never goes to the second. Is there a mean to make this works? Or any others techniques. If possible avoid to create another endpoint like (router.route('usersPublic').
Thx
EDIT:
I have override the onDenied function like this and make the change in the route.
var authorizer = new authorizer.Authorizer(authorizer.options);
authorizer.options.onDenied = function (req, res, next) { next('route') };
authorizer.options.withSubject = function (req, res, done) {}
That seems to work. But if no route are valid. i get a 404 error instead of 403. Any suggestion?
EDIT2:
onDenied should not be set at global but inside the route itself like this
router.route('/users')..get([passport.authenticate('jwt', { session: false }), authorization.authorizer.onDenied((req, res, next) => next('route')).isPermitted('private:users:list')], (req, res) => {
getUserList(req, res);
});
router.route('/users')..get([passport.authenticate('jwt', { session: false }), authorization.authorizer.isPermitted('public:users:list')], (req, res) => {
req.query.filter.roles.name = 'user'
getUserList(req, res);
});

I'm not familiar with express-authorize, so the following is pieced together from its source and may not work.
First, you need to change how authentication mismatches are handled. The default is to redirect to /login, but you need to change that so the request gets passed to the next route chain:
let myAuthorizer = authorization.authorizer.onDenied((req, res, next) => next('route'));
Then you need to set up two separate routes for /users:
router.route('/users').get([passport.authenticate('jwt', { session: false }), myAuthorizer.isPermitted('private:users:list') ], (req, res) => {
getUserList(req, res);
})
router.route('/users').get([passport.authenticate('jwt', { session: false }), authorization.authorizer.isPermitted('public:users:list')], (req, res) => {
req.query.filter.roles.name = 'user'
getUserList(req, res);
});
The next('route') is explained here, but it basically means that instead of passing the request to the next handler in the current chain, it's passed to the next full route chain (which in this case is the second router.route('/users') if private access is denied).

Related

node js express - middleware next() is not triggering consecutive fm in mocha test cases

I have configured a middleware (router level) which checks for admin access, and if successful I am logging the information and calling next().
middleware:
const validateAdmin = function (scope) {
return function (req, res, next) {
if (req.admin) {
//log the information and using scope to log
next();
} else {
// send the response with 403
}
};
};
router level usage
router.use('/rule/:name', validateAdmin({ serviceScope: this.#serviceScope }), async (req, res) => {
await this._handleRequest(req, res);
});
working case
when i trigger the req from postman, i can see that middleware is called and after next getting executed, the control is coming to 'await this._handleRequest(req, res);' which is expected.
issue
Test case
it.only('shall get single rule', async function () {
const getSingleReq = {
method: 'GET',
admin: true,
originalUrl: '<some req>/rule/rule_1',
baseUrl: '<some_req>',
query: {},
params: { name: 'rule_1' },
headers: {
//token info
},
body: {}
};
const next = sinon.spy();
await router.use.args[1][1](getSingleReq, res, next);
}));
when await router.use.args[1][1](getSingleReq, res, next); is called, I can see the middleware is triggered but next() is not calling the subsequent middleware. I expect that await this._handleRequest(req, res); is called just like the scenario when triggered from postman.
I hope the issue is clear.
Referred many use cases but everywhere the scenario of just checking if next() is called is only done .

How can I respond the request from an authorization middleware in a Next.js api route?

Let's say I have the following Next.js api route.
/api/protected-api
This api will get a authorization: "Bearer: TOKEN" header for authorization purposes.
import { NextApiHandler } from "next";
const apiHandler: NextApiHandler = async (req, res) => {
await runAuthMiddleware(req,res);
// THE REST OF THE API LOGIC SHOULD ONLY RUN IF AUTHORIZATION SUCCEEDS
// IN THEORY, runAuthMiddleware WOULD RESPOND WITH 403 IF AUTHORIZATION FAILS
return res.json(data);
};
What I mean by the code above is:
If authorization fails, I would like to respond a 403 from the runAuthMiddleware function, and don't even bother running the rest of the code in the apiHandler.
Is this even possible? Is this an anti-pattern?
Should I got with something like this instead?
const apiHandler: NextApiHandler = async (req, res) => {
const authSuccess = await runAuthMiddleware(req,res);
if (authSuccess)
return res.json(data);
else
return res.status(403).send("Forbidden");
};
UPDATE:
It seems that there's no easy way of doing it. This package offers a possible solution: next-api-middleware
There is an easy way to do it if that is the only thing that you need, just make your own higher order function which checks auth:
const withAuth = (f) => async (req, res) => {
const isLogged = await checkIfUserIsLogged(req)
if (isLogged) {
return f(req, res)
} else {
return res.status(403).send("Forbidden");
}
}
const apiHandler = withAuth(async (req, res) => {
// This code now only runs of the user is logged
return res.json(data);
});
For more complex scenarios I would recommend to use https://github.com/hoangvvo/next-connect or something like that. But if you only need one middleware then it is completely fine to write your own.

Is it possible to use multiple .get on app.route?

app.route('/users')
.post(user.post)
.get(user.get)
.get(user.everyone)
.put(user.update)
.delete(user.delete);
I have ran into the problem of my function using two res.send, so I am getting the 'Error: cannot set header after they are sent.' error, to fix this I have turned it into two functions which I am trying to use two .get on the app.route, but it seems I can only use one as when I use two the second one doesn't work.
Is there a way I could use two .get on one app.route?
If not, what are my options to get around this problem?
You need to create separate routes for each api endpoint like this:
app.route('/users').get(req, res) => {
//Get all users
});
app.route('/users:/id').get(req, res) => {
//Get specific user
});
app.route('/users').post(req, res) => {
//Create new user
});
app.route('/users/:id').put(req, res) => {
//Update user
});
app.route('/users/:id').delete(req, res) => {
//Delete user
});
Once res.send is called, means your server has sent the response to the browser or whatever. you can't change the already sent response and its header.
You can use multiple callbacks on one route and one method(post,get)
An array of callback functions can handle a route. For example:
var cb0 = function (req, res, next) {
console.log('CB0')
next()
}
var cb1 = function (req, res, next) {
console.log('CB1')
next()
}
var cb2 = function (req, res) {
res.send('Hello from C!')
}
app.get('/example/c', [cb0, cb1, cb2])
yes, you can use multiple HTTP requests either its .get or .post but with different params. or routes.

Will emitting request and response handler in Express.js cause problem?

I have a doubt in my design pattern on my Express app,
so i wrap my controller in try and catch, and the catch method is emitting (req, res) handler from controller and later will be handled by a function that send response back to the client.
the code is more or less like this :
const errorExceptionHandler = fn => (req, res, next) => {
fn(req, res, next).catch((err) => {
emitter.emit('onControllerError', {
err: err,
req: req,
res: res,
next: next
})
})
}
the code above emtting req, res, and next, the default parameters that express provided.
emitter.on('onControllerError', params => {
const err = params.err
const req = params.req
const res = params.res
const next = params.next
if (!res.headerSent) {
res.send({
status: 500,
url: process.env.DEBUG ? req.url : undefined,
message: process.env.DEBUG ? err.message : "Something went wrong!"
})
}
})
and above is how the 'onControllerError' event is handled, my concern is, will this cause trouble later if the traffic goes up? or will it send a wrong response to the client?
Increased traffic wouldn't matter here as each request is still handled independently, plus all the necessary data is being passed directly to the event handler.
So no, based on your code I can't think of any reason why it would start to fail.

modify response in express/nodejs

i am making multiple streamdata in Nodejs using express.
this is how i make a url:
app.get('/temp/1', function(req, res){
res.send('hello, i am not modified')
})
my question is: is it possible to modify the response of that url?
i tried like this:
app.get(/modify/1, function(req, res){
app.get('/temp/1', function(req, res){
res.send('hello, i am modified')
})
res.send('done');
}
So i would think that the response is changed, but nothing happens actually.
is there a way to achieve it?
Here's an example using express-modify-response:
const modifyResponse = require('express-modify-response');
...
let modify = modifyResponse(
function(req, res) { return true }, // always modify the response
function(req, res, body) { return 'hello, i am modified' } // the new response
);
app.get('/temp/1', modify, function(req, res){
res.send('hello, i am not modified')
})
EDIT: second attempt. You have an endpoint /temp/1 that sends a particular response, and you want an endpoint /modify/1 that will take that response and modify it.
This calls for some abstraction:
function someFunction(id) {
return 'hello, i am not modified';
}
app.get('/temp/1', function(req, res) {
res.send(someFunction(1));
});
app.get('/modify/1', function(req, res) {
let value = someFunction(1);
// Remove the word `not`.
value = value.replace(/not /, '');
res.send(value);
});
So both handlers use the same function, that provides the actual output, but /modify/1 modifies the output before returning it to the client.

Resources