mocha and supertest.agent not working as expected - express

I'm trying to write some tests where I need to authenticate first. If I make multiple requests in "before()" I get connection refused. If I split it between "before()" and "it()" it works but I cannot acheive what I want.
Code I want to work:
var agent = request.agent(myExpressApp),
token;
before(function(done) {
async.series([
function(cb) {
agent
.post('/1/auth/login')
.send({
email: 'john#smith.com',
password: 'j0hn_Sm1TH'
})
.expect(200)
.end(cb);
}, function(cb) {
agent
.get('/1/auth/authorize')
.query({
response_type: 'token',
client_id: 'some id',
redirect_uri: 'http://ignore'
})
.expect(302)
.end(function(err, res) {
/* console.log(arguments) = { '0':
{ [Error: connect ECONNREFUSED]
code: 'ECONNREFUSED',
errno: 'ECONNREFUSED',
syscall: 'connect' } }*/
if (err) return cb(err);
cb();
});
}
], done);
});
it('some authenticated task', function(done) {
// do something else
done();
});
Code that is working:
var agent = request.agent(myExpressApp),
token;
before(function(done) {
async.series([
function(cb) {
agent
.post('/1/auth/login')
.send({
email: 'john#smith.com',
password: 'j0hn_Sm1TH'
})
.expect(200)
.end(cb);
}, function(cb) {
cb();
}
], done);
});
it('some authenticated task', function(done) {
agent
.get('/1/auth/authorize')
.query({
response_type: 'token',
client_id: 'some id',
redirect_uri: 'http://ignore'
})
.expect(302)
.end(function(err, res) {
if (err) return done(err);
done();
});
});

You have encountered issue 153 with superagent. Annoyingly and magically, superagent looks at the arity of the callback function you pass to it. If the function is declared accepting 2 arguments, superagent conforms to the node convention of (error, result), however, if the callback function looks like it expects any other number of arguments (zero in the case of use with async.js), it invokes the function as just callback(res) which I guess TJ thought would be nice for the browser, but for node it totally breaks convention.
The exact line of code in question is here

Related

How to save user data in signup/register using passport.js with express server

I am working to implement passport.js in my react/node/express/sequelize app.
I currently have middleware working for logging a user in, and checking if the user is authenticated. However, when a new user signs up or registers, their user data is not being saved to the server session (even though it is created in the DB). This means after a user registers, they have to go to the login page, enter their credentials and hit login before their session is saved.
My login function is simple:
router.post('/login', passport.authenticate('local'), (req, res) => {
//console.log(req);
console.log("Is authenticated: " + req.isAuthenticated());
res.json(req.user);
});
it uses passport.authenticate local strategy, which I've defined as:
passport.use(new LocalStrategy(
{
usernameField: 'email',
},
((email, password, done) => {
User.findOne({
where: {
email,
},
}).then((dbUser) => {
if (!dbUser) {
return done(null, false, {
message: 'Incorrect email.',
});
}
if (!dbUser.validPassword(password)) {
return done(null, false, {
message: 'Incorrect password.',
});
}
return done(null, dbUser);
});
}),
));
I know from the passport documentation and from looking at other questions that the passport.authenticate local strategy automatically calls the req.login() function, which serializes my user information and saves it in the server session.
My main issue is I'm not sure exactly how to implement this during my register function.
router.post('/signup', (req, res) => {
const user = {
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
password: req.body.password,
};
User.findOrCreate({where: {email: user.email}, defaults: user})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the User."
});
});
});
I've tried calling req.login() after findOrCreate, but I get an error:
(node:42313) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
If I use my local strategy, I get an unauthorized response (since the credentials I'm using to authorize are not yet in the DB).
I figure I need to make a custom strategy for sign in, but it's not clear to me if that's the right approach, or how I would specify it.
I fixed this by adding req.login before res.send:
router.post('/signup', (req, res) => {
const user = {
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
password: req.body.password,
};
User.findOrCreate({where: {email: user.email}, defaults: user})
.then(data => {
req.login(data[0], function(err) {
if (err) {
console.log("login function erroring out with: " + err)
}
});
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the User."
});
});
});

`Cannot set headers after they are sent to the client ` when I try to login with `passport-google-oauth20`

When I tried to implement Google OAuth into my node app using passport-google-oauth20, I got a problem.
Whenever I attempt the first login to the secrets page with the following code, I fail to authenticate and got redirected to the /login page, also got the error saying Cannot set headers after they are sent to the client at the line serializing the user, even though newUser has been saved in the mongoDB.
However, I can successfully authenticate and login to the secrets page the second login attempt.
What's happening behind the scenes where the error occurs? How can I successfully authenticate the user when the first login attempt?
I referred to this Q&A as well.
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/google/secrets"
},
(accessToken, refreshToken, profile, done) => {
User.findOne({ googleId: profile.id }, (err, foundUser) => {
if (err) return done(err);
if (!foundUser) {
const newUser = new User({
googleId: profile.id
});
newUser.save((err, savedUser) => {
if (err) throw err;
return done(null, savedUser);
});
}
return done(null, foundUser);
});
}
));
passport.serializeUser((user, done) => {
done(null, user.id); ///// The error occurs at this line /////
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile'] }));
app.get(
"/auth/google/secrets",
passport.authenticate("google", {
successRedirect: "/secrets",
failureRedirect: "/login"
})
);
app.get("/secrets", (req, res) => {
if (req.isAuthenticated()) return res.render("secrets");
res.redirect("/login");
});
The issue I see is within the verify callback. Calling return done(null, savedUser) will occur asynchronously. This means that the program will first call return done(null, foundUser) then after the saving call return done(null, savedUser).
To resolve the issue I would recommend refactoring the verify callback to use async/await. This makes it easier to reason about and reduces the chances of race conditions from conflicting callbacks.
Example Refactor:
async (accessToken, refreshToken, profile, done) => {
try {
let foundUser = await User.findOne({ googleId: profile.id });
if (!foundUser) {
const newUser = new User({
googleId: profile.id
});
await newUser.save();
return done(null, newUser);
}
return done(null, foundUser);
} catch (err) {
return done(err);
}
}));

