Store sessions in mongodb with connect-mongo and mongoose - node.js

I am getting errors left and right while I try to configure the express.js session storage with mongodb. I am using locomotive for my framework and have configured mongoose.
In my initializers directory for 02_mongoose.js I have this.
module.exports = function() {
mongoose.connect('mongodb://localhost:27018/basbac');
mongoose.model('User', UserSchema);
}
I know I have a connection to the database because I can pull my users in my controller.
DeveloperController.show = function() {
var self = this;
var user = mongoose.model('User');
user.find().exec(function(error, users) {
if(error) {
console.log(error);
} else {
self.res.json({response: { id: self.param('id'), api: self.param('api'), users: users } });
}
});
}
http://localhost:3000/developer/test/?api=hhfkgjhukdsfkjhvsduhvudhcsiudvlskejfbk
{
response: {
id: "test",
api: "hhfkgjhukdsfkjhvsduhvudhcsiudvlskejfbk",
users: [
{
_id: "52706695a43c83a739358de5",
firstname: "cad",
lastname: "bane",
address: "duro",
email: "cad#bane.com"
},
{
_id: "52706695a43c83a739358de6",
firstname: "jar jar",
lastname: "binks",
address: "naboo",
email: "jarjar#binks.com"
}
]
}
}
Inside my config/all.js I have this as my configuration for sessions
var MongoStore = require('connect-mongo')(express);
var mongoose = require('mongoose');
this.use(express.cookieParser());
this.use(express.session({
secret: 'keyboard cat',
store: new MongoStore({
mongoose_connection: mongoose.connection
})
}));
But this throws an error.
this.db = new mongo.Db(options.mongoose_connection.db.databaseName,
TypeError: Cannot read property 'databaseName' of undefined
I also tried to do it like the connect-mongo docs where saying but I get an error with that as well. (https://github.com/kcbanner/connect-mongo) mongoose_connection in the form: someMongooseDb.connections[0] to use an existing mongoose connection. (optional)
this.use(express.session({
secret: 'keyboard cat',
store: new MongoStore({
mongoose_connection: mongoose.connections[0]
})
}));
But I get the same error as before.
this.db = new mongo.Db(options.mongoose_connection.db.databaseName,
TypeError: Cannot read property 'databaseName' of undefined
I also tried to do as many articles are saying to do. Here is one for example of someones working configuration (Logout in ExpressJS, PassportJS and MongoStore)
this.use(express.session({
secret: 'keyboard cat',
store: new MongoStore({
db: mongoose.connection.db
})
}));
But that also produces an error, and I know that the db key is actually undefined
throw new Error('Required MongoStore option `db` missing');
What am I doing wrong to pass this connection into the new MongoStore? When I start console.log() the mongoose object I am not able to find any information about the connection it is using. I do see a base object but it does not have a db key inside it. Do I need to pass some more options into the mongoose configuration?

The problem is the order in which Locomotive starts up. According to the docs:
When a Locomotive application is started, it proceeds through a
sequence of steps:
Configure the Environment
In this step, config/environments/all.js is executed followed by the
configuration file for the current environment. For instance, when
running in development, config/environments/development.js is
executed.
Invoke Initializers
After the environment has been configured, initializers are invoked.
Initializers are used to configure sub-systems and connect to
databases, message queues, and other services utilized by the
application.
So when your environment code is being called, the initializer hasn't yet run and Mongoose isn't configured. Try moving the Mongoose setup to a separate file (I use app/db/mongoose myself, but that's a matter of personal preference) and require that in your environment file.

Related

Node, Postgres and Knex: SASL: SCRAM_SERVER_FIRST_MESSAGE: client password must be a string

I am creating a site using Node.js, React, Vite, Knex.js and PostgreSQL and have run into an error when trying to start up my server and connect to my database which I don't know how to solve. I have looked around elsewhere online, which also hasn't been much help. Here are what the relevant files look like:
server.js
const express = require('express');
const PATH = 5000;
const app = express();
const cors = require('cors');
const session = require('./db/session');
const { passport } = require('./passport');
app.use(cors({
origin: process.env.VITE_CORS_ORIGIN,
credentials: true
}));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(session);
app.use(passport.initialize());
app.use(passport.session());
app.use(require('./routes'));
app.use(function(req, res, next) {
res.status(404).send("Unable to find requested resource.")
});
app.use((err, req, res, next) => {
if (err) {
req.logout();
next();
}
res.status(err.status || 500).send(err.message);
});
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`)
});
knexfile.js
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '.env.development') });
const dbMode =
process.env.VITE_ENV === 'development' ? {
client: "pg",
connection: {
host: 'localhost',
port: 5432,
user: process.env.VITE_DB_USER,
password: process.env.VITE_DB_PASS,
database: process.env.VITE_DB_NAME,
charset: 'utf8'
},
migrations: {
directory: './server/db/migrations',
tableName: "knex_migrations"
},
seeds: {
directory: './server/db/seeds'
},
} : {
client: "pg",
connection: process.env.DATABASE_URL,
ssl: { require: true }
}
module.exports = dbMode;
db.js
const knex = require('knex');
const dbConfig = require('../../knexfile');
const db = knex(dbConfig);
module.exports = db;
I also have a session store set up using express-session and connect-pg-simple. I also use Passport.js.
Whenever I try start the server ('node initServer.js') I get the error message:
<project path>/node_modules/pg/lib/sasl.js:24
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string'
I have made sure that all my environment variables are working and are the right type. I have used console.log() to confirm that the variables aren't undefined and used typeof to confirm that the type of the environment variable for the DB password is a string.
I am using the same password details and postgreSQL installation as I used for another recent project, so I am sure that the password is not wrong and that all the details are correct.
I have no idea what I need to do to fix this as the password is (as far as I can tell) being passed correctly. I'd really appreciate your help.
If there's anything you'd like me to show or explain to help you solve this, please let me know.
Couple days I had this same issue running the knex migrate:list cli command. Checking your example, you are doing as the same way I had before. So testing changes and reading the knex documentation I reached to conclusion that:
Its seems like the knex-cli not reading the values from the
environment variables
If you still have the issue, these are the steps I follow to fixed it
1. What I did to be sure that the knex-cli was reading the environments variables, I add the client config as static string values. This will validate the reading variable issue.
2. Instead of using path lib and __dirname, I just used the relative path.
3. I tested the fix, running the knex migrate:list, and its work for me.
Maybe you others alternative of solution, but if you reach to fix the issue in ad different way please share it as a comment, so we can exchange knowledge and see a different way how to solve this issue.
Regards
I found a solution, though I am not entirely sure why this was necessary given that in projects I have done in the past this step was not required in order to connect to my database.
It turns out that the issue was connected to my session.js file. By adding the database connection object from my knexfile to express-session as follows, I was able to get around this error:
const path = require('path');
const connection = require('../../knexfile');
require('dotenv').config({ path: path.join(__dirname, '..', '..', '.env.development') });
const express_session = require('express-session');
const pgSession = require('connect-pg-simple')(express_session);
const theSecret = process.env.VITE_SESSION_SECRET;
const session = express_session({
store: new pgSession({ tableName: 'sessions', conObject: connection }),
secret: theSecret,
resave: false,
saveUninitialized: false,
cookie: { maxAge: 1000 * 60 * 60 * 24 }
})
module.exports = session;
Admittedly, I actually still have another issue despite doing this. Though I can now actually run my server, for some reason I also get the below message:
Failed to prune sessions: con.connect is not a function
I will make a separate question about this.

Mongoose and connect-mongo

I am using mongoose for managing relationships between data and I am trying to use connect-mongo to store specific sessions in the database.
It looks like that we need to connect twice to the db, one with mongoose and another one with connect-mongo.
I am using the following code to initialise a connection for mongoose
await mongoose.connect(this._connectionUrl, this._connectionOptions);
Initialising a new store every time (not sure if I am correct regarding code initialisation).
app.use(session({
// secret: config.sessionSecretKey,
secret: "secretkey",
resave: true,
saveUninitialized: true,
cookie: { maxAge: 19 * 60000 }, // store for 19 minutes
store: MongoStore.create({
mongoUrl: this._connectionUrl,
mongoOptions: this._connectionOptions // See below for details
})
}))
Is there any way that I can pass the connection from mongoose to mongo-connect Store?
i'm lookin for a solution too and just read this on the "migration guide" of connect-mongo
For the options, you should make the following changes:
Change url to mongoUrl Change collection to collectionName if you are
using it Keep clientPromise if you are using it mongooseConnection has
been removed. Please update your application code to use either
mongoUrl, client or clientPromise To reuse an existing mongoose
connection retreive the mongoDb driver from you mongoose connection
using Connection.prototype.getClient() and pass it to the store in the
client-option. Remove fallbackMemory option and if you are using it,
and there's this example https://github.com/jdesboeufs/connect-mongo/blob/master/example/mongoose.js
I've just been digging through the docs and through a few other SO responses. I've found this works really well with the new version of connect-mongo.
const session = require('express-session');
const MongoStore = require('connect-mongo');
app.use(
session({
secret: "secretkey",
resave: true,
saveUninitialized: true,
cookie: { maxAge: 19 * 60000 }, // store for 19 minutes
store: MongoStore.create({
client: mongoose.connection.getClient()
})
})
);
It is recommended by the devs for connect-mongo to utilise the connection object for mongoose to retrieve the client to ride the same connection so you don't have to setup two separate connections. This seems like a really clean way to do it but comment if you spot anything off!
This was pulled from the bottom of the connect-mongo migration guide here

How to set up mongoose and connect-mongo?

I had an Error setting TTL error when starting my application in express. Maybe the problem is because I use for sessions and for db operations the same database through different connections.
So it there a specific sequence of requiring connect-mongo and mongoose that needs to be respected if I want to store my sessions in mongodb via the connect-mongo middleware and use mongo as my database for my app specific data?
Currently my app looks like this:
App.app.use(express.session({
store: new MongoStore({
db: settings.cookie.db,
host: settings.cookie.host,
port: settings.cookie.port
}),
secret: settings.cookie.secret
}))
and later I set start the connection for mongo:
function connect(connectionString) {
mongoose.connect(connectionString)
var db = mongoose.connection
db.on('error', console.error.bind(console, 'connection error'))
db.once('open', function callbck() {
console.log('Mongoose connected at: ', connectionString)
})
}
There are no error logs apart
Also how do I tear down properly mongo connections when I close my app (from command line let's say)? For this question I found the answer here I think.
First of all i've created a sessionStore module
var mongoose = require('mongoose'),
express = require('express'),
MongoStore = require('connect-mongo')(express),
sessionStore = new MongoStore({mongoose_connection: mongoose.connection});
module.exports = sessionStore;
Then i've included it into app
sessionStore = require('libs/sessionStore');
And finaly
app.use(express.session({
secret: config.get('session:secret'),
key: config.get('session:key'),
cookie: config.get('session:cookie'),
store: sessionStore
}));
That's config
"session": {
"secret": "secret",
"key": "connect.sid",
"cookie": {
"path": "/",
"httpOnly": true,
"maxAge": null
}
},

Can't use mongo based session store

I have a mongoose and connect-mongo module instance in ym app. I am using the connect-mongo module to have a session store persisted in a mongodb database (mongohq) instead of a memory store.
Everytime I try to access my app when the server is launched (facebook auth with everyauth) I get the following:
500 MongoError: Error: unauthorized db:express-sessions lock type:-1
client:199.192.242.4
My user name, password are good.
var conf = {
db: {
db: 'express-sessions',
host: 'staff.mongohq.com',
port: 10072, // optional, default: 27017
username: 'admin', // optional
password: 'admin', // optional
collection: 'facebookSessions' // optional, default: sessions
},
secret: '076ee61d63aa10a125ea872411e433b9'
};
app.use(express.session({
secret: conf.secret,
maxAge: new Date(Date.now() + 3600000),
store: new MongoStore(conf.db)
}));
Edit, this seems to be an issue with my mongohq. I modified the collection for an older one and it works.
I was facing a similar error using Heroku and Mongolab.
I resolved it by manually create a new database user with the mongolab web admin.
It sounds like the db was started with --auth but the user has not been granted access to the db.
http://www.mongodb.org/display/DOCS/Security+and+Authentication

Node.js + express.js + passport.js : stay authenticated between server restart

I use passport.js to handle auth on my nodejs + express.js application. I setup a LocalStrategy to take users from mongodb
My problems is that users have to re-authenticate when I restart my node server. This is a problem as I am actively developing it and don't wan't to login at every restart... (+ I use node supervisor)
Here is my app setup :
app.configure(function(){
app.use('/static', express.static(__dirname + '/static'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({secret:'something'}));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
});
And session serializing setup :
passport.serializeUser(function(user, done) {
done(null, user.email);
});
passport.deserializeUser(function(email, done) {
User.findOne({email:email}, function(err, user) {
done(err, user);
});
});
I tried the solution given on a blog (removed the link as it does not exist any more) using connect-mongodb without success
app.use(express.session({
secret:'something else',
cookie: {maxAge: 60000 * 60 * 24 * 30}, // 30 days
store: MongoDBStore({
db: mongoose.connection.db
})
}));
EDIT additional problem : only one connection should be made (use of one connexion limited mongohq free service)
EDIT 2 solution (as an edition as I my reputation is to low to answer my question by now
Here is the solution I finally found, using mongoose initiated connection
app.use(express.session({
secret:'awesome unicorns',
maxAge: new Date(Date.now() + 3600000),
store: new MongoStore(
{db:mongoose.connection.db},
function(err){
console.log(err || 'connect-mongodb setup ok');
})
}));
There's an opensource called connect-mongo that does exactly what you need - persists the session data in mongodb
usage example (with a reuse of mongoose opened connection) :
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/sess');
app.use(express.session({
secret:'secret',
maxAge: new Date(Date.now() + 3600000),
store: new MongoStore(
// Following lines of code doesn't work
// with the connect-mongo version 1.2.1(2016-06-20).
// {db:mongoose.connection.db},
// function(err){
// console.log(err || 'connect-mongodb setup ok');
// }
{mongooseConnection:mongoose.connection}
)
}));
you can read more about it here: https://github.com/kcbanner/connect-mongo
i use connect-mongo like so:
var MongoStore = require('connect-mongo');
var sess_conf = {
db: {
db: mydb,
host: localhost,
collection: 'usersessions' // optional, default: sessions
},
secret: 'ioudrhgowiehgio'
};
app.use(express.session({
secret: sess_conf.secret,
maxAge: new Date(Date.now() + 3600000),
store: new MongoStore(sess_conf.db)
}));
[...]
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
This is because you use MemoryStore (default) for sessions. Look at this code from memory.js (part of Connect framework):
var MemoryStore = module.exports = function MemoryStore() {
this.sessions = {};
};
and this snippet from session.js (Express)
function session(options){
/* some code */
, store = options.store || new MemoryStore
/* some code */
}
Now you should understand that every server restart resets the MemoryStore. In order to keep the data you have to use some other session store. You can even write your own (shouldn't be too difficult), although Redis (see this library) might be a good choice (and it is well supported by Express).
// EDIT
According to the Connect documentation it is enough for you if you implement get, set and destroy methods. The following code should work:
customStore = {
get : function(sid, callback) {
// custom code, for example calling MongoDb
},
set : function(sid, session, callback) {
// custom code
},
destroy : function(sid, callback) {
// custom code
}
}
app.use(express.session({
store: customStore
}));
You just need to implement calling MongoDb (or any other Db although I still recommend using nonpermament one like Redis) for storing session data. Also read the source code of other implementations to grab the idea.
This is probably obvious to experienced node users but it caught me out:
You need to configure the node session - e.g.
app.use(session({secret: "this_is_secret", store: ...}));
before initializing the passport session - e.g.
app.use(passport.initialize());
app.use(passport.session());
If you call passport.session() first it won't work (and it won't warn you). I thought the problem was with the serialize/deserialize user functions and wasted hours.
I'm using mongoose, I tried the code presented in the answers above and it didn't work for me. I got this error when I did:
Error: db object already connecting, open cannot be called multiple times
However, this works for me:
app.use(express.session({
secret:'secret',
maxAge: new Date(Date.now() + 3600000),
store: new MongoStore({mongoose_connection:mongoose.connection})
}))
Note: If you don't have MongoStore for whatever reason, you need to do:
npm install connect-mongo --save
then:
var MongoStore = require('connect-mongo')(express)
What I ended up doing:
var expressSession = require('express-session');
var redisClient = require('redis').createClient();
var RedisStore = require('connect-redis')(expressSession);
...
app.use(expressSession({
resave: true,
saveUninitialized: true,
key: config.session.key,
secret: config.session.secret,
store: new RedisStore({
client: redisClient,
host: config.db.host,
port: config.db.port,
prefix: 'my-app_',
disableTTL: true
})
}));
Works for me.
You need to change the store you are using for your sessions. The default one 'MemoryStore' does not continue to store the session when you're application stops. Check out express-session on github to find out more about what other stores there are like the mongo one. (Can't remember the name)

Resources