Can i use route inside a controller in express js? - javascript

So I have something like this in one of my controllers:
module.exports.authToken = (req, res, next) => {
const token = req.cookies.jwt;
//console.log(token);
if (!token) {
return res.sendStatus(403);
}
try {
const data = jwt.verify(token, "secret token");
console.log(data);
req.userId = data.id;
return next();
} catch {
return res.sendStatus(403);
}
};
and it's called by a route:
router.get("/protected", authController.authToken, (req, res) => {
return res.json({ user: { id: req.userId, role: req.userRole } });
});
and I want to get a JSON response of that route in one of my other controllers. I tried some things but none of it worked.

What I would do is abstract the response out to a function for re-use:
// the function will just return the data without writing it to the response
function protectedRoute(req) {
return {user: {id: req.userId, role: req.userRole}};
}
router.get("/protected", authController.authToken, (req, res) => {
// in the actual handler you can return the response
return res.json(protectedRoute(req));
});
// make sure the middleware is still being run
router.get("/other_route", authController.authToken, (req, res) => {
// use the same function to get the response from /protected
const protectedResponse = protectedRoute(req);
// do stuff with it
});

Related

How to Fix Headers Already Sent to Client in NodeAPI with Angular

I am getting the following error on my node api, which is really just console logging the request at this point.
router.get('/booksByISBN', checkRole, async (req, res) => {
console.log(req.params)
return res.sendStatus(200);
});
node:internal/errors:484
ErrorCaptureStackTrace(err);
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I believe the issue is because of pre-flight CORS data, but no clue how to fix it.
There is one API call in my Angular 15 application, but upon inspection of the Network tab, I see two api calls are actually being made to my endpoint.
I understand this is because of the CORS options request, but I don't know how to fix it to let the API go through.
CheckRole function
var checkRole = async function CheckRoleAuth (req, res, next) {
try {
const token = req.headers.authorization.split(' ')[1];
const decodedToken = jwt.verify(token, envs.jwtSecret);
await User.findById(decodedToken.userId)
.then(foundUser => {
if (foundUser) {
if (foundUser.role != null || foundUser.role != '') {
if (foundUser.role.includes('Admin'))
{
req.userData = {
email: decodedToken.email,
id: decodedToken.id
};
next();
} else {
return res.sendStatus(401);
}
} else {
return res.sendStatus(401);
}
}
})
.catch(err => {
return res.sendStatus(401);
});
} catch (error) {
return res.sendStatus(401);
}
}
You are combining async/await with then/catch in your checkRole middlware, so probably both your checkRole middleware and your endpoint handler try to send back the response.
Refactor your checkRole middleware like this:
const checkRole = async function CheckRoleAuth(req, res, next) {
try {
const token = req.headers.authorization.split(' ')[1];
const decodedToken = jwt.verify(token, envs.jwtSecret);
const user = await User.findById(decodedToken.userId).lean();
if (!user) return res.sendStatus(401);
if (!user?.role?.includes('Admin')) return res.sendStatus(403);
req.userData = { email: decodedToken.email, id: decodedToken.id };
next();
} catch (error) {
return res.sendStatus(401);
}
};

JWT Authorization is failing for all endpoints