Express Passportjs Authenticate not being reached in router callback

If I pass passport.authenticate("local") as middleware into my route, it executes. But this way I do not have access to res so I can send a message back to my front end. However, if I attempt to call it in the route callback function, it is not firing.
router.post("/login", function(req, res, next) {
passport.authenticate("local", function(err, user, info) {
console.log("Unreached"); // This is not logging
});
})
Here is my passport.use inside app.js
passport.use(new LocalStrategy({
usernameField: "portalId"
}, function(portalId, enteredPassword, done) {
var params = {
TableName: "MyTableName",
KeyConditionExpression : "PortalID = :portalID",
ExpressionAttributeValues : {
":portalID" : Number(portalId)
}
}
docClient.query(params, function(err, user) {
if (err) throw err;
let realPassword = user.Items[0].password;
bcrypt.compare(enteredPassword, realPassword, function(err, res) {
if (err) throw err;
if (res) {
return done(null, user);
}
if (!res) {
return done(null, false, { message: "Invalid Credentials" });
}
})
})
}));
Saw in some other post a snippet of code using the custom callback and he had (req, res, next) right after the passport.authenticate function. I added this and my code was being fired now.

Difference between POST and GET (hapijs)

I'm new to the hapijs. Can someone tell me what's the difference between POST and GET in hapijs? For some reason my POST method doesn't work at all so I do is INSERT via GET function.
GET:
server.route({
method: 'GET',
path: '/index/{orderId}',
config: {
handler: test,
validate: {
params: {
orderId: Joi.string()
.required()
.description('Order indentifier')
}
}
}
});
And test function:
function test (request, reply) {
console.log(request.params.orderId);
var params = {orderId: request.params.orderId}
connection.query('INSERT QUERY HERE', function (err, res, fields) {
if (err) throw error;
console.log(res);
reply(res);
});
}

passport.js deserializeUser not modifying req.user

I am not seeing any effect on the req.user or req.session.passport.user objects as a result of deserializeUser().
My understanding is that successful completion of deserializeUser() should result in my User object which I retrieve from a DB should be set to the req.user property. Is this correct?
Furthermore, it seems that the purpose of this callback is to allow me to serialize a small object (e.g.: {username: 'me', email: 'me#me.com'}) in the cookie itself, but then add more user info retrieved from the database to use downstream.
Instead, I see the following:
passport.serializeUser(function(user, done) {
// user: 'stan#stadelman.com'
fetchUser(user, function(u) {
// u: { email: 'stan#stadelman.com', name: 'Stan Stadelman' }
done(null, u);
});
});
passport.deserializeUser(function(id, done) {
// id: { email: 'stan#stadelman.com', name: 'Stan Stadelman' }
fetchUser(id.email, function(user) {
// user: { email: 'stan#stadelman.com', name: 'Stan Stadelman', local_props: 'from_db'}
done(null, user);
});
});
req.user = 'stan#stadelman.com'
req.session = { cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
passport: { user: { email: 'stan#stadelman.com', name: 'Stan Stadelman' } } }
My expectation was that the req.session.passport.user and/or req.user objects should contain the local_props property value. Am I reading the documentation & tutorials incorrectly, or is there an issue? I've cross-posted in passport.js github here.
My express & passport setup is as follows:
// express configs
app.use(cookieParser(expressSecret));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(expressSession({
secret: expressSecret,
resave: true,
saveUninitialized: true }
));
app.use(passport.initialize());
app.use(passport.session());
// passport implementation
passport.use(new BasicStrategy(/...));
You should pass user id to done callback in serialize
passport.serializeUser(function(user, done) {
done(null, user.id);
});
And then use that id to fetch user in deserialize
passport.deserializeUser(function(id, done) {
fetchUser(id, function(user) {
done(null, user);
});
});
I run into a similar problem but caused by something else. Passport.js deserializeUser() does its job setting user object to req.user:
passport.deserializeUser(async function(id, done) {
try {
const account = await Account.findById(id);
done(null, account);
} catch (error) {
done(error, null)
}
});
However, in order to check whether a user is logged in, I have a route on the server /account/me that takes req.user and returns it as json:
router.route('/account/me').get(mwAuthentication, (req, res) => {
res.json(req.user);
});
To filter sensitive properties, I have a method toJSON() defined in Mongoose schema:
AccountSchema.methods.toJSON = function() {
let account = this;
let accountObject = account.toObject();
return _.pick(accountObject, ['_id', 'fullName', 'email', 'facebookId', 'photoUrl']);
};
When you call res.json(req.user) it calls JSON.stringify(req.user) internally, which again calls my toJSON() method and limits the object to properties that I picked.
Hope this helps someone.