Im creating and employee leave managment system.
Everything works fine until when I try to update the leave status by admin and th logged in acc or user is not roled as admin.
Ive a middleware that checks the authentication of user and role of user
when an employeee tries to access this route it crashes the server logging cannot set headers already set to client and then ive to restart the server and access the route again
here is my Auth code:
const jwt = require("jsonwebtoken");
const employeeModels = require("../models/employeeModels");
exports.isAuthenticated = async (req, res, next) => {
try {
const { token } = req.cookies;
if (!token)
return res
.status(400)
.json({ success: false, error: "Please Login First" });
const decodedData = jwt.verify(token, "njuebuipoqdjbcibwjcnowdopq");
req.employee = await employeeModels.findById(decodedData.id);
next();
} catch (e) {
res.status(500).json({
success: false,
error: e.message,
});
next();
}
};
exports.AuthorizeRoles = (...role) => {
return (req, res, next) => {
if (!role.includes(req.employee.role)) {
res.status(400).json({
success: false,
error: "Only Admin is Allowed to Access this route",
});
}
next();
};
};
here is my leave controller
exports.createLeave = async (req, res, next) => {
try {
const { reason, noOfDays } = req.body;
const leave = await (
await LeaveModel.create({
reason,
noOfDays,
employee: req.employee._id,
})
).populate({
path: "employee",
});
res.status(200).json({
success: true,
message: "leave Has Been Submitted Successfully",
leave,
});
} catch (e) {
res.status(400).json({
success: false,
error: e.message,
});
next();
}
};
//get All Leaves For Admin
exports.getAllLeaves = async (req, res, next) => {
try {
const leaves = await LeaveModel.find().populate("employee");
if (leaves)
return res.status(200).json({
success: true,
leaves,
});
} catch (e) {
res.status(401).json({
success: false,
error: e.message,
});
next();
}
};
exports.updateLeaveStatus = async (req, res, next) => {
try {
const leave = await LeaveModel.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});
if (leave)
return res.status(200).json({
success: true,
message: "Leave Status Updated Successfully",
leave,
});
} catch (e) {
return res.status(401).json({
success: false,
error: e.message,
});
next();
}
};
exports.deleteLeave = async (req, res) => {
try {
const leave = await LeaveModel.findByIdAndDelete(req.params.id);
if (leave)
return res.status(200).json({
success: true,
message: "Leave Deleted Successfully",
});
} catch (e) {
res.status(401).json({
success: false,
error: e.message,
});
next();
}
};
//controller for getting single leave detail
exports.getSingleLeave = async (req, res) => {
try {
//getting Leave From The Leave Model By Passing Id Fetched From The Req Url Param
const leave = await LeaveModel.findById(req.params.id).populate("employee");
if (leave)
return res.status(200).json({
success: true,
leave,
});
} catch (e) {
res.status(401).json({
success: false,
error: e.message,
});
next();
}
};
here are my leave routes
const {
createLeave,
getAllLeaves,
updateLeaveStatus,
deleteLeave,
getSingleLeave,
} = require("../controllers/leaveController");
const {
isAuthenticated,
AuthorizeRoles,
} = require("../middleware/Authentication");
const Leaverouter = express.Router();
Leaverouter.post("/createleave", isAuthenticated, createLeave)
.get(
"/admin/getallleaves",
isAuthenticated,
AuthorizeRoles("admin"),
getAllLeaves
)
.patch(
"/admin/updateleave/:id",
isAuthenticated,
AuthorizeRoles("admin"),
updateLeaveStatus
)
.delete(
"/admin/deleteleave/:id",
isAuthenticated,
AuthorizeRoles("admin"),
deleteLeave
)
.get(
"/admin/leavedetails/:id",
isAuthenticated,
AuthorizeRoles("admin"),
getSingleLeave
);
module.exports = Leaverouter;
Do not call next() after sending a response (you have multiple places where you are doing this). You ONLY call next() when you want routing to continue so some other route handler can send a response. If you've already sent a response and then you call next() and some other route handler (or the default 404 route handler) gets called and tries to then send a second response, you will get this error.
The particular error you are getting is caused when your code is attempting to send more than one response to the same request.
Related
I am learning how to implement user authentication using passport.js. I have a basic passport "local" strategy set up on the server side and so far just a single POST route to log a user in. This all works exactly as intended when troubleshooting with insomnia but when I make the same request from the browser I get a message missing credentials. This message is coming from console.log(info) in controllers/auth.js.
I have tried including credentials in the fetch request as seen below but I must be missing something else or including them incorrectly. I have also changed the variable names from 'email' to 'username' since I read that was the default for passport.js.
From what I can tell in Chrome dev tools, the request body is formatted correctly and I am hitting the proper endpoint.
controllers/auth.js
const express = require("express");
const router = express.Router();
const passport = require("passport");
router.post("/register_login", (req, res, next) => {
passport.authenticate("local", function(err, user, info) {
console.log(info)
if (err) {
return res.status(400).json({ errors: err });
}
if (!user) {
return res.status(400).json({ errors: "No user found" });
}
req.logIn(user, function(err) {
if (err) {
return res.status(400).json({ errors: err });
}
return res.status(200).json({ success: `logged in ${user.id}` });
});
})(req, res, next);
});
module.exports = router;
passport/setup.js
const bcrypt = require('bcrypt');
const { User } = require('../models');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
// Local Strategy
passport.use(new LocalStrategy((username, password, done) => {
// Match User
User.findOne({ email: username })
.then(user => {
// Create new User
if (!user) {
return done(null, false, { message: 'No user found!!'})
// Return other user
} else {
// Match password
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Wrong password' });
}
});
}
})
.catch(err => {
return done(null, false, { message: err });
});
})
);
module.exports = passport;
client side fetch
const handleLogin = async (evt) => {
evt.preventDefault();
const response = await fetch('/auth/register_login', {
method: 'POST',
credentials: 'include',
withCredentials: true,
body: JSON.stringify({
"username": "test#email.com",
"password": "password"
})
})
return response;
};
Oof, that was a simple one...
I was reading the "Content-Type" in Chrome dev tools however I was reading the "Response Headers" thinking they were the "Request Headers". The issue is that I was sending text instead of json.
Changing the client side fetch to the snippet below resolved the issue.
client side fetch
const handleLogin = async (evt) => {
evt.preventDefault();
const response = await fetch('/auth/register_login', {
method: 'POST',
credentials: 'include',
withCredentials: true,
headers: {
'Content-Type': 'application/json' // <--add this
body: JSON.stringify({
"username": "test#email.com",
"password": "password"
})
})
return response;
};
We're trying to integrate Passport authentication with out React app, and we're using React Router.
On the first submission of correct user credentials, the server receives the post, adds a session to our database, and seems to send a response, but the client doesn't update. The username and password show up in the url as a query string. Then when we resend the credentials without removing the query string from the url, the client is able to receive the response from the server.
In other words, if we don't refresh before submitting the login info again, it works.
This is the click handler that our form utilizes:
const handleClick = () => {
return axios.post('/login', { username, password })
.then(({ data }) => {
const { message } = data;
if (message === 'success') {
const { user } = data;
setUserId(user.id);
setUser(user);
}
setAuthStatus(message);
})
.catch(err => console.error(err));
};
This is our server route that is hit on every post request:
loginRouter.post('/', (req, res, next) => {
console.log('stop');
passport.authenticate('local', (err, user, info) => {
const { invalidPassword } = info || false;
if (err) {
return next(err); // will generate a 500 error
}
if (!user) {
return res.send({ message: 'invalidUser' });
}
if (invalidPassword) {
return res.send({ message: 'invalidPassword' });
}
req.login(user, loginErr => {
if (loginErr) {
return next(loginErr);
}
return res.send({ user, message: 'success' });
});
})(req, res, next);
});
This is our Passport Local Strategy that uses Sequelize:
passport.use(new LocalStrategy(
(username, password, cb) => {
User.findOne({ where: { username } })
.then((user) => {
if (!user) {
return cb(null, false);
}
if (validPassword(password, user.hash, user.salt)) {
return cb(null, user);
}
return cb(null, false, { invalidPassword: true });
})
.catch(err => {
cb(err);
});
},
));
Having trouble debugging this... We suspect the error is on the client side and may have to do with React-Router. We are using React-Router and Passport for the first time on this project.
Any help is appreciated!
Welp... All we were missing was event as a parameter in handleClick and event.preventDefault().
When i trying to run my API in postman it is working fine and sessions are getting maintained. But when i am trying to run it from UI Part the login session is not working.
This is my Login API from where i am login into
app.post('/user/login', (req, res, next) => {
const body = req.body;
let email = body.email;
let password = body.password;
const userDetails = db.collection(userProfiles);
userDetails.findOne({email: email}, function (err, user) {
if (err) {
return next(err);
} else if (!user) {
return res.status(400).send({
status: 'error',
message: 'user does not exist'
});
} else {
if (user.password == password) {
user_object = user;
req.session.user = user;
return res.send({
user_obj: user,
status: 'success',
message: 'Successfully logged in'
});
} else {
return res.status(400).send({
status: 'error',
message: 'Wrong Password'
})
}
}
return res.send(user);
});
});
This is my session API from where i am sending user req.session.user on calling this api
app.get('/user/dashboard', function (req, res) {
if (!req.session.user) {
return res.status(400).send({
data:'need to be logged in'
});
}
return res.status(200).send({
data:req.session.user
});
});```
The below is the javascript file from where i am trying to call the user stores in req.session.user
`
async function fetchUserId(){
let cookie = document.cookie;
let res = await fetch('http://127.0.0.1:8080/user/dashboard',
{redirect: 'follow',
headers:{
"Cookie":cookie
}});
let userJson = await res.json();
console.log(res);
console.log(userJson);
//return userJson;
};
`
when i hit the login API from Postman it is maintaining session and working fine but when i do the same from UI from browser it is giving error status 400 every time.
You can do a
fetch(url, options).then(function(res) { console.log(res} )
and
app.get('/user/dashboard', function (req, res) {
console.log(req.headers)
if (!req.session.user) {
return res.status(400).send({
data:'need to be logged in'
});
}
return res.status(200).send({
data:req.session.user
});
});
To check is the cookie really there and where is the user object.
And you can check your browsers dev console to see if the cookie is updating.
I see that is fetch request you put a cookie in your header. But than at the API you are looking for a user at req.session.user. Although the cookie is in req.header["Cookie"].
If passport returns the user { status: 200 }:
passport.js
...
return done(null, rows[0], { status: 200 });
...
I want the controller 'controllerLogin.login' to be called:
routs/index.js
const express = require('express');
const passport = require('passport');
const passportConf = require('../passport');
const controllerLogin = require('../controllers/login');
...
router.route('/v1/login')
.post( function(req, res, next) {
passport.authenticate('local-login', function (err, user, context = {}) {
if (err) {
console.log(err);
}
if (context.status === 429) {
return res.status(429).send({ status: 429, success: false })
}
if (context.status === 401){
return res.status(401).send({ status: 401, success: false })
}
next();
//return;
})(req, res, next);
}, controllerLogin.login );
But I can't reach the controller 'controllerLogin.login'. What am I missing and how to execute 'controllerLogin.login'?
The below was working, but I need the upper version.
const passLogin = passport.authenticate('local-login', { session: false, failWithError: true });
router.route('/v1/login')
.post( passLogin, function(err, req, res, next) {
return res.status(401).send({ status: 401, success: false })
}, controllerLogin.login );
Edit: What works ...
router.route('/v1/login')
.post( function(req, res, next) {
passport.authenticate('local-login', { session: false, failWithError: false }, function (err, user, context = {}) {
if (err) {
console.log(err);
}
if (context.statusCode === 429) {
return res.status(429).send({ status: 429, success: false, message: { name: 'Rate Limit Error' } })
}
if (context.statusCode === 401){
return res.status(401).send({ status: 401, success: false, message: { name: 'Authentication Error' } })
}
// this works getting user information
console.log('user:');
console.log(user);
next();
})(req, res, next);
}, /*controllerLogin.login*/ (req, res) => { res.status(200).json({just: 'testing'})} );
controller/login.js
module.exports = {
login: async (req, res, next) => {
// Can't access user information via 'req.user' anymore
console.log('req.user:');
console.log(req.user);
/* .. How to access user information here? .. */
res.status(200).json({just: 'testing'})
}
}
It sounds like controllerLogin.login wants req.user, but that is not being set. So try doing that manually in the callback you pass in to passport's authenticate function.
router.route('/v1/login')
.post( function(req, res, next) {
passport.authenticate('local-login', { session: false, failWithError: false }, function (err, user, context = {}) {
if (err) {
console.log(err);
return next(err); // might want to add this line to handle errors?
}
if (context.statusCode === 429) {
return res.status(429).send({ status: 429, success: false, message: { name: 'Rate Limit Error' } })
}
if (context.statusCode === 401){
return res.status(401).send({ status: 401, success: false, message: { name: 'Authentication Error' } })
}
if(!user) {
// might want to handle this separately? user not found?
//return next('User not found');
}
// this works getting user information
console.log('user:');
console.log(user);
req.user = user;
next(); // this moves us on to controllerLogin.login
})(req, res, next);
}, controllerLogin.login);
Then in controller/login
module.exports = {
login: (req, res) => {
// remove user logging once this works, don't want to log sensitive info (!)
console.log('req.user in controller/login:')
console.log(req.user)
// user logic here
res.status(200).json({status: 200, success: true})
}
}
It's worth taking a look at passportjs docs under the "Custom Callback" section. That example doesn't pass along to another function as you are doing, but it can help to see another approach.
i am new to developing apis in node js. recently i started working on a node js application there i use jwt tokens to authentication purposes.
my jwt validation function is as bellow
var jwt = require('jsonwebtoken');
var config = require('../config.js')
var JwtValidations = {
//will validate the JTW token
JwtValidation: function(req, res, next, callback) {
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, config.secret, callback);
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
}
}
module.exports = JwtValidations;
to this function i am passing a call back function so that if the jtw token validation passed i can serve to the request. bellow is one example of adding a user to the system
// addmin a user to the database
router.post('/add', function(req, res, next) {
JwtValidations.JwtValidation(req, res, next, function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
retrunval = User.addUser(req.body);
if (retrunval === true) {
res.json({ message: "_successful", body: true });
} else {
res.json({ message: "_successful", body: false });
}
}
})
});
// addmin a user to the database
router.put('/edit', function(req, res, next) {
JwtValidations.JwtValidation(req, res, next, function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
User.UpdateUser(req.body, function(err, rows) {
if (err) {
res.json({ message: "_err", body: err });
} else {
res.json({ message: "_successful", body: rows });
}
});
}
})
});
as you can see in both of these function i am repeating same code segment
return res.json({ success: false, message: 'Failed to authenticate token.' });
how do i avoid that and call the callback function if and only if JwtValidations.JwtValidation does not consists any error
how do i avoid that and call the callback function if and only if JwtValidations.JwtValidation does not consists any error
Just handle it at a level above the callback, either in JwtValidations.JwtValidation itself or a wrapper you put around the callback.
If you were doing it in JwtValidations.JwtValidation itself, you'd do this where you call the callback:
if (token) {
// verifies secret and checks exp
jwt.verify(token, config.secret, function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
}
callback(decoded);
});
} else /* ... */
Now when you use it, you know either you'll get the callback with a successfully-decoded token, or you won't get a callback at all but an error response will have been sent for you:
router.put('/edit', function(req, res, next) {
JwtValidations.JwtValidation(req, res, next, function(decoded) {
User.UpdateUser(req.body, function(err, rows) {
if (err) {
res.json({ message: "_err", body: err });
} else {
res.json({ message: "_successful", body: rows });
}
});
})
});
The code above is using a lot of (old-style) NodeJS callbacks. That's absolutely fine, but you may find it's simpler to compose bits of code if you use promises instead. One of the useful things do is split the return path in two, one for normal resolution, one for errors (rejection).
Use the jwt authentication function as a middleware function and not as a route, plenty of examples on the express documentation.
http://expressjs.com/en/guide/using-middleware.html