How to get a connection to mongoDB to use the express session? - node.js

Previously I follow a YouTube tutorial to make a node-express app without session, I am now trying to include session into this app. But I cannot figure out what to put in the MongoStore.create()
index.js
const connectDB = require("./db/connect");
const authenticateUser = require("./middleware/authentication");
// routers
app.get("/", (req, res) => {
res.send("jobs api");
});
app.use("/api/v1/auth", authRouter);
app.use("/api/v1/jobs", authenticateUser, jobsRouter);
//const sessionStore = MongoStore.create({});
// app.use(
// session({
// secret: "some secret",
// resave: false,
// saveUninitialized: true,
// store: sessionStore,
// cookie: {
// maxAge: 1000 * 60 * 60 * 24,
// },
// })
// );
app.use(notFoundMiddleware);
app.use(errorHandlerMiddleware);
const port = process.env.PORT || 3000;
const start = async () => {
try {
await connectDB(process.env.MONGO_URI);
app.listen(port, () =>
console.log(`Server is listening on port ${port}...`)
);
} catch (error) {
console.log(error);
}
};
start();
connect.js
const mongoose = require("mongoose");
const connectDB = (url) => {
return mongoose.set("strictQuery", true).connect(url);
};
module.exports = connectDB;
I have tried something like conn = mongoose.connection.getClient() in the connect.js and export it to index.js as well, but the MongoStore.create({conn}) won't work.

I think it will help you.
https://stackoverflow.com/a/14060310/16251783
connect-mongo (https://github.com/kcbanner/connect-mongo) looks better then others.
express-session-mongo and connect-session-mongo are very old and based on old version of mongodb driver.
session-mongoose based on mongoose, that slower than mongodb driver.
I think connect-mongo is the best choose.

Related

req.cookies and req.signedcookies are empty

I'm storing cookies on my server, but when I tried to access them. Object Returns Null.
code I'm using to store my cookies. This is done when I'm logging in!
res.cookie("accessToken", accessToken, {
httpOnly: true,
secure: true,
expires: new Date(Date.now() + oneDay),
});
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: true,
expires: new Date(Date.now() + oneDay),
});
index.ts
const dotenv = require("dotenv");
dotenv.config();
const PORT = process.env.PORT || 3001;
const cookies = require("cookie-parser");
const express = require("express");
const app = express();
const cors = require("cors");
app.use(cors());
app.use(express.json());
app.use(cookies());
import dbConnect from "./db/connect";
import adminAuthRoter from "./routes/admin/adminAuthRouter";
app.get("/", (req: any, res: any) => {
res.send("Hello World");
});
app.use("/api/v1/adminauth", adminAuthRoter);
const start = async () => {
try {
await dbConnect(process.env.MONGODB_URI);
app.listen(PORT, () =>
console.log(`Server is listening on port ${PORT}...`)
);
} catch (error) {
console.log(error);
}
};
start();
When I tried to console.log(req.cookies) or console.log(req.signedCookies) my response is empty. But when I see my Postmon cookies there are cookies stored
Postmon Cookie Reponse Image
What may be the issue here?

con.connect is not a function: using Knex.js, express-session, pg and connect-pg-simple

