Express session not saving after successful authentication - mysql

I am implementing a login system for my project. This project is divided in two, a server portion in NodeJS, and a client portion in ReactJS. Both of these are wrapped up in docker containers including a couple more containers for mySQL and PHPMyAdmin. Thus far, I've been able to connect to databases in the mySQL container and insert into a table for Users. Now, I'm trying to log in with a user, then save this user information if the login is successful, and return the session when asked. So I call the sign in get request as follows in the front-end:
export function signIn(table, userName, password) {
return axios.get(`http://localhost:8000/signin`, {
params: {
table,
userName,
password,
},
}, {withCredentials: true}).then((response) => {
if (response.data.length === 1) {
return "success";
}
return response;
});
}
Then in the server, I receive and work with the information like this:
const bcrypt = require('bcryptjs');
const bodyParser = require('body-parser');
const cors = require('cors');
const express = require('express');
const multer = require('multer');
const mysql = require('mysql');
const nodeMailer = require('nodemailer');
const session = require('express-session');
const smtpTransport = require('nodemailer-smtp-transport');
const app = express();
const upload = multer();
app.use(session({
secret: 'secret',
resave: true,
saveUninitialized: true,
cookie: {
maxAge: 7 * 24 * 60 * 60 * 1000,
secure: false,
}
}));
app.use(cors(({
credentials: true,
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
const pool = mysql.createPool({
host: process.env.MYSQL_HOST_IP,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
});
app.get('/signin', (req, res) => {
const { table, userName, password } = req.query;
pool.query(`select * from ${table} where username = '${userName}'`, (err, results) => {
if (err) {
res.send(err);
} else {
if (bcrypt.compareSync(password, results[0].password)) {
req.session.userId = results[0].id;
req.session.name = results[0].name;
req.session.email = results[0].email;
req.session.sex = results[0].sex;
req.session.img = results[0].img;
req.session.userName = results[0].username;
req.session.about = results[0].about;
req.session.save(err => console.log(err));
res.send(results);
} else {
res.send([]);
}
}
});
});
Then I expect to call it with another request to get the information back and use to to modify a front end component's state like this (both of these requests are in the same file):
app.get('/loggeduser', (req, res) => {
if (req.session.userId) {
const {
userId,
name,
email,
sex,
img,
userName,
about,
} = req.session;
const userInfo = {
userId,
name,
email,
sex,
img,
userName,
about,
};
res.send(userInfo);
} else {
res.send({});
}
});
and the component calls it like this:
export function getLoggedUser(setUserInfo) {
axios.get(`http://localhost:8000/loggeduser`, {}, {withCredentials: true}).then((response) => {
setUserInfo(response.data);
});
}
But the information never gets sent back, because req.session.userId is always undefined. I tried adding a console.log to output req.session and whenever I refresh the page (at which time the component calls getLoggedUser) the server image outputs req.session with a created time that is just a few seconds ago from the moment I refresh the page, meaning it gets created anew whenever I refresh. Is it that this is not saving properly because it's a get request and not a route? Please let me know if I may be missing something vital for this to work.

Related

Why I'm always getting an Internal Server Error (code 500) after making a request to BackEnd

I'm having a little trouble with my site and I can't understand what is happening.
First of all I have to say that I was NOT having this behavior when developing on localhost, but now that my site is close to be completed I think that uploading my code to a hosting service and make some tests there would be a good idea.
The issue is that when I make a request to the database, most of the times the site keeps in an eternal loading state, until the error code 500: Internal Server Error appears (I said "most of the times" because it works nice sometime, but normally it remains in a pending state).
Given the fact that SOME TIMES the request work nice, makes me think that the issue is not on the server.js file (where I defined the endpoints), and also is not on my controllers files (where I have some logic and the requests itself).
I'll leave here some pics as example of what is happening but if you need some extra info just tell me:
A simple login example, I just fill the fields and send the request
And here you can see how the request remain as pending
Until it fails
EDIT: I'm using package Mysql2 to connect to the DB, and I was reading that this behavior may be because a bad use of connections (and I'm reading about "pools", but I'm kinda lost tbh)
Here is the connection file:
require("dotenv").config();
const mysql = require("mysql2");
const db = mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
});
const connection = async () => {
db.connect((err) => {
if (err) throw err;
console.log("Successfully connected");
})
}
exports.db = db;
exports.connection = connection;
The first call to the DB (just to check the connection)
connection().then(() => {
app.listen(port, () => {
console.log(`Server running at ...`);
});
});
And the login logic
app.post("/dev-end/api/login", async (req, res) => {
await singleAccount(db, req.body.email)
.then(async (response) => {
if (response.code) {
res.render("templateLogin");
}
try {
if (await bcrypt.compare(req.body.password, response.password)) {
const user = { id: response._id, name: response.name };
await deleteTokenById(db, user.id.toString());
const accessToken = generateAccessToken(user);
const refreshToken = jwt.sign(
user,
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: "604800s" }
);
createToken(db, {
_id: user.id,
accessToken: accessToken,
refreshToken: refreshToken,
createdAt: new Date().toISOString().slice(0, 19).replace("T", " "),
}).then(
res
.cookie("access_token", accessToken, {
httpOnly: true,
maxAge: 60000 * 60 * 24 * 7,
})
.redirect("/dev-end/dashboard")
);
} else {
res.render("templateLogin");
}
} catch {
res.status(500).send();
}
})
.catch(console.log);
});
=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>
const singleAccount = async (conn, email) => {
return await read(conn).then((res) => {
if (!res.code) {
const result = res.find((e) => e.email.toString() === email);
if (!result) {
return {
code: 404,
msg: "No account was found with the provided id",
};
}
return result;
}
return res;
});
};
=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>
const read = async (conn) => {
const sql = `SELECT * FROM accounts`;
return await conn.promise().query(sql)
.then(([res, fields]) => res);
};

Can't Get Cookies in Browser (Sessions and Cookies with Express, MySql, Express MySql session)

I'm having trouble getting my cookies in the browser using Express, MySQL and Express-MySQL-Session. I made sure that my browsers are allowing cookies, I made sure that my store is connected properly. When I or anyone logs into the app I attach some user data to the session object. I can console log it and see it. It also shows in my remote MySQL DB.
Here is a sample of my code
// sessions
const session = require('express-session');
// mysql and its store
const mysql = require('mysql');
const MySQLStore = require('express-mysql-session')(session);
// create session store
const connection = mysql.createPool({
host: process.env.HOST,
port: 3306,
user: process.env.USER,
password: process.env.PASSWORD,
database: process.env.DATABASE,
createDatabaseTable: true,
});
const sessionStore = new MySQLStore({connectionLimit: 10}, connection);
// session
app.use(session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24
},
store: sessionStore
}));
// login
app.post('/api/v1/login', (req, res) => {
// get input from front
const { username, password } = req.body;
// find user
db.query(`SELECT * from users WHERE username = ?`, username, async (err, user) => {
if(err) throw err;
// user not found
if(!user) {
res.status(404).json({message: "User Not Found"})
} else {
// compare passwords
console.log(user)
const matchedPassword = await bcrypt.compare(password, user[0].hashPassword);
// password doesn't match
if(matchedPassword === false) {
res.json({message: "Bad Credentials", user, matchedPassword})
} else {
// user found
req.session.user = user[0].username;
console.log(req.session);
res.status(200).send({message: 'Success' , user})
}
}
})
});
// register
app.post('/api/v1/register', async (req, res, next) => {
// get input from user
const { username, password } = req.body;
try {
// if user already exists
db.query(`SELECT username FROM users WHERE username = ?`, username, (err, user) => {
if (err) res.status(400).json({message: 'Error Occurred'})
if (!user) res.status(400).json({message: 'User Not Found'});
});
// create user
const salt = await bcrypt.genSalt(2);
const hashedPassword = await bcrypt.hash(password, salt);
db.query(`INSERT INTO users (username, hashPassword) VALUES (?, ?)`, [username, hashedPassword], (err, user) => {
if (err) next(err);
res.status(200).json({message: 'User Created!', user});
})
} catch (err) {
next(err);
}
});
Every time I visit the page a new session is created in the DB despite the configuration of the session object.
UPDATE
I think my problem may have to do with my backend being hosted on Heroku, but the frontend being hosted on Netlify. When I make a request through postman I see that it returning a cookie with the Session ID, but thats it.
SOLVED
So, I looked into setting the domain attribute of my cookies to other domain where the frontend is hosted but apparently this isn't allowed. Makes sense. So I have to choose between hosting the whole site in one place, or forgetting about sessions and cookies for this small project.

