Issue in executing callback and saving data to mysql db - node.js

I am trying to authenticate using google plus api and trying to save the google user details in the callback function from google plus api but due to some reason i am unable to pass values from google plus api response to callback request.
Snippet from Router.js
router.get("/auth/google", passport.authenticate('google', { scope: ['profile','email'] }));
router.get("/auth/google/callback" , passport.authenticate('google') , googleInsert);
Snippet from User.controller.js
const {
getGoogleUserByEmail,
createGoogleUser,
} = require("./user.service.js");
module.exports = {
googleInsert: (req, res) => {
body = req.body;
body.googleId = req.body.profile.id;
body.firstName = req.body.profile.name.givenName;
body.lastName = req.body.profile.name.familyName;
body.email = req.body.profile.emails[0].value;
body.photoUrl = req.body.profile.photos[0].value;
//const salt = genSaltSync(10);
//body.password = hashSync(body.password, salt);
//verify if email id exists
getGoogleUserByEmail(body.email, (err, results) => {
if (err) {
console.log(err);
}
//Email id already registered and exists in db
if (results) {
console.log("Google Email already exists");
return res.status(409).json({
success: 0,
data: "Google Email already exist",
});
}
console.log(
"Google Email id is not registered, proceed with Google User Insert"
);
if (!results) {
createGoogleUser(body, (err, createResults) => {
console.log(body);
if (err) {
console.log(err);
return res.status(500).json({
success: 0,
message: "Database connection error",
});
}
if (createResults.affectedRows == 1) {
console.log("inside succcess is 1");
//Insert into UserRole Table
createUserRole(
createResults.insertId,
(body.role_id = 2),
(err, results) => {
console.log(results);
if (err) {
console.log(err);
return res.status(500).json({
success: 0,
message: "DB error",
});
}
if (!err) {
console.log("Google User created successfully");
return res.status(200).json({
success: 1,
data: createResults,
});
}
}
);
}
});
}
});
},
};
Passport.js
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const keys = require('../config/keys');
passport.use(
new GoogleStrategy(
{
clientID: process.env.clientID,
clientSecret: process.env.clientSecret,
callbackURL: "http://localhost:1111/api/users/auth/google/callback",
//passReqToCallback: true
},
(accessToken, refreshToken, profile, callback) => {
console.log("access token", accessToken);
console.log("refresh token", refreshToken);
console.log("profile", profile);
console.log("callback", callback);
}
)
);
The issue i am getting is i am not sure how to get the value from google authentication to be used in callback request and am not sure if callback is working or not. After i select the email id from google plus api client screen, it just goes into infinite loop and no data is getting inserted into db.
body = req.body;
body.googleId = req.body.profile.id;
body.firstName = req.body.profile.name.givenName;
body.lastName = req.body.profile.name.familyName;
body.email = req.body.profile.emails[0].value;
body.photoUrl = req.body.profile.photos[0].value;

Related

Bcrypt compareSync is always returning False

trying the user Auth first time and able to create the users, it seems the bcrypt password hash is working when registering the user as I can see the hashed password in the DB, However when I am trying to login with the same credential, getting an error Invalid email or password based on my code below:
const {
create,
getUserByUserId,
getUserByUserEmail,
} = require('./user-services')
const {genSaltSync, hashSync, compareSync} = require('bcrypt')
const {sign} = require('jsonwebtoken')
module.exports = {
createUser: (req, res) => {
const body = req.body;
const salt = genSaltSync(10);
body.password = hashSync(body.password, salt);
create(body, (err, results) => {
if (err) {
console.log(err);
return res.status(500).json({
success: 0,
message: "Database connection errror"
});
}
return res.status(200).json({
success: 1,
data: results
});
});
},
login: (req, res) => {
const body = req.body;
console.log(body.user_email)
getUserByUserEmail(body.user_email, (err, results) => {
if (err) {
console.log(err);
}
if (!results) {
return res.json({
success: 0,
data: "* Invalid email or password *"
});
}
const result = compareSync(body.password, results.password);
console.log(result)
console.log(results.password)
console.log(body.password)
if (result) {
results.password = undefined;
const jsontoken = sign({ result: results }, "test1234", {
expiresIn: "1h"
});
return res.json({
success: 1,
message: "Login successfully",
token: jsontoken
});
} else {
return res.json({
success: 0,
data: "Invalid email or password"
});
}
});
},
}
When console log, I can see the body. password and response from DB. Here is what I am getting in the console.log
Solved it. Modified MySQL column for Password to VARCHAR(1024). it was
limited to VARCHAR(56)