I am making a site with express-session, connect-pg-simple, passport.js, Knex.js, pg and PostgreSQL and am currently experiencing what appears to be an issue with my session store or database connection when I start my server.
I can start my server but I also get this message in my console:
Failed to prune sessions: con.connect is not a function
I have no idea what this means as I don't have 'con.connect' anywhere in the code that I have written. I can't find any explanation or solution for this online, but I presume that it must be related to the connection between my database and the session store.
This is what my knexfile looks like:
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;
Here's my session file:
const path = require('path');
const dbCon = 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: dbCon }),
secret: theSecret,
resave: false,
saveUninitialized: false,
cookie: { maxAge: 1000 * 60 * 60 * 24 },
})
module.exports = session;
My db file:
const knex = require('knex');
const dbConfig = require('../../knexfile');
const db = knex(dbConfig);
module.exports = db;
And finally my initServer.js file:
const express = require('express');
const path = require('path');
const PORT = process.env.PORT || 5000;
const app = express();
const cors = require('cors');
const session = require('./db/session');
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
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}`)
});
There is no proper error message for this so I really don't know what's causing my session store or the connection to fail.
I would really appreciate your help with solving this issue, which doesn't seem to have much info online.
I'm not entirely sure what the precise cause of the problem was, but it must have been related to some sort of interaction between Knex.js and connect-pg-simple.
By uninstalling connect-pg-simple and replacing it with an alternative suited more specifically to Knex (namely connect-session-knex) I was able to get the session store to start up without any errors.

NodeJS/Express Server on Heroku, NextJS Client on Vercel, SocketIO Problem

I’ve got a problem with my NextJS + NodeJS + SocketIO setup and i wrap my head around it since days.
In development mode on my mac machine everything is fine but in production there is the problem.
The NodeJS server is hosted on Heroku and the NextJS client is hosted on Vercel.
Server:
require('dotenv').config()
// Packages
const express = require('express')
const mongoose = require('mongoose')
const passport = require('passport')
const cookie = require('cookie')
const jwtDecode = require('jwt-decode')
const cors = require('cors')
// App
const app = express()
// Models
const User = require('./models/User')
// App Settings
app.use(cors())
app.use(express.urlencoded({ limit: '10mb', extended: true }))
app.use(express.json({ limit: '10mb', extended: true }))
app.use(passport.initialize())
// App Routes
app.use('/_admin', require('./routes/_admin'))
app.use('/auth', require('./routes/auth'))
app.use('/profile', require('./routes/profile'))
app.use('/posts', require('./routes/posts'))
app.use('/comments', require('./routes/comments'))
app.use('/search', require('./routes/search'))
app.use('/users', require('./routes/users'))
require('./utils/passport')(passport)
const db = process.env.MONGO_URI
const port = process.env.PORT || 5000
mongoose
.connect(db, {
useNewUrlParser: true,
useFindAndModify: false,
useCreateIndex: true,
useUnifiedTopology: true
})
.then(() => {
console.log('MongoDB Connected') // eslint-disable-line no-console
const server = app.listen(port, () => console.log(`Server running on port ${port}`)) // eslint-disable-line no-console
const io = require('socket.io')(server)
io.on('connection', async socket => {
const decodedUser =
socket.handshake.headers.cookie && cookie.parse(socket.handshake.headers.cookie).jwtToken
? jwtDecode(cookie.parse(socket.handshake.headers.cookie).jwtToken)
: null
if (decodedUser) {
console.log(`${socket.id} -> ${decodedUser.username} -> connected`) // eslint-disable-line no-console
const user = await User.findById(decodedUser.id)
if (!user.sockets.includes(socket.id)) {
user.sockets.push(socket.id)
user.dateOnline = Date.now()
user.isOnline = true
user.save()
}
socket.on('disconnect', async () => {
console.log(`${socket.id} -> ${decodedUser.username} -> disconnected`) // eslint-disable-line no-console
const user = await User.findById(decodedUser.id)
const index = user.sockets.indexOf(socket.id)
user.sockets.splice(index, 1)
if (user.sockets.length < 1) {
user.isOnline = false
user.dateOffline = Date.now()
}
user.save()
})
} else {
console.log(`${socket.id} -> GUEST -> connected`) // eslint-disable-line no-console
socket.on('disconnect', async () => {
console.log(`${socket.id} -> GUEST -> disconnected`) // eslint-disable-line no-console
})
}
})
})
.catch(err => console.log(err)) // eslint-disable-line no-console
Client React Context:
import React, { createContext, useContext, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import io from 'socket.io-client'
const SocketContext = createContext()
export function useSocket() {
return useContext(SocketContext)
}
export function SocketContextProvider({ children }) {
const [socket, setSocket] = useState(null)
useEffect(() => {
setSocket(io(process.env.NOIZE_APP_SERVER_URL))
}, [])
const defaultContext = { socket }
return <SocketContext.Provider value={defaultContext}>{children}</SocketContext.Provider>
}
SocketContextProvider.propTypes = {
children: PropTypes.node
}
The hole React app is wrapped is this context-provider and as i said, on my localhost everything works fine.
The problem on the Heroku server is, that it is not receiving the cookie with my bearer jwt token from client in the SocketIO handshake. I’m lost right now and hope for help/hints/and so on…
Thank you very much =)
I solved the problem!
cookies.set('jwtToken', jwtToken, {
path: '/',
domain: process.env.NODE_ENV === 'development' ? 'localhost' : 'example.com'
})
The cookie needs the domain attribute in this case because the server runs on api.example.com and the client on www.example.com.

Openlitespeed Session Timeout after 1 min idle with Nextjs App

Hello Stackoverflow Community.
So I am encountering a very weird problem when hosting my nextjs powered by express with openlitespeed. Everything works great in production, except one thing - the authentification of sessions. The user is saved in the cookies correctly and it works if you are not idle for more than a minute on the page you are on, but if you are idle for more than a minute, then the request is not authenticated anymore even though the cookie is still there.
I am using redis for my cookie store, and everything works in local testing, where openlitespeed is not present. The authentification I am using is passportjs with express-session. Have any of you encountered this problem, and if so, how did you solve it?
I have tried disabling the cache module, set all timeouts to a higher value or disabling them, use different memorystores and more, but no luck. Here is the server.js file, however, I do not believe it has something to do with the code itself, but rather the config of openlitespeed:
const express = require('express')
const next = require('next')
const passport = require('passport');
const redis = require('redis')
const session = require('express-session')
const {v4: uuidv4} = require('uuid');
const path = require('path');
const log = require('./logger')
let RedisStore = require('connect-redis')(session)
let redisClient = redis.createClient()
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = express()
//Json parsing
server.use(express.json());
server.use(express.urlencoded({extended: true}));
if (dev){
//Express session
server.use(session({
store: new RedisStore({ client: redisClient }),
genid: function() {
return uuidv4()},
secret: uuidv4(),
resave: false,
saveUninitialized: false,
cookie: {
secure: false,
maxAge: 86400000
}
}))
}
else{
//Express session
server.use(session({
store: new RedisStore({ client: redisClient }),
genid: function() {
return uuidv4()},
secret: uuidv4(),
proxy: true,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
maxAge: 86400000
}
}))
}
//Passport auth
server.use(passport.initialize());
server.use(passport.session());
//Import of the passport config
const initializePassport = require('./passport-config');
initializePassport(passport);
//Login route
server.post('/login', passport.authenticate('login'), (req, res) => {
res.send({message: 'Successful login', login: true})
});
const passportLogout = function (req, res, next) {
req.logout()
next()
}
//Logout route
server.get('/logout', passportLogout, (req, res) => {
req.session.destroy();
res.redirect('/login');
});
//Import registrerings route. Pga. brugen af route i stedet for app kan vi bruge denne middleware med en anden underside, hvis vi f.eks. ville gøre så admins også kunne lave brugere.
const registerRoute = require('./routes/register-user');
server.use('/register', registerRoute);
//User routes hvor login er required. Rendering. Skal stå under called til initializepassport, ellers kan den ikke finde ud af at den er authenticated via passport, og auth.js returnerer dig derfor til login
const usersRoutes = require('./routes/user/user-routes');
server.use(usersRoutes);
//Admin routes til rendering
const adminRoutes = require('./routes/admin/admin-routes');
server.use(adminRoutes);
const indexRoutes = require('./routes/index-routes');
server.use(indexRoutes);
server.all('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
log.logger.log({
level: "info",
message: `Server was started on ${port}`,
additional: "properties",
are: "passed along",
});
console.log(`> Ready on http://localhost:${port}`)
})
})
All right, so I figured it out finally. The configuration for Openlitespeed was set, so that it could create as many httpd workers as it wants. Therefore, when a new was created and the requests went over to that one, it seems the authentification did not stick. I have fixed this by setting the "Number of Workers" to 1 under Server Configuration -> Server Process -> Number of Workers.
As for my server.js file I used to setup nextjs and openlitespeed:
const express = require("express");
const next = require("next");
const passport = require("passport");
const redis = require("redis");
const session = require("express-session");
const { v4: uuidv4 } = require("uuid");
const path = require("path");
const log = require("./logger");
let RedisStore = require("connect-redis")(session);
let redisClient = redis.createClient({ auth_pass: process.env.DB_PASSWORD });
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
//Json parsing
server.use(express.json());
server.use(express.urlencoded({ extended: true }));
if (dev) {
//Express session
server.use(
session({
store: new RedisStore({ client: redisClient }),
genid: function () {
return uuidv4();
},
secret: uuidv4(),
resave: false,
saveUninitialized: false,
cookie: {
secure: false,
maxAge: 86400000,
},
})
);
} else {
//Express session
server.use(
session({
store: new RedisStore({ client: redisClient }),
genid: function () {
return uuidv4();
},
secret: uuidv4(),
proxy: true,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
maxAge: 86400000,
},
})
);
}
//Passport auth
server.use(passport.initialize());
server.use(passport.session());
//Import of the passport config
const initializePassport = require("./passport-config");
initializePassport(passport);
//Login route
server.post("/login", passport.authenticate("login"), (req, res) => {
res.send({ message: "Successful login", login: true });
});
const passportLogout = function (req, res, next) {
req.logout();
next();
};
//Logout route
server.get("/logout", passportLogout, (req, res) => {
req.session.destroy();
res.redirect("/login");
});
//Import registrerings route. Pga. brugen af route i stedet for app kan vi bruge denne middleware med en anden underside, hvis vi f.eks. ville gøre så admins også kunne lave brugere.
const registerRoute = require("./routes/register-user");
server.use("/register", registerRoute);
//User routes hvor login er required. Rendering. Skal stå under called til initializepassport, ellers kan den ikke finde ud af at den er authenticated via passport, og auth.js returnerer dig derfor til login
const usersRoutes = require("./routes/user/user-routes");
server.use(usersRoutes);
//Admin routes til rendering
const adminRoutes = require("./routes/admin/admin-routes");
server.use(adminRoutes);
const indexRoutes = require("./routes/index-routes");
server.use(indexRoutes);
server.all("*", (req, res) => {
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready on ${port}`);
});
});

React intercepts /auth/twitter/when HTTPS. I want to access /auth/twitter/ from my node server not the react application

New to React/Node.
I have an implementation of React(React-boiler-plate with webpack etc)/Node running on the same host on Heroku.
I am using passport and twitter-oath sessions.
When I hit the endpoint http://example.heroku.com/auth/twitter/callback everything works accordingly (as well as running local dev server).
When I try to access it via HTTPS https://example.heroku.com/auth/twitter/callback React intercepts it and shows a page not found page.
I am trying to get an understanding to understand why this happens and the best way to handle this in a "production" like environment. I would like to be handle /auth/twitter and /auth/twitter/callback all on the same host.
I have tried adding http proxy in misc places as well as package.json and to no avail I am spinning my wheels.
Thank you in advance.
auth routes
module.exports = app => {
app.get('/api/logout', (req, res) => {
// Takes the cookie that contains the user ID and kills it - thats it
req.logout();
// res.redirect('/');
res.send(false);
// res.send({ response: 'logged out' });
});
app.get('/auth/twitter', passport.authenticate('twitter'));
app.get(
'/auth/twitter/callback',
passport.authenticate('twitter', {
failureRedirect: '/'
}),
(req, res) => {
res.redirect('/dashboard');
}
);
app.get('/api/current_user', (req, res) => {
// res.send(req.session);
// res.send({ response: req.user });
res.send(req.user);
});
};
index.js
app.use(morgan('combined'));
app.use(bodyParser.json());
app.use(
//
cookieSession({
// Before automatically expired - 30 days in MS
maxAge: 30 * 24 * 60 * 60 * 1000,
keys: [keys.COOKIE_KEY]
})
);
app.use(passport.initialize());
app.use(passport.session());
require('./routes/authRoutes')(app);
// They export a function - they turn into a function - then immediately call with express app object
app.use('/api/test', (req, res) => {
res.json({ test: 'test' });
});
setup(app, {
outputPath: resolve(process.cwd(), 'build'),
publicPath: '/',
});
// get the intended host and port number, use localhost and port 3000 if not provided
const customHost = argv.host || process.env.HOST;
const host = customHost || null; // Let http.Server use its default IPv6/4 host
const prettyHost = customHost || 'localhost';
/ Start your app.
app.listen(port, host, async err => {
if (err) {
return logger.error(err.message);
}
// Connect to ngrok in dev mode
if (ngrok) {
let url;
try {
url = await ngrok.connect(port);
} catch (e) {
return logger.error(e);
}
logger.appStarted(port, prettyHost, url);
} else {
logger.appStarted(port, prettyHost);
}
});
console.log('Server listening on:', port);
/**
* Front-end middleware
*/
module.exports = (app, options) => {
const isProd = process.env.NODE_ENV === 'production';
if (isProd) {
const addProdMiddlewares = require('./addProdMiddlewares');
addProdMiddlewares(app, options);
} else {
const webpackConfig = require('../../internals/webpack/webpack.dev.babel');
const addDevMiddlewares = require('./addDevMiddlewares');
addDevMiddlewares(app, webpackConfig);
}
return app;
};
const path = require('path');
const express = require('express');
const compression = require('compression');
module.exports = function addProdMiddlewares(app, options) {
// messing around here
const proxy = require('http-proxy-middleware');
const apiProxy = proxy('/api', { target: 'http://localhost:3000' });
const apiProxy2 = proxy('/auth', { target: 'http://localhost:3000' });
app.use('/api', apiProxy);
app.use('/auth/*', apiProxy2);
const publicPath = options.publicPath || '/';
const outputPath = options.outputPath || path.resolve(process.cwd(), 'build');
// compression middleware compresses your server responses which makes them
// smaller (applies also to assets). You can read more about that technique
// and other good practices on official Express.js docs http://mxs.is/googmy
app.use(compression());
app.use(publicPath, express.static(outputPath));
app.get('*', (req, res) =>
res.sendFile(path.resolve(outputPath, 'index.html')),
);
};
const path = require('path');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
function createWebpackMiddleware(compiler, publicPath) {
return webpackDevMiddleware(compiler, {
logLevel: 'warn',
publicPath,
silent: true,
stats: 'errors-only',
});
}
module.exports = function addDevMiddlewares(app, webpackConfig) {
const compiler = webpack(webpackConfig);
const middleware = createWebpackMiddleware(
compiler,
webpackConfig.output.publicPath,
);
app.use(middleware);
app.use(webpackHotMiddleware(compiler));
// Since webpackDevMiddleware uses memory-fs internally to store build
// artifacts, we use it instead
const fs = middleware.fileSystem;
app.get('*', (req, res) => {
fs.readFile(path.join(compiler.outputPath, 'index.html'), (err, file) => {
if (err) {
res.sendStatus(404);
} else {
res.send(file.toString());
}
});
});
};
Chances are you have a service worker that is running client side and intercepting the requests, then serving your react app from it's cache.
One hint that gives it away is that the service worker will only be installed / run over https https://developers.google.com/web/fundamentals/primers/service-workers/#you_need_https
Solution would be to either edit the service worker code to have it not serve for the auth urls or disable it all together if you are not planning to build an app around it, it may be more trouble than it is worth.

Resources