I am developing an app, where I invite other people. So I just wanted to skip passport auth if the url pattern matches. Here is code:
const routes = (app) => {
app.all('/*', (req, res, next) => {
if ((req.path === '/api/v2/auth/sign_up') || (req.path === '/api/v2/members/invite*'))
next();
else if (req.path === '/api/v2/auth/sign_in') {
passport.authenticate('local', { session: false }, (err, user, info) => {
if (!user || err || info != undefined) {
return res.status(500).send({ message: "Something went wrong, Please try again!" });
}
req.user = user;
next();
})(req, res, next);
} else {
passport.authenticate('jwt', { session: false }, (err, user, info) => {
if (!user || err || info != undefined) {
return res.status(500).send({ message: "Something went wrong, Please try again!" });
}
req.user = user;
next();
})(req, res, next);
}
app.use('/api/v2/members/invite',
require("./memberInviteRoute"));
app.use('/api/v2/members',
authorize('admin', 'company'),
require("./memberRoute"));
});
}
module.exports = routes;
Related
So I'm trying to fetch some data from my backend and I get this error. It's wierd because until a few days ago it worked just fine.
Failed to load resource: the server responded with a status of 403 (Forbidden)
Also this is my backend code:
verifyToken.js
const jwt = require('jsonwebtoken');
const verifyToken = (req, res, next) => {
const authHeader = req.headers.token;
if (authHeader) {
const token = authHeader.split(' ')[1];
jwt.verify(token, process.env.JWT_SEC, (err, user) => {
if (err) res.status(403).json('Token is not valid!');
req.user = user;
next();
});
} else {
return res.status(401).json('You are not authenticated!');
}
};
const verifyTokenAndAuthorization = (req, res, next) => {
verifyToken(req, res, () => {
if (req.user.id === req.params.id || req.user.isAdmin) {
next();
} else {
res.status(403).json('You are not alowed to do that!');
}
});
};
const verifyTokenAndAdmin = (req, res, next) => {
verifyToken(req, res, () => {
if (req.user.isAdmin) {
next();
} else {
res.status(403).json('You are not alowed to do that!');
}
});
};
module.exports = {
verifyToken,
verifyTokenAndAuthorization,
verifyTokenAndAdmin,
};
What should I do to fix it?
Here I have a POST method that gets called quite often.
app.post('/return_data', async (req, res) => {
var response, file
console.log("request recieved: " + req.body.room)
await readFile('Data/'+req.body.room+'.json').then((data) => {
console.log(data)
return res.send(data)
}).catch((e) => {
console.log(e)
})
})
For some reason the response is intermittent. Sometimes it will log the following error.
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:535:11)
at ServerResponse.header (/home/ec2-user/dc-floorplans/node_modules/express/lib/response.js:771:10)
at ServerResponse.send (/home/ec2-user/dc-floorplans/node_modules/express/lib/response.js:194:10)
at /home/ec2-user/dc-floorplans/app.js:127:13
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async /home/ec2-user/dc-floorplans/app.js:125:5 {
code: 'ERR_HTTP_HEADERS_SENT'
}
When I first start my web app, usually the first POST logs an error. After refreshing the page, the data comes through as intended. There is only one response in the function and so I'm puzzled as to why this error is occurring.
I know that readFile works because the data is logged in .then(). How can it be that sometimes data is sent to the client and other times it throws an error when the exact same process is happening?
Thanks in advance.
UPDATE -- FULL APP.JS:
app.set('view-engine', 'ejs');
app.use(express.urlencoded({ extended: false }))
app.use(cors())
app.use(flash())
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
app.use(methodOverride('_method'))
app.use(express.static(__dirname + '/static'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', checkAuthenticated, (req, res) => {
res.render('index.ejs');
});
app.get('/login', checkNotAuthenticated, function(req, res, next) {
res.render('login.ejs');
});
app.post('/login', checkNotAuthenticated, (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if(err) throw err
if(!user) return res.redirect('/login')
req.logIn(user, (err) => {
if(err) return next(err)
if(info.message==process.env.DEFAULTPW) {
console.log("Default password detected for: " + user.email)
return res.redirect('/')
}
return res.redirect('/')
})
})(req, res, next)
})
app.get('/update', checkAuthenticated, (req, res, next) => {
res.render('update.ejs')
})
app.post('/update', checkAuthenticated, async (req, res, next) => {
if(req.body.pw1 != req.body.pw2) { res.redirect('/update') }
if(req.body.pw2 == process.env.DEFAULTPW) { res.redirect('/update') }
const newpw = await encrypt(req.body.pw2)
var queryresult
await con.query("UPDATE login SET password = \'"+newpw+"\' WHERE email = \'"+req.body.email.toLowerCase()+"\'", (err, result, fields) => {
if (err) {
console.log(err)
return res.redirect('/update')
};
console.log(result)
queryresult = result
if(result.changedRows == 0) {
return res.redirect('/update')
}
})
return res.redirect('/')
});
app.post('/get_new_data', async (req, res) => {
console.log("New Data:")
await pollLM(req.body.room).then((data) => {
console.log(data)
return res.send(data)
}).catch((e) => {
console.log(e)
})
})
async function pollLM(room) {
return new Promise((resolve, reject) => {
const getData = pySpawn('python3.7', ['getData.py', room])
getData.stdout.on('data', (data) => {
resolve(data)
})
})
}
app.post('/return_data', async (req, res) => {
console.log("request recieved: " + req.body.room)
await readFile('Data/'+req.body.room+'.json').then((data) => {
console.log(data)
return res.send(data)
}).catch((e) => {
console.log(e)
})
})
async function readFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) reject(err);
console.log(typeof(data))
resolve(data)
})
})
}
app.post('/update_reload_time', (req, res) => {
console.log("reload time updated to: " + req.body.time)
fs.writeFile('Data/delay.json', JSON.stringify(req.body), () => {
console.log("delay updated to: " + req.body.time)
})
})
app.get('/reload_time', async (req, res) => {
await readFile('Data/delay.json').then((data) => {
console.log(data)
return res.send(data)
}).catch((e) => {
console.log(e)
})
})
app.delete('/logout', (req, res) => {
req.logOut()
res.redirect('/login')
})
// Page routing...
function checkAuthenticated(req, res, next) {
if(req.isAuthenticated()) {
return next()
}
res.redirect('/login')
}
function checkNotAuthenticated(req, res, next) {
if(req.isAuthenticated()) {
return res.redirect('/')
}
next()
}
function encrypt(text) {
const key = crypto.scryptSync(process.env.CRYPTOPW, process.env.SALT, 24)
const cipher = crypto.createCipher('aes-192-cbc', key)
let encrypted = ''
cipher.on('readable', () => {
let chunk
while(null != (chunk = cipher.read())) {
encrypted += chunk.toString('hex')
}
})
cipher.write(text)
cipher.end()
return encrypted
}
function decrypt(text) {
const key = crypto.scryptSync(process.env.CRYPTOPW, process.env.SALT, 24)
const decipher = crypto.createDecipher('aes-192-cbc', key)
let decrypted = '';
decipher.on('readable', () => {
while(null !== (chunk = decipher.read())) {
decrypted += chunk.toString('utf8')
}
})
const encrypted = text
decipher.write(encrypted, 'hex')
decipher.end()
return decrypted
}
All you have to look for is where you response twice on the same call.
For example:
app.post('test', (req,res)=>{
res.send('1')
res.json('{1:2}')
})
will always return this error, because you are responding twice.
You have to investigate your routes one by one, I would start here:
app.post('/update', checkAuthenticated, async (req, res, next) => {
if(req.body.pw1 != req.body.pw2) { res.redirect('/update') }
if(req.body.pw2 == process.env.DEFAULTPW) { res.redirect('/update') }
const newpw = await encrypt(req.body.pw2)
var queryresult
await con.query("UPDATE login SET password = \'"+newpw+"\' WHERE email = \'"+req.body.email.toLowerCase()+"\'", (err, result, fields) => {
if (err) {
console.log(err)
return res.redirect('/update')
};
console.log(result)
queryresult = result
if(result.changedRows == 0) {
return res.redirect('/update')
}
})
return res.redirect('/')
});
You are calling res.redirect explicitly and on condition. Have you checked if both conditions meet simultaneously? I guess here is one weak point.
And I would check this one
app.post('/login', checkNotAuthenticated, (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if(err) throw err
if(!user) return res.redirect('/login')
req.logIn(user, (err) => {
if(err) return next(err)
if(info.message==process.env.DEFAULTPW) {
console.log("Default password detected for: " + user.email)
return res.redirect('/')
}
return res.redirect('/')
})
})(req, res, next)
})
Again there potentially meet two conditions. Check them out. Other parts look fine to me.
I am creating a middleware for admin users:
let admin_middleware = (req, res, next) => {
let token = req.header('x-auth');
User.findByToken(token).then((user) => {
if(!user || user._doc.user_type !== user_roles.admin)
return Promise.reject();
req.user = user;
req.token = token;
next();
}).catch((e) => {
res.status(401).send();
})
};
The question is around this line of code:
if(!user || user._doc.user_type !== user_roles.admin)
return Promise.reject();
When using the admin_middleware in server.js I catch the rejected promise :
.catch(e => {
res.status(401).send();
})
which means that for both: user not authorized and `access forbidden I return the 401 status code.
I would like to return 403 in one case, and 401 in another. What would be the solution?
Thank you!
As suggested in comments. You can reject with custom error like this:
class UnauthorizedError extends Error {}
class ForbiddenError extends Error {}
let admin_middleware = (req, res, next) => {
let token = req.header('x-auth');
User.findByToken(token).then((user) => {
if (!user) return Promise.reject(new UnauthorizedError());
if (user._doc.user_type !== user_roles.admin) return Promise.reject(new ForbiddenError());
req.user = user;
req.token = token;
next();
}).catch((e) => {
if (e instanceof UnauthorizedError) {
res.status(401);
} else if (e instanceof ForbiddenError) {
res.status(403);
} else {
res.status(500);
}
res.send();
})
};
I have a Node.js Express API that doesn't have any security and now I'm looking to patch on JWT-based authorization.
I have it working (using the code below) for one method. Is there an easy way to apply this to all the methods? e.g., maybe using app.all? Or do I have to add the code to every method?
app.get('/people/:id', ensureToken, (req, res, next) => {
var id = req.params.id;
getPerson(id, (err, people) => {
if (err) {
next(err);
return;
}
jwt.verify(req.token, process.env.JWT_KEY, function(err, data) {
if (err) {
res.sendStatus(403);
} else {
res
.status(200)
.set('Content-Type', 'application/json')
.set("Connection", "close")
.send(person);
}
});
});
});
function ensureToken(req, res, next) {
const bearerHeader = req.headers["authorization"];
if (typeof bearerHeader !== 'undefined') {
const bearer = bearerHeader.split(" ");
const bearerToken = bearer[1];
req.token = bearerToken;
next();
} else {
res.sendStatus(403);
}
}
You can do as:
app.use(function(req, res, next){
const bearerHeader = req.headers["authorization"];
if (typeof bearerHeader !== 'undefined') {
const bearer = bearerHeader.split(" ");
const bearerToken = bearer[1];
req.token = bearerToken;
next();
} else {
res.sendStatus(403);
}
});
I'd recommend checking out passport-jwt
You can use this as middleware as well:
app.get('/people/:id', passport.authenticate('jwt', { session: false }), (req, res, next) => { });
The advantage to this over using app.use is that you can specify which routes should be authenticated. i.e., you can exclude a login or registration route. without having to hack in checks like if (req.path == '/login')The other advantage is that using passport you can later add additional authentication methods, should you choose. There's also quite a bit of community support.
I implemented it with app.all, excluding the login route from the check:
app.all(process.env.API_BASE + "*", (req, res, next) => {
if (req.path.includes(process.env.API_BASE + "login")) return next();
return auth.authenticate((err, user, info) => {
if (err) { return next(err); }
if (!user) {
if (info.name === "TokenExpiredError") {
return res.status(401).json({ message: "Your token has expired. Please generate a new one" });
} else {
return res.status(401).json({ message: info.message });
}
}
app.set("user", user);
return next();
})(req, res, next);
});
The full solution here: https://jonathas.com/token-based-authentication-in-nodejs-with-passport-jwt-and-bcrypt/
login api:
api.post('/login', function(req, res) {
User.findOne({
username: req.body.username
}).select('password').exec(function(err, user) {
if(err) throw err;
if(!user) {
res.send({ message: "User doesn't exist."});
} else if(user) {
var validPassword = user.comparePassword(req.body.password);
if(!validPassword) {
res.send({ message: "Invalid password."});
} else {
/////// token
var token = createToken(user);
res.json({
success: true,
message: "Successfully logged in.",
token: token
});
}
}
});
});
middleware:
api.use(function(req, res, next) {
console.log("somebody just came to our app.");
var token = req.body.token || req.param('token') || req.headers['x-access-token'];
// check if token exists
if(token) {
jsonwebtoken.verify(token, secretKey, function(err, decoded) {
if(err) {
res.status(403).send({ success: false, message: "failed to authenticate user."});
} else {
req.decoded = decoded;
next();
}
});
} else {
res.status(403).send({ success: false, message: "no token provided."});
}
});
authService:
authFactory.login = function(username, password) {
return $http.post('/api/login', {
username: username,
password: password
})
.success(function(data) {
AuthToken.setToken(data.token);
return data;
})
}
authFactory.isLoggedIn = function() {
if(AuthToken.getToken())
return true;
else
return false;
}
Now, if my user is logged in and he tries to access: localhost:3000/login , then he should be redirected to localhost:3000/
only after he logs out, he should be able to access the login page (similar to facebook).
How to do this?
1
In login API store user info in respond object(res).
2
app.get('/',function(req,res){
if(req.user){
res.redirect('/login');
} else {
res.render('home');
}
});
RECOMMENDED:PASSPORT and its Local-Strategy.
exports.loginPage = (req, res) => {
if(req.session.user){
res.redirect("/");
} else {
res.render("loginPage");
}
};
On your login route, add the logic inside the middleware function. You could the same on the registration page as well.
exports.registrationPage = (req, res) => {
if(req.session.user){
res.redirect("/");
} else {
res.render("registrationPage");
}
};
The key takeaway is this: if you want a user not to see a page on your site, go to the page's route, add the if/else logic.
Hope that helps, cheers!