So I am creating a social media application.
I used JWT token for verification on all endpoints. It's giving me custom error of "You are not authorized, Error 401"
For example: Create post is not working:
This is my code for JWT
const jwt = require("jsonwebtoken")
const { createError } = require ("../utils/error.js")
const verifyToken = (req, res,next) => {
const token = req.cookies.access_token
if(!token) {
return next(createError(401,"You are not authenticated!"))
}
jwt.verify(token, process.env.JWT_SECRET, (err,user) => {
if(err) return next(createError(401,"Token is not valid!"))
req.user = user
next()
}
)
}
const verifyUser = (req, res, next) => {
verifyToken(req,res, () => {
if(req.user.id === req.params.id || req.user.isAdmin) {
next()
} else {
return next(createError(402,"You are not authorized!"))
}
})
}
const verifyAdmin = (req, res, next) => {
verifyToken(req, res, next, () => {
if (req.user.isAdmin) {
next();
} else {
return next(createError(403, "You are not authorized!"));
}
});
};
module.exports = {verifyToken, verifyUser, verifyAdmin}
This is my createPost API:
const createPost = async (req, res) => {
const newPost = new Post(req.body);
try {
const savedPost = await newPost.save();
res.status(200).json(savedPost);
} catch (err) {
res.status(500).json(err);
}
}
Now, in my routes files, I have attached these functions with every endpoints.
For example: In my post.js (route file)
//create a post
router.post("/", verifyUser, createPost);
When I try to access it, this is the result
But, when I remove this verify User function from my route file, it works okay.
I have tried to re-login (to generate new cookie) and then try to do this but its still giving me error.
What can be the reason?
P.S: my api/index.js file https://codepaste.xyz/posts/JNhIr9W6zNnN26CH9xWT
After debugging, I found out that req.params.id is undefined in posts routes.
It seems to work for user endpoints since it contains req.params.id
const verifyUser = (req, res, next) => {
verifyToken(req,res, () => {
if(req.user.id === req.params.id || req.user.isAdmin) {
next()
} else {
return next(createError(402,"You are not authorized!"))
}
})
}
So I just replaced === with || and its working. (but its not right)
if(req.user.id || req.params.id || req.user.isAdmin) {
Can anyone tell me the how can I truly apply validation here since in my posts routes i dont have user id in params

Node Js pass the role as a string to the JWT verification function

the JWT verification function accepts the req, res and next as its params. I need to pass an additional string 'Admin' so that only admin users may access this API
My jwtVerification.js code:
module.exports = async function (req, res, next) { //I need to be able to add role to this call
try {
const token = req.header("Authorization");
if (!token) return res.status(401).send('Invalid access token.');
const _token = token.substring(7, token.length);
const decoded = jwt.verify(_token, process.env.JWT_PRIVATE_KEY)
const user = await prisma.user.findFirst({ where: { id: decoded.id } });
if (!user) return res.status(401).send('Invalid access token.');
//I need to be able to read the role so that I can do the following verifications
//if(!role) next();
//else{
// if(user.role !== role || decode.role !== role) return res.status(403).send('Forbidden!')
// else next();
//}
next();
} catch (error) {
res.status(401).send(error.message);
}
};
finally, the API call itself:
//use verifyJWT('Admin') for example
router.post('/test', verifyJWT, async (req, res) => {
res.send('hi');
})
You cat use some thing like this:
module.exports = function (myParam) => {
return async function (req, res, next) {
//use myParam here
try {
const token = req.header("Authorization");
if (!token) return res.status(401).send('Invalid access token.');
const _token = token.substring(7, token.length);
const decoded = jwt.verify(_token, process.env.JWT_PRIVATE_KEY)
const user = await prisma.user.findFirst({ where: { id: decoded.id } });
if (!user) return res.status(401).send('Invalid access token.');
//I need to be able to read the role so that I can do the following verifications
//if(!role) next();
//else{
// if(user.role !== role || decode.role !== role) return res.status(403).send('Forbidden!')
//}
next();
} catch (error) {
res.status(401).send(error.message);
}
}
};
And after that use the middleware this way:
router.post('/test', verifyJWT(someParam), async (req, res) => {
res.send('hi');
})

How to create a reusable code for passport.authenticate?

I have multiple controllers and each controller has multiple methods. In each method I authenticate the user and use the user id returned from the authentication to get the data from database. I am trying to create reusable code for authentication since the code is repeated.
In the controller:
const authenticate = require('../utils/user-authenticate');
exports.getData = async (req, res, next) => {
const userId = await authenticate.user(req, res, next);
console.log(userId);
};
And in the authentication I have:
exports.user = (req, res, next) => passport.authenticate('jwt', async (error, result) => {
if (error) {
// Send response using res.status(401);
} else {
return result;
}
})(req, res, next);
The console.log(userId); prints undefined always. This is print before passport finishes. Looks like async/await does not work the way I want here.
It works if I use await authenticate.user(req, res, next).then() but isn't it possible to assign the result directly to userId variable?
If I use return next('1'): first time undefined but second time it prints 1.
wrapped into a promise:
exports.user = (req, res, next) => new Promise((resolve, reject) => {
passport.authenticate('jwt', async (error, result) => {
if (error) {
// reject(error)
// Send response using res.status(401);
} else {
resolve(result);
}
})(req, res, next);
})
but think about:
//app.use or something similar
addMiddleware(authJWT);
// later in the chain
useMiddleware((req, res, next)=>{
// test auth or end chain
if(!req.JWT_user) return;
req.customField = 'one for the chain'
// process next middleware
next()
});
Thanks #Estradiaz for the suggestion:
exports.user returns undefined ... Return is scoped within inner
callback - if you want to pass it outside wrap it into a promise
Reusable passport.authenticate:
exports.user = (req, res) => {
return new Promise(resolve => {
passport.authenticate('jwt', null, async (error, result) => {
if (error) {
email.sendError(res, error, null);
} else if (result) {
resolve(result);
} else {
return res.status(401).json({errors: responses['1']});
}
})(req, res);
});
};
And this is how I use it in my controller, for instance in a function:
exports.getData = async (req, res, next) => {
const userId = await authenticate.user(req, res);
};

express/node: Cannot set headers after they are sent to the client

I'm having some issues with my expressJS application, posting to one route will always result in Cannot set headers after they are sent to the client - I don't understand why and where I'm sending a request/response twice.
I tried playing around with async and await in my functions to get rid of this error but ultimately it's always coming back. I'm also writing an ID to a database, I thought this would be the issue. But I don't think so, because I'm basically just returning a code and not even checking the dynamodb.put request in my current function.
async function putNewUrl(inputUrl) {
const newId = await getId();
const short = ShortURL.encode(newId);
const params = {
TableName: URL_TABLE,
Item: {
long: inputUrl,
short,
},
};
try {
const data = await dynamoDb.put(params).promise();
return short;
} catch (error) {
console.log(error);
}
return short;
}
app.post('/submit', async (req, res) => {
const inputUrl = req.body.url;
try {
const shortUrl = await putNewUrl(inputUrl);
console.log(shortUrl)
return res.json({ success: true, message: shortUrl });
} catch(error) {
console.log(error)
return
}
});
here are my imports:
import { config, DynamoDB } from 'aws-sdk';
import { json } from 'body-parser';
import express from 'express';
import helmet from 'helmet';
import { URL } from 'url';
const app = express();
app.use(helmet());
this is how I start my server
app.listen(3000, () => { console.log('app running'); });
solved it:
there was another route like this:
app.post('/submit', (req, res, next) => {
const inputUrl = req.body.url;
findExistingUrl(inputUrl, (error, data) => {
if (error) {
return res.json({ success: false, message: 'server error' });
}
if (typeof data.Items[0] === 'undefined' && data.Items[0] !== null) {
next();
} else {
return res.json({ success: true});
}
});
});
where I was calling next() right at the end again.
solved it:
there was another route like this:
app.post('/submit', (req, res, next) => {
const inputUrl = req.body.url;
findExistingUrl(inputUrl, (error, data) => {
if (error) {
return res.json({ success: false, message: 'server error' });
}
if (typeof data.Items[0] === 'undefined' && data.Items[0] !== null) {
next();
} else {
return res.json({ success: true});
}
});
});
where I was calling next() right at the end again.

Categories

Resources