How to test authentication with jwt inside a cookie with supertest, passport, and JEST

Hey guys I am currently am trying to do something similar to what is posted here:
How to authenticate Supertest requests with Passport?
as I would like to test other endpoints that require authentication but in addition need to pass in a jwt. Right now, I tested it on POSTMAN and on the browser and it seems like it's working fine, but my test cases keep on breaking. I have a login POST route that is setup like so:
AccountService.js
// Login POST route
router.post('/account_service/login', (req, res, next) => {
passport.authenticate('local-login', (err, user, info) => {
try {
if (err) {
const error = new Error('An Error occurred: Cannot find user');
return next(error);
} else if (!user) {
return res.redirect('/account_service/login');
}
req.login(user, { session: false }, (error) => {
if (error) {
return next(error);
}
const email = req.body.email;
const role = req.user[0].role;
const id = req.user[0].id;
const user = {
email: email,
role: role,
id: id
};
const accessToken = jwt.sign(user, config.ACCESS_TOKEN_SECRET, {
expiresIn: 28800 // expires in 8 hours
});
const cookie = req.cookies.cookieName;
if (cookie === undefined) {
// set a new cookie
console.log('setting new cookie');
res.cookie('jwt', accessToken, { maxAge: 900000, httpOnly: true });
res.send({ token: accessToken });
} else {
// cookie was already present
console.log('cookie exists', cookie);
}
res.redirect('/account_service/profile');
});
} catch (error) {
return next(error);
}
})(req, res, next);
});
After the user is authenticated, I assign a JSON web token to the user and place it in the cookie so it gets stored within the headers for authorized requests. Here is an example:
AccountService.js
// Get all users
router.get('/account_service/all_users', passport.authenticate('jwt', { session: false }), (req, res, next) => {
const sql = 'select * from user';
const params = [];
db.all(sql, params, (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({
message: 'success',
data: rows
});
});
});
I use passport.authenticate to ensure that the jwt is valid. This GET request only works after I login with admin user account.
Within my passport file I have it setup like so:
passport.js
const LocalStrategy = require('passport-local').Strategy;
const db = require('../database.js');
const bcrypt = require('bcrypt');
const config = require('../config/config.js');
const JwtStrategy = require('passport-jwt').Strategy;
const cookieExtractor = function (req) {
var token = null;
if (req && req.cookies) token = req.cookies.jwt;
return token;
};
module.exports = function (passport) {
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, (req, email, password, done) => {
try {
const sql = `select * from user WHERE email = "${email}"`;
const params = [];
db.all(sql, params, (err, row) => {
if (err) {
return done(err);
}
if (!row.length || !bcrypt.compareSync(password, row[0].password)) {
return done(null, false, req.flash('loginMessage', 'Inavalid username/password combination. Please try again.'));
}
return done(null, row);
});
} catch (error) {
return done(error);
}
}));
const opts = {};
opts.jwtFromRequest = cookieExtractor; // check token in cookie
opts.secretOrKey = config.ACCESS_TOKEN_SECRET;
// eslint-disable-next-line camelcase
passport.use(new JwtStrategy(opts, function (jwtPayload, done) {
try {
const sql = `select * from user WHERE email = "${jwtPayload.email}"`;
const params = [];
db.all(sql, params, (err, row) => {
if (err) {
return done(err);
}
if (!row.length || !bcrypt.compareSync('admin', jwtPayload.role)) {
return done(null, false, { message: '403 Forbidden' });
}
return done(null, row);
});
} catch (error) {
return done(error);
}
}));
};
Here's where I get confused as my test cases break. I am trying to login before my test cases to allow my other test cases to run but I end up getting a 401 error. Here are my test cases:
accountservice.test.js
const app = require('../../app');
const supertest = require('supertest');
const http = require('http');
describe('Account Service', () => {
let server;
let request;
beforeAll((done) => {
server = http.createServer(app);
server.listen(done);
request = supertest.agent(server);
request.post('/account_service/login')
.send({ email: 'admin#example.com', password: 'admin' })
.end(function (err, res) {
if (err) {
return done(err);
}
console.log(res);
done();
});
});
afterAll((done) => {
server.close(done);
});
it('Test request all users endpoint | GET request', async done => {
const response = await request.get('/account_service/all_users');
expect(response.status).toBe(200);
expect(response.body.message).toBe('success');
expect(response.body.data.length).toBe(3);
done();
});
});
But my test cases fail as I get a 401 error when it expects a 200 success code.
I tried thinking of a way to extract the jwt from a cookie after the login call so that I can set up the headers for the /account_service/all_users GET request code but was unable to find a way using Supertest. I saw this post: Testing authenticated routes with JWT fails using Mocha + supertest + passport but saw that it gets the token from the body.
After messing around with my code, I ended up having issues with in-memory storage and running asynchronous db.run functions that would call every time I ran my server. So I used a file to store my data and ran my tests again and it ended up working!
Here was the faulty code:
const sqlite3 = require('sqlite3').verbose();
const md5 = require('md5');
const DBSOURCE = ':memory:';
const db = new sqlite3.Database(DBSOURCE, (err) => {
if (err) {
// Cannot open database
console.error(err.message);
throw err;
} else {
db.run(`CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name text,
email text UNIQUE,
password text,
status text,
comments text,
photos text,
CONSTRAINT email_unique UNIQUE (email)
)`,
(err) => {
if (err) {
// Table already created
console.log('Table already created');
} else {
// Table just created, creating some rows
const insert = 'INSERT INTO user (name, email, password, status, comments, photos) VALUES (?,?,?,?,?,?)';
db.run(insert, ['user_delete', 'user_delete#example.com', md5('admin123456'), 'pending_deleted', 'comment1,comment2', 'https://giphy.com/gifs/9jumpin-wow-nice-well-done-xT77XWum9yH7zNkFW0']);
db.run(insert, ['user_no_delete', 'user#example.com', md5('user123456'), 'active', 'comment1', 'https://giphy.com/gifs/cartoon-we-bare-bears-wbb-NeijdlusjcduU']);
db.run(insert, ['mikey', 'mikey#example.com', md5('mikey123'), 'pending_deleted', 'comment1', 'https://giphy.com/gifs/wwe-shocked-vince-mcmahon-gdKAVlnm3bmKI']);
}
});
}
});
module.exports = db;
I simply stored this data within a file and used this code instead:
const sqlite3 = require('sqlite3').verbose();
const DBSOURCE = 'mockdb.sqlite';
// Data inserted inside file
/*
db.run(insert, ['user_delete', 'user_delete#example.com', bcrypt.hashSync('admin123456', saltRounds), 'pending_deleted', 'comment1,comment2', 'https://giphy.com/gifs/9jumpin-wow-nice-well-done-xT77XWum9yH7zNkFW0', bcrypt.hashSync('user', saltRounds)]);
db.run(insert, ['user_no_delete', 'user#example.com', bcrypt.hashSync('user123456', saltRounds), 'active', 'comment1', 'https://giphy.com/gifs/cartoon-we-bare-bears-wbb-NeijdlusjcduU', bcrypt.hashSync('user', saltRounds)]);
db.run(insert, ['mikey', 'mikey#example.com', bcrypt.hashSync('mikey123', saltRounds), 'pending_deleted', 'comment1', 'https://giphy.com/gifs/wwe-shocked-vince-mcmahon-gdKAVlnm3bmKI', bcrypt.hashSync('user', saltRounds)]);
db.run(insert, ['admin', 'admin#example.com', bcrypt.hashSync('admin', saltRounds), 'active', 'admincomments', 'adminphoto', bcrypt.hashSync('admin', saltRounds)]);
console.log('last hit in database');
});
*/
const db = new sqlite3.Database(DBSOURCE, (err) => {
if (err) {
// Cannot open database
console.error(err.message);
throw err;
}
console.log('Connection successful!');
});
module.exports = db;
I also ended up using supertest.agent.
const app = require('../../app');
const supertest = require('supertest');
const http = require('http');
const db = require('../../database/database.js');
describe('Account Service', () => {
let server;
let request;
// Find cookie management option.
beforeAll(async (done) => {
server = http.createServer(app);
server.listen(done);
request = supertest.agent(server);
done();
});
And it ended up working and successfully solving my issue!

Update a property in document in an Express route (Mongoose, MongoDB, Express)

I've successfully set up the registration and login functionality using Express, MongoDB and Mongoose.
I would like to log when the user last visited the site once the user's credential is accepted in a lastConnection property of the user document,
I tried but "lastConnection" is null (see the line below where I add a comment)
router.post("/login", async function(req, res) {
const { errors, isValid } = validateLoginInput(req.body);
if (!isValid) {
return res.status(400).json(errors);
}
const email = req.body.email;
const password = req.body.password;
const user = await User.findOne({ email }).then(user => {
if (!user) {
errors.email = "Email already exists";
}
console.log("user ", user); <-- returns an object with the datas of user
bcrypt.compare(password, user.password).then(isMatch => {
if (isMatch) {
const payload = {
id: user.id,
name: user.name
};
user.lastConnection = new Date(); <-- doesn't work
jwt.sign(
payload,
keys.secretOrKey,
{
expiresIn: 7200
},
(err, token) => {
res.json({
success: true,
token: "Bearer " + token
});
}
);
} else {
errors.password = "Password is not correct";
// return res
// .status(400)
// .json({ passwordincorrect: "Password incorrect" });
}
});
});
return {
errors,
isValid: isEmpty(errors)
};
});
Any ideas? I think I have to do an update but I don't know where to put it
Try replacing user.lastConnection = new Date(); with
user.update({ lastConnection: new Date() })
.then( updatedUser => {
console.log(updatedUser)
// put jwt.sign code here
})

Postgres results.rows[0] are undefined

Not sure what I'm doing wrong, other than writing really messy code for a project I'm doing to learn Nodejs.
This used to be an async function/object but decided to get rid of try catch because my code ran twice for some reason I couldn't figure out.
Eliminating the try catch hasn't really stopped it from still running twice I think.
So the question: Why is my results.rows[0].email returning as undefined?
Sometimes it works sometimes it doesn't. I don't know why. Any help would rock.
router.post('/', (req, res, next) => {
const {password, email} = req.body
//var LoginPwd = await bcrypt.hash(password, 5);
const loginPlainPwd = password;
pool.query("SELECT password, id, email FROM companies_admins WHERE email=$1", [email], (err, results) => {
if (err)
{
throw err;
}
const dbemail = results.rows[0].email
const dbPwd = results.rows[0].password
const dbid = JSON.stringify(results.rows[0].id)
console.log('results.rows[0] = ' + results.rows[0])
console.log('loginPlainPwd = ' + loginPlainPwd)
console.log('dbPwd = ' + dbPwd)
//console.log(JSON.stringify(results.rows[0]))
//res.cookie('userId', id)
//res.sendFile(path.join(__dirname, './views/account.html'));
//bcrypt.compare(loginPlainPwd, dbPwd, (err, res) => {
if (loginPlainPwd != dbPwd)
{
console.log("loginPlainPwd != dbPwd")
/////////////////////////////////////////////?SHOULD THIS BE OUTSIE POOL.QUERY??????
console.log('err')
return res.status(401).json({
message: 'Auth failed'
});
}
else if (loginPlainPwd == dbPwd)
{
//token variable signage/creation with user data and expiration (i also included .env)
const token = jwt.sign(
{
email: dbemail,
userId: dbid,
},
process.env.JWT_KEY,
{
expiresIn: "1h"
},
);
console.log("passwords match: token created:" + token)
res.cookie('userId', token,)
console.log('cookie should be sent')
databaseJWTin(err, token, dbemail); // database function to store jwttoken from below to store jwt in database
console.log('databaseJWT function should have fired')
//had to use ../ below because path was going into routes directory for some reason
res.sendFile(path.join(__dirname, '../views/account.html'))
//return res.status(200).json({
// message: "Auth successful",
// token: token
//});
}
//res.sendFile(path.join(__dirname, './views/account.html'))
});
//res.sendFile(path.join(__dirname, './views/account.html'));
})
Please check whether result contains data in it.
router.post('/', (req, res, next) => {
const { password, email } = req.body
//var LoginPwd = await bcrypt.hash(password, 5);
const loginPlainPwd = password;
pool.query("SELECT password, id, email FROM companies_admins WHERE email=$1", [email], (err, results) => {
if (err) {
throw err;
}
if (results && results.length>0) {
const dbemail = results.rows[0].email
const dbPwd = results.rows[0].password
const dbid = JSON.stringify(results.rows[0].id)
console.log('results.rows[0] = ' + results.rows[0])
console.log('loginPlainPwd = ' + loginPlainPwd)
console.log('dbPwd = ' + dbPwd)
//console.log(JSON.stringify(results.rows[0]))
//res.cookie('userId', id)
//res.sendFile(path.join(__dirname, './views/account.html'));
//bcrypt.compare(loginPlainPwd, dbPwd, (err, res) => {
if (loginPlainPwd != dbPwd) {
console.log("loginPlainPwd != dbPwd")
/////////////////////////////////////////////?SHOULD THIS BE OUTSIE POOL.QUERY??????
console.log('err')
return res.status(401).json({
message: 'Auth failed'
});
}
else if (loginPlainPwd == dbPwd) {
//token variable signage/creation with user data and expiration (i also included .env)
const token = jwt.sign(
{
email: dbemail,
userId: dbid,
},
process.env.JWT_KEY,
{
expiresIn: "1h"
},
);
console.log("passwords match: token created:" + token)
res.cookie('userId', token)
console.log('cookie should be sent')
databaseJWTin(err, token, dbemail); // database function to store jwttoken from below to store jwt in database
console.log('databaseJWT function should have fired')
//had to use ../ below because path was going into routes directory for some reason
res.sendFile(path.join(__dirname, '../views/account.html'))
//return res.status(200).json({
// message: "Auth successful",
// token: token
//});
}
//res.sendFile(path.join(__dirname, './views/account.html'))
}
});
//res.sendFile(path.join(__dirname, './views/account.html'));
})

Keep session/cookies in React after authenticating with Passport

My React application is logging in successfully, and the backend is also working successfully.
Question: What is going wrong here? This works fine in Postman and retains my cookie/session information.
How do I make sure react retains the necessary authentication details for fetching from the API?
However, when rendering a new page and accessing my API to get user information, I'm received a 401 Unauthorized error.
I am using an emitter to fetch my data.
My API works fine using Postman but does not work in React. Code below:
Passport login & User serialization:
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
connection.query("select * from users where id = "+ id, function(err,user) {
done(err, user[0]);
});
});
passport.use('local-signup', new LocalStrategy({
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true
},
function(req, email, password, done) {
connection.query("select * from users where email = '"+ email +"'",function(err,user) {
console.log(user);
console.log("above row object");
if (err)
return done(err);
if (user.length) {
return done(null, false);
} else {
var passwordHash = sha1(req.body.password)
var newUserMysql = new Object();
newUserMysql.name = req.body.name;
newUserMysql.password = passwordHash;
newUserMysql.email = req.body.email;
newUserMysql.group_id = req.body.group_id;
var insertQuery = "INSERT INTO users (name, password, email, group_id) values ('"+ req.body.name +"','"+ passwordHash +"','"+ req.body.email +"','"+ req.body.group_id +"')";
console.log(insertQuery);
connection.query(insertQuery,function(err,user){
newUserMysql.id = user.insertId;
return done(null, newUserMysql);
});
}
});
}));
My react emitter for profile information:
export default class ProfileController {
constructor(emitter) {
this.Emitter = emitter;
}
init() {
this.Emitter.on('GetMyUser', this.getMyUser.bind(this));
}
getMyUser() {
fetch(`${domain}/api/user/self`, {//This is where it appears to be failing!!!
method: 'GET',
credentials: 'include'
})
.then((res) => {
return res.json();
})
.then((res) => {
this.Emitter.emit('OnGetMyUser', res.data);
})
.catch((e) => {
this.Emitter.emit('OnGetMyUser', 'Failed to load User from Controller.');
Alert.error('Failed to load user.');
});
}
}
And finally, the route itself in my API:
app.get('/api/user/self', function(req, res) {//Get current user by id profile information
if (!req.isAuthenticated()) {
return res.status(401).send({code: 401, message: "Unauthorized"});
}
connection.query("SELECT id, name, email, group_id FROM users WHERE id = ?", [
req.user.id
], function(error, result) {
if (error) {
console.log(error);
return res.status(500).send({code: 500, message: "Internal Server Error"});
}
else if (result.rowCount === 0) {
return res.status(404).send({code: 404, message: "Not found"});
}
console.log(result);
return res.send(userFull(result));
});
});

Resources