sails session writing bug - node.js

I'm using sails 0.10.4 and stumbled with one pretty annoying bug. When user logs in I write his data into the req.session.user then in policies I can retrieve his data such as his role, password etc. But the req.session.user becomes undefined when I go out of the login action. Do you have any ideas how to handle this? Here's the code:
api/controllers/User.js :
module.exports = {
login: function (req, res) {
Users.findOneByEmail(req.param('email'))
.exec(function (err, user) {
if ((err) || (!user)) {
res.send({
error: 'User not found'
});
return;
}
if (!passwordHash.verify(req.param('password'), user.password)) {
res.send({
error: 'Incorrect passwpord'
});
return;
}
req.session.user = user;//I write user into the session
res.send({
user: user
});
});
}
}
api/policies/isLoggedIn.js
module.exports = function (req, res, next) {
if (req.headers.authentication) {
var credentials = JSON.parse(req.headers.authentication);
if(req.session.user.login === credentials.login)//User doesn't exist in session
return next();
}
}

In a testing environment , this issue can happen when testing with Supertest and not defining an agent
var agent = request.agent(app);
agent.post('/api/login',{email:'foo#bar.com',password:'foobar})
.end(function(err,res){...; done();});
It is the correct way to work with sessions, simply using request.post would not work as it would reinit the session variable as soon as the response is sent, even if we are chaining requests inside the same test.
Learnt it the hard way, so I hope it can help some lost developper.

Related

How to call a middleware function from inside a middleware function?

Noob question here.
I am using Node.JS and Express (with JWT Auth) and am really struggling with the middleware.
Sometimes I need to know if a user is logged in, but don't need to force them to be logged in (such as authorize middleware). For this, I create a new middleware isLoggedIn. The problem here is that if I find the user is logged in, I then want to authorize them so I can use the req.auth property. I know this is not the most resource-efficient method, but was the best I could think of. Even now it doesn't work, It just skips over the auth part. I have mainly been debugging with console.log(); and still can't find the problem.
function isLoggedIn() {
return (req, res, next) => {
var clientToken
// Check if the user has token. If not return null.
if (req.headers.authorization && req.headers.authorization.split(" ")[0] === "Bearer") {
clientToken = req.headers.authorization.split(" ")[1];
} else if (req.query && req.query.token) {
clientToken = req.query.token;
} else if (req.cookies && req.cookies['session']) {
clientToken = req.cookies['session'];
} else {
clientToken = null;
}
if (!clientToken) next();
else authorize();
}
}
function authorize(roles = []) {
console.log("1");
// roles param can be a single role string (e.g. Role.User or 'User')
// or an array of roles (e.g. [Role.Admin, Role.User] or ['Admin', 'User'])
if (typeof roles === 'string') {
roles = [roles];
}
console.log("2");
return [
//console.log("3"),
// authenticate JWT token and attach user to request object (req.auth)
jwt({
secret,
algorithms: ['HS256'],
getToken: function fromHeaderOrQuerystring(req) {
console.log("4");
if (req.headers.authorization && req.headers.authorization.split(" ")[0] === "Bearer") {
console.log("why is it here?");
return req.headers.authorization.split(" ")[1];
} else if (req.query && req.query.token) {
console.log("query string?")
return req.query.token;
} else if (req.cookies && req.cookies['session']) {
console.log("5");
return req.cookies['session'];
}
console.log("null?");
return null;
}
}),
//console.log("6"),
// authorize based on user role
async(req, res, next) => {
//this is the part that doesn't run... I think...
console.log("7");
const account = await User.findOne({ id: req.auth.sub });
const refreshTokens = await refreshToken.find({ account: account.id });
if (!account || (roles.length && !roles.includes(account.role))) {
// account no longer exists or role not authorized
return res.status(401).json({ message: 'Unauthorized' });
}
// authentication and authorization successful
req.auth = account;
//req.auth.role = account.role;
req.auth.ownsToken = token => !!refreshTokens.find(x => x.token === token);
next();
}
];
}
As you can see: for example. app.get("/", isLoggedIn(), (req, res) => res.render('index')); Here I am using isLoggedIn, because they don't need to be logged to see index, but I want to be able to use req.auth if I can (they are logged in).
Compared to here: when I need to use authorize, app.get("/user", authorize(), (req, res) => res.render('user')); They cannot access the user route if they aren't logged in, that doesn't make sense.
TL;DR, I am a big noob. I have probably made an extremely obvious mistake and don't know what to google to find a solution. I have been experimenting with different stuff and console.log() and still can't find a solution.
Thank you everyone, for your help!
THE SOLUTION:
So, the solution? Change your approach. My approach to this was completely wrong, however, #HeikoTheißen was able to show me the right way. I only had to make a few small tweaks to his provided answer.
Unprotected Route:
app.get("/unprotected", authorize.authorize(), authorize.NoLoginRequired, (req, res) => res.render('unprotectedview'));
Protected Route:
app.get("/user", authorize.authorize(), (req, res) => res.render('user'));
Authorize():
Pretty much stayed the same. I did note, however, that it should be reformed to follow middleware like express documentation. rather than returning an array of functions to run.
isLoggedIn(): [REMOVED]
NoLoginRequired:
function NoLoginRequired(err, req, res, next) { //<-- make sure follow the (err, req, res, next) and do not add ().
if (err && err.name === "UnauthorizedError") { //<-- Needed to add this (err) as this was being triggered without an err being defined. (not sure how though, just saw in console)
next(); // proceed without logged-in user (works as expected. thanks!)
} else {
next(err); // report all other errors
}
}
I really appreciate your help solving this issue and hope to reform it to become clearer to the reader. I hope this may be able to help others with a similar issue. (although it's probably just because I am a noob)
You try to find out whether a user is logged in and then validate their login credentials, which leads to much duplicated code. Instead, you should always try to authorize() the user and for certain views that don't require a logged-in user catch the UnauthorizedError that express-jwt might throw with an error-handling middleware:
function NoLoginRequired(err, req, res, next) {
if (err.name === "UnauthorizedError") {
req.auth.name = "not logged-in user";
next(); // proceed without logged-in user
} else
next(err); // report all other errors
}
app.get("/protectedview", authorize(), function(req, res, next) {...});
app.get("/unprotectedview", authorize(), NoLoginRequired, function(req, res, next) {
res.end("Hi " + req.auth.name);
});

How to communicate from the back end to the front end in a MEAN stack application?

I'm new to the MEAN stack app and am having some trouble trying to send data from the server to the front end. However, I do have some communication going on, but this is all I can seem to do. In the server I have the json message being sent.
Server
router.route("/users/register").post((req, res) => {
registerLogin.findOne({ $or: [{ username }, { email }]}, (err, user) => {
if (err)
res.send(err);
else if (!username || !email || !password)
res.status(403).json({ registerErrRequired: "Fill out whole form." });
Front end
registerUser(username, email, password) {
const user = {
username: username,
email: email,
password: password
};
return this.http.post(`${this.uri}/users/register`, user)
.pipe(map(response => console.log(response)))
.subscribe(() => { this.router.navigate(["/users/login"]) }, (errResp) => {
errResp.error ? this.ngFlashMessageService.showFlashMessage({
messages: [errResp.error.registerErrRequired], // Where json message gets caught and shown to the browser
dismissible: true,
timeout: false,
type: 'danger'
}) : console.log("An unkown error occured.");
});
}
This works well, but I can't seem to do req/res other than using a flash message. My issue is wanting to use it in other ways than just flash messages. For example, if the user does not have a session, then I want them to navigate back to the the log in page. Here's what I tried but failed.
Server
// Middleware
const redirectLogin = ((req, res, next) => {
if (!req.session.user)
res.status(401).json({ loggedIn: false });
else
next();
});
// Route
router.route("/home").get(redirectLogin, (req, res) => {
Blog.find((err, docs) => {
if (err)
console.log(err);
else
res.json(docs);
});
});
Front end
homePage() {
// Here is where I would like to say, If session, then true, else navigate(["/users/login"])
if (loggedIn === false)
this.router.navigate(["/users/login"])
else
// Success
return this.http.get(`${this.uri}/home`);
}
The only way I found communication was through sending error flash messages, but nothing else.
What you can do is call an api to check whether the user is logged in or not in ngOnInit lifecycle hook,so every time your component loads you can check whether the session exists on backend and route accordingly.
export class App implements OnInit{
constructor(){
//called first time before the ngOnInit()
}
ngOnInit(){
//CheckLogin() is a method in your service which calls your backend api
this.http.post("your authentication url to check if session exits ",{username:username}).subscribe(data=>{
if (data["loggedIn"] === false)
this.router.navigate(["/users/login"])
})
}
}
Angular also has HTTP interceptors,you can solve this issue with jwt and http interceptors

Return value from one function to another with Node.JS

I am working on a login interface using the MEAN stack. I have managed to get it to work using PassportJS. My problem now is I need a way to let my client-side know whether the person logging in is an admin or user(user role). These info are available from my MongoDB.
The flow of my API call is as follow :
app.post('/login', passport.authenticate('local'), authRoutes.loginCheck);
First, it runs the passport.authenticate where it calls the function below
function verifyCredentials(username, password, done) // username & password from what user provide when logging in
{
console.log('VC');
User.findOne({username: username}, function(err, user) //query Mongo
{
console.log(user); // User role is available here, in JSON format
if(user === null) // if no username in database, do this
{
console.log('Username does not exist in database');
}
else
{
user.comparePassword(password, function(err, match) // function written to compare hashed password in Mongo & password provided by user
{
if(match)
{
done(null, {id: username, name: username});
return user; // this is not the correct syntax, but the idea is, I want to send over the user details here, so I can access the role later
}
else
{
done(null, null);
}
});
}
});
}
The verifyFunction is called with this syntax.
passport.use(new LocalStrategy(verifyCredentials));
Once that function is successfully called, the server executes the 2nd part of it which is the loginCheck.
module.exports.loginCheck = function(req, res)
{
console.log('Calling loginCheck route');
// I generate some sort of jwt token here
// payload, body, blah blah blah ...
console.log(req.body);
res.json({
authenticated: req.isAuthenticated(), //built-in authentication function, returns true or false
token: token // sends over token
role: user.role // want to send over something like this
}); // sends all these to client side as JSON
}
Since both functions are in different files, I am unclear if I have to require something or simply just pass an extra parameter to the loginCheck function. I have tried the latter though and it did not work.
One way that I could think of is do another Mongo query in the loginCheck function, but that would be kinda redundant.
Even a specific keyword for me to google up would definitely be of big help as don't I know what I should be looking for. The reason is because I am new to NodeJS, thus I am not familiarize with most of the terms yet.
I think these codes should suffice but if I am needed to provide more, let me know and I will do so. Thanks in advance !!
To pass control to next matching route you need to use next that passes as third argument in the routes:
function verifyCredentials(req, res, next) {
User.findOne({username: req.body.username}, function(err, user) //query Mongo
{
if(user === null) {
return next(new Error('Username does not exist in database'));
} else {
user.comparePassword(req.body.password, function(err, match) {
if(match) {
next(null, {id: username, name: username});
} else {
next(new Error('not match'));
}
});
}
});
}
app.post('/login', verifyCredentials, authRoutes.loginCheck);

Sending the User model's password to the client side in Sailsjs App

While learning Sailsjs I am going through an example Chat application code. But it seems after successful login or signup the functions in MainController to do these are sending the entire User object found in db or created in db to the client side by the res.send(user) line.
Am I correct here? Is it not wrong and insecure to send password?
Or It is just not sent? If so, how?
login action from the api/controllers/MainController.js:
login: function (req, res) {
var username = req.param('username');
var password = req.param('password');
// Users.findByUsername(username)...
// In v0.9.0 the find method returns an empty array when no results are found
// when only one result is needed use findOne.
Users.findOneByUsername(username)
.done(function loginfindUser(err, usr){
if (err) {
// We set an error header here,
// which we access in the views an display in the alert call.
res.set('error', 'DB Error');
// The error object sent below is converted to JSON
res.send(500, { error: "DB Error" });
} else {
if (usr) {
var hasher = require("password-hash");
if (hasher.verify(password, usr.password)) {
req.session.user = usr;
res.send(usr);
} else {
// Set the error header
res.set('error', 'Wrong Password');
res.send(400, { error: "Wrong Password" });
}
} else {
res.set('error', 'User not Found');
res.send(404, { error: "User not Found"});
}
}
});
},
add a toJSON function in your Users model's attributes that removes the password
module.exports = {
attributes: {
...
toJSON: function() {
user = this.toObject()
delete user.password
return user
}
}
}
if you're properly encrypting your user's passwords, sending them down to the client isn't horrible, but it is still regarded as a bad practice.

Access to "req" Object in Supertest After a Response

Is there any way to directly access the req object in supertest, while/after the request is being tested? I want to test my passport strategies, so I want to check req.user, req.session, and perhaps others. I know I can test page redirects or flash, as those are what my strategies do, but it seems useful to see if there is a user on the req object, as well. If I do this, I can also check how many users there are at any one time.
I will sign users up with the "local-signup" strategy, which is defined thusly:
'use strict';
// get passport & mongoose
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var UserModel = require('mongoose').model('User');
module.exports = function() {
// signup function
passport.use('local-signup', new LocalStrategy({
passReqToCallback: true // pass the entire request to the callback
},
function(req, username, password, done) {
process.nextTick(function() {
// find a user with the same username
UserModel.findOne({username: username}, function(err, user) {
// if there is an error, log it then return it
if(err) {
console.log("Error finding a user in the database: " + err);
return done(err);
}
// if a user was already found
if(user) {
return done(null, false, "User already exists");
}
// if we get this far, create a new user from the request body
var newUser = new UserModel(req.body);
// save it and sign it in
newUser.save(function(err) {
if(err) {
console.log("Error during signup: " + err);
return done(err);
}
return done(null, newUser);
});
});
});
}
));
};
One way I use this strategy is like this:
My "local" strategy is defined like this:
'use strict';
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var UserModel = require('mongoose').model('User');
module.exports = function() {
// create our local passport strategy & use it
passport.use(new LocalStrategy({
// use the default names for the username & password fields
usernameField: 'username',
passwordField: 'password'
},
// main strategy function
function(username, password, done) {
// find user with given username
UserModel.findOne({
username: username
},
// with this username, do this
function(err, user) {
// if there's an error, log it then pass it along
if(err) {
console.log("Error during login: " + err);
return done(err);
}
// if the username and/or password is incorrect, return an error
// along with a message
if(!user || !user.authenticate(password)) {
return done(null, false, {
message: 'Invalid username and/or password'
});
}
// if everything is correct, return the user document from the database
return done(null, user);
});
}
));
};
I use both strategies like this, for example:
app.route(pageName).post(function(req, res, next) {
passport.authenticate(strategyName, function(err, user, info) {
if(err || !user) {
res.status(401).send(info);
}
else {
req.login(user, function(err) {
if(err) {
res.status(400).send(err);
}
else {
res.send(null);
}
});
}
})(req, res, next);
});
I tried
request = require('supertest');
this.authServer = require('../my-server');
request(this.authServer)
.put('/signup')
.set('Content-Type', 'application/json')
.set('Host', 'konneka.org')
.send(this.fullUser)
.end(function(req, res, done) {
console.log(res);
});
The res object I logged, inside the end() function, which was way too long to show here, has a req object defined on it, but it seems to only have the objects & functions that were defined before the request was opened. In other words, it does not have req.user, req.session, or other objects I want, because they are defined after the request completes and a new request is started. I noticed it has status codes, as well, which are only defined after the request completes, so I must be missing something.
Is there any way to get access to the req object after the request you are testing is ended? Or am I going about this completely the wrong way?
You cannot do what you want using supertest.
Not sure if this helps but I'll add a little context to clarify the answer:
supertest is a wrapper on top of superagent (client side) with some basic hooks into express to start up the HTTP listener. Under the hood, it really is not any different from starting up your express app, waiting for it to listen on a port, making an HTTP request to that port, and parsing the result. In fact, that is exactly what it does.
So essentially supertest only has access to what ever your client would have access to (a browser or some API client). In other words, if it isnt in the HTTP response body, you wont have access to it. req.user and req.sesssion are server side state variables that are (most likely) not in the response (unless you are doing something strange).
If you want to test in exactly the way you describe, you will have to use some alternative strategy of testing, not supertest.
I found this question when I thought I wanted to do this, and for me it worked well to check the status of the user created by the request instead of verifying the content of the req object. You do have access to the full database where I assume you users somehow ends up.

Resources