Extracting information from reddit json and placing it in mysql database using node.js

I'm trying to extract JSON data from /r/askreddit and put it in a mysql database table called "post". The columns in the table are information such as the title of the post, url of the post, and the username of the poster.
I'm at a complete loss at this point on how to bring the data from the raw JSON into my table from the raw JSON, as I thought it should now be working.
Here is my .js server file, any help is appreciated. Thanks.
/*jshint esversion: 6 */
let mysql = require('mysql2');
let dbInfo = require('./dbInfo.js');
let express = require('express');
let bodyParser = require("body-parser");
let app = express();
// Add static route for non-Node.js pages
app.use(express.static('public'));
// Configure body parser for handling post operations
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.post('/reddit-import', function (req, res) {
console.log("Route for the /r/askreddit POST");
let sql = for (let i=0; i < x.data.children.length; i++) {
"insert into post (post_title, post_date, post_url, user_name) values (?,?,?,?)"
};
let data = [req.body.post_title, req.body.post_date, req.body.post_url, req.body.user_name];
connection.query(sql,
data,
function (errQuery, result) {
if (errQuery) {
console.log(errQuery);
res.json({status: "Error", err: errQuery});
} else {
console.log("Insert ID: ", result.insertId);
res.json({status: result.insertId, err: ""});
}
}
);
});
// Create database connection
console.log('Creating connection...\n');
let connection = mysql.createConnection({
host: dbInfo.dbHost,
port: dbInfo.dbPort,
user: dbInfo.dbUser,
password: dbInfo.dbPassword,
database: dbInfo.dbDatabase
});
// Connect to database
connection.connect(function(err) {
console.log('Connecting to database...\n');
// Handle any errors
if (err) {
console.log(err);
console.log('Exiting application...\n');
} else {
console.log('Connected to database...\n');
// Listen for connections
// Note: Will terminate with an error if database connection
// is closed
const ip = 'localhost';
const port = 8080;
app.listen(port, ip, function () {
try {
console.log('Alumni server app listening on port ' + port);
} catch (err) {
console.log(err);
}
});
}
});

How to create login endpoint using express-session and express-mysql-session

I want to create a secure login. I'd like to add session but I can't figure out how they should be used together.
I have 2 codes, one code came from express-mysql-session and another code which I wrote and has the login (/api/login) endpoint.
Below is the code which I copied from the readme.md of express-mysql-session and it works.
var express = require('express');
var app = module.exports = express();
var session = require('express-session');
var MySQLStore = require('express-mysql-session')(session);
var options = {
host: 'localhost',
port: 3306,
user: 'root',
password: 'password',
database: 'session_test'
};
var sessionStore = new MySQLStore(options);
app.use(session({
key: 'session_cookie_name',
secret: 'session_cookie_secret',
store: sessionStore,
resave: true,
saveUninitialized: true
}));
Here is the output on the terminal. The code above ran well but not really sure what it did. I see it has established connection to the locally running mysql using netstat command
tcp4 0 0 127.0.0.1.3306 127.0.0.1.52470 ESTABLISHED
tcp4 0 0 127.0.0.1.52470 127.0.0.1.3306 ESTABLISHED
then the output
$ DEBUG=express-mysql-session* node index.js
express-mysql-session:log Creating session store +0ms
express-mysql-session:log Setting default options +2ms
express-mysql-session:log Creating sessions database table +46ms
express-mysql-session:log Setting expiration interval: 900000ms +42ms
express-mysql-session:log Clearing expiration interval +0ms
Then below is the basic login auth endpoint I created using Express. This works but I want to add express-session, express-mysql-session as well as use crypt, bcrypt or scrypt-for-humans but not sure how to integrate it.
const express = require('express');
const bodyParser = require('body-parser');
const mysql = require('mysql');
const app = express();
app.use(bodyParser.json()); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
app.set('port', (process.env.API_PORT || 8000));
const connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'password',
database : 'authdb'
});
connection.connect(function(err) {
if (err) {
console.error('error connecting: ' + err.stack);
return;
}
console.log('connected as id ' + connection.threadId);
});
app.post('/api/login', function(req, res) {
const user_id = req.body.user_id;
const password = req.body.password;
let response = {};
res.setHeader('Content-Type', 'application/json');
connection.query('SELECT password from user WHERE `username` = "' + user_id + '"' , function(err, rows) {
if (err) throw err;
if (rows.length > 0) {
if (password === rows[0].password) {
response.status = 200;
response.message = "authenticated";
response.authenticated = true;
response.user_id = user_id;
} else {
response.status = 403;
response.message = "Login failed!";
response.authenticated = false;
response.user_id = user_id;
}
} else {
response.status = 403;
response.message = "Login failed!";
response.authenticated = false;
response.user_id = user_id;
}
res.status(response.status).send(JSON.stringify(response));
});
});
app.listen(app.get('port'), () => {
console.log(`Find the server at: http://localhost:${app.get('port')}/`);
});
I got it working and quite happy with the results. My login endpoint is working great! I now have more ideas on how to make it better as well. Here is the screenshot of the REST client - http://i.imgur.com/fJOvmzh.png and below is the endpoint
app.post('/api/login', function(req, res) {
const user_id = req.body.user_id;
const password = req.body.password;
let response = {};
res.setHeader('Content-Type', 'application/json');
connection.query('SELECT * FROM authdb.users as authusers inner join authdb.passwords as hashed on authusers.email = hashed.email WHERE authusers.email = "' + user_id + '"' , function(err, rows) {
if (err) throw err;
Promise.try(function(){
return scrypt.verifyHash(password, rows[0].password);
}).then(function(){
var sess = req.session;
if (sess.views) {
sess.views++;
} else {
sess.views = 1
}
response = { status: 200, message: "Login successful!", authenticated: true, user_id: user_id, views: sess.views }
res.status(response.status).send(JSON.stringify(response));
}).catch(scrypt.PasswordError, function(err){
response = { status: 403, message: "Login failed!", authenticated: false, user_id: user_id }
res.status(response.status).send(JSON.stringify(response));
});
});
});
To make it secure, I'll setup an EC2 behind an ELB which terminates all SSL connections and sends all traffic in clear to the NodeJS running my Express auth API spawned by PM2 or other better balancers. The AWS secgroup will only accept traffic whose source is the ELB.

Persistent Session in Nodejs using MySql

new to nodejs. this might be a silly/easy question
I have an Express App and i am using mysql for persistent sessions. (using express-mysql-session to do that).
Here's code snippet from app.js:
var express = require('express');
var session = require('express-session');
var SessionStore = require('express-mysql-session');
var app = express();
app.use(session({
store: new SessionStore({
host: 'localhost',
user: 'test',
password: 'test',
database: 'test'
}),
secret: 'secret_key',
resave: false,
saveUninitialized: false
}));
routes.js
module.exports = function(app) {
app.post('/login', wrap(function* (req, res) {
var email = req.body.email;
var password = req.body.password;
var response = yield new AccountController().login(email, password);
if (response.status === 'success') {
req.session.account = {
accountId: response.accountId,
accountStatus: response.accountStatus
};
req.session.save(function(err) {
if (err) console.log('error in saving session: ' + err);
});
}
}
}));
The get and set method of express-mysql-session are called everytime a request is sent.
I wanted to know how can i set my custom data into the persistent session store without using any other library like passport.
and also how to read the store too.