When on localhost this works, in production this error is triggered Unauthorized
Production is running on Digital Ocean App Platform
Nodejs
require("dotenv").config();
require("./lib/mongodb");
const express = require("express");
const socket = require("socket.io");
const logger = require("morgan");
const cors = require("cors");
const session = require("express-session");
const MongoStore = require("connect-mongo");
const chatSocket = require("./sockets/chat");
const passport = require("./routes/passport");
const sessionMiddleware = session({
resave: false,
saveUninitialized: false,
secret: process.env.MONGO_SECRET,
store: MongoStore.create({
mongoUrl: process.env.MONGO_URI,
ttl: 12 * 60 * 60,
}),
cookie: {
secure: process.env.NODE_ENV === "production",
domain: process.env.FRONTEND_URL,
path: "/",
httpOnly: true,
maxAge: 14 * 24 * 60 * 60 * 1000,
},
});
const app = express()
.use(sessionMiddleware)
.use(passport.initialize())
.use(passport.session())
.use(logger("dev"))
.use(
express.json({
verify: function (req, res, buf) {
var url = req.originalUrl;
if (url.startsWith("/webhooks/checkout/stripe")) {
req.rawBody = buf.toString();
}
},
})
)
.use(express.urlencoded({ extended: false }))
.use(cors())
.use("/api", require("./routes"))
.use("/webhooks", require("./webhooks"))
.listen(process.env.PORT || 8080);
const io = socket(app, {
cors: {
origin: process.env.FRONTEND_URL,
methods: ["GET", "POST"],
credentials: true,
},
});
const wrap = (middleware) => (socket, next) => {
return middleware(socket.request, {}, next);
};
io.use(wrap(sessionMiddleware));
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
io.use((socket, next) => {
if (socket.request.user) {
next();
} else {
next(new Error("Unauthorized"));
}
});
io.on("connection", (socket) => chatSocket(io, socket));
Nextjs/Reactjs
useEffect(() => {
if (socket === null && user) {
const socket = io("https://api.websidev.com", {
withCredentials: true,
transports: ["websocket", "polling", "flashsocket"],
});
setSocket(socket);
}
}, [socket, selected, project, user, messagesRef]);
Related
I have a semi successful deployment to heroku but the calls to the server are saying CONNECTION REFUSED and I can't figure it out.
I can hit the route and it returns correctly in postman but not in production
Below is the services file (the ones with /api in front are the ones being called)enter image description here
`import http from "../utils/http-common";
class CountriesService {
getAll() {
return http.get("/api/country");
}
getAllCountries() {
return http.get("/country/getAll");
}
getScroll(skip) {
return http.get(`/country?skip=${skip}`);
}
get(id) {
return http.get(`/country/${id}`);
}
create(countryForm) {
return http.post("/country/new", countryForm);
}
edit(id, values) {
return http.put(`/country/${id}`, values);
}
delete(id) {
return http.delete(`/country/${id}`);
}
}
export const getPostsPage = async (pageParam = 1, options = {}) => {
const response = await http.get(`/api/country?_page=${pageParam}`, options)
return response.data
}
export default new CountriesService();
`
This is the http-common
import axios from "axios";
var url;
process.env.REACT_APP_NODE_ENV === "production"
? (url = "")
: (url = "http://localhost:5000/");
export default axios.create({
baseURL: `${url}`,
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
});
this is the server file on the backend with a proxy from the frontend to localhost 5000
const path = require('path');
require("dotenv").config();
const express = require("express");
const app = express();
const mongoose = require("mongoose");
const cors = require("cors");
var morgan = require("morgan");
const MongoDBStore = require('connect-mongo');
const mongoSanitize = require('express-mongo-sanitize');
const helmet = require('helmet');
// Models
const User = require("./models/user.js");
// Passport Config
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const flash = require("connect-flash");
const session = require("express-session");
const cookieParser = require("cookie-parser");
// const { MongoClient } = require("mongodb");
const BodyParser = require("body-parser");
const { storage } = require("./imageupload/cloudinary");
const userAuthRoute = require("./routes/user.js");
const reviewRoute = require("./routes/review.js");
const countryRoute = require("./routes/country.js");
const cityRoute = require("./routes/city.js");
const activityRoute = require("./routes/activity.js");
const restaurantRoute = require("./routes/restaurant.js");
const dishesRoute = require('./routes/dishes.js');
const outdoorsRoute = require('./routes/outdoors.js');
const apiRoutes = require("./routes/api.js");
const cityReviewRoute = require("./routes/cityReviews.js");
const foodRoute = require('./routes/food.js');
const landmarkRoute = require('./routes/landmark.js');
const searchRoute = require('./routes/search.js');
const contactRoute = require('./routes/contact.js');
const db_url = process.env.DB_URL;
const PORT = process.env.PORT || 5000;
const secret = process.env.SESSION_SECRET;
// const client = new MongoClient(process.env.DB_URL);
const corsOptions = {
origin: [
"http://localhost:3000",
"http://localhost:5000",
"https://geopilot.herokuapp.com",],
credentials: true,
optionSuccessStatus:200,
};
app.use(cors(corsOptions));
app.use(express.json());
app.use(mongoSanitize({ replaceWith: "_" }));
app.use(BodyParser.json());
app.use(express.urlencoded({ extended: true }));
app.use(morgan("tiny"));
// app.use(cookieParser());
mongoose
.connect(db_url)
.then(() => {
console.log("database connection established successfully");
})
.catch((error) => console.log("this is the error", error));
const store = MongoDBStore.create({
mongoUrl: process.env.DB_URL,
ttl: 24 * 60 * 60 * 365, // 1 year
autoRemove: 'native',
crypto: {
secret,
},
});
store.on('error', function(error) {
console.log('SESSION STORE ERROR:', error);
});
// Session Settings
const sessionOptions = {
name: "geopilot_session",
secret: secret,
store: store,
resave: false,
saveUninitialized: false,
cookie: {
samesite: false,
// httpOnly: true,
// secure: true,
expires: Date.now() + 1000 * 60 * 60 * 24 * 365,
maxAge: 1000 * 60 * 60 * 24 * 365,
},
};
// app.set('trust proxy', 1) // trust first proxy
// Session Setup
app.use(session(sessionOptions));
// Helmet Setup
// app.use(helmet())
// Passport Middleware
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
// Routes
app.use("/api/country/:countryId/reviews", reviewRoute);
app.use("/api/country", countryRoute);
app.use('/api/city', cityRoute);
app.use('/api/city/:cityId/reviews', cityReviewRoute);
app.use('/api/activity', activityRoute);
app.use('/api/restaurant', restaurantRoute);
app.use('/api/restaurant/:restaurantId/reviews', reviewRoute);
app.use('/api/landmark', landmarkRoute);
app.use('/api/landmark/:landmarkId/reviews', reviewRoute);
app.use('/api/food', foodRoute);
app.use('/api/dishes', dishesRoute);
app.use('/api/outdoor', outdoorsRoute);
app.use('/api/search', searchRoute);
app.use('/api/user', userAuthRoute);
app.use('/api/contact', contactRoute);
// ----------- Deployment -----------
__dirname = path.resolve();
if (process.env.NODE_ENV === "production") {
app.use(express.static(path.join(__dirname, "../client/build")));
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "../client", "build", "index.html"));
});
}
// ----------- Deployment -----------
app.get("*", () => {
res.send("PAGE NOT FOUND");
});
I tried requesting in postman which works.
I tried changing things in the package.json to help proxy or run server.
I tried to switch endpoints and change CORS policy but it won't work.
For anyone seeing this and is stuck, I changed http-common baseUrl to match my website url (not localhost) and then had to do "npm run build" to make it actually implement the changes.
My express app:
const https = require("https");
const http = require("http");
const path = require("path");
const express = require("express");
const cookieParser = require("cookie-parser");
const dotenv = require("dotenv");
const cors = require("cors");
const helmet = require("helmet");
const logger = require("morgan");
const compression = require("compression");
const session = require("express-session");
const pgSession = require("connect-pg-simple")(session);
const api = require("../server/routes");
const pool = require("../config/database");
/** ====== DotENV configuration */
dotenv.config({ path: require("find-config")(".env") });
const configurations = {
production: { ssl: true, port: process.env.PORT, hostname: "" },
development: {
ssl: false,
port: process.env.PORT,
hostname: process.env.STAGE_HOSTNAME,
},
};
const environment = process.env.NODE_ENV || "development";
const config = configurations[environment];
const {
responseHandlerMiddleware,
sessionChecker,
} = require("../server/middlewares");
const app = express();
var credentials = {
// key: fs.readFileSync("/etc/letsencrypt/live/myresorts.in/privkey.pem"),
// cert: fs.readFileSync("/etc/letsencrypt/live/myresorts.in/fullchain.pem"),
};
let server = config.ssl
? https.createServer(credentials, app)
: http.createServer(app);
app.use(cors());
app.options("*", cors());
app.use(helmet());
app.use(compression());
app.use(logger("dev"));
app.use(express.json({ limit: "20mb" }));
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.set("trust proxy", 1);
app.use(
session({
store: new pgSession({
pool,
tableName: "session",
}),
name: "user_sid",
secret: process.env.SESSION_SECRET,
resave: true,
cookie: { secure: false, maxAge: 60000 * 5 },
saveUninitialized: true,
})
);
app.use((req, res, next) => {
if (req.session.user && req.cookies.user_sid) {
res.redirect("/dashboard");
}
return next();
});
app.get("/", sessionChecker, (req, res) => {
res.redirect("/login");
});
app.set("views", path.resolve(__dirname, "../views"));
app.set("view engine", "ejs");
app.use(express.static("../public"));
app.use("/", sessionChecker, api);
// app.use(responseHandlerMiddleware);
app.route("/login").get(sessionChecker, (req, res) => {
res.render("pages/login");
});
app.route("/register").get(sessionChecker, (req, res) => {
res.render("pages/register");
});
app.route("/dashboard").get(sessionChecker, (req, res) => {
if (req.session.user && req.cookies.user_sid) {
res.render("pages/dashboard");
} else {
res.redirect("/login");
}
});
module.exports = server;
Session handler middleware:
const sessionChecker = (req, res, next) => {
console.log("sesson ---------", req.session);
console.log("cookies ---------", JSON.stringify(req.cookies));
if (req.session.user && req.cookies.user_sid) {
res.redirect("/dashboard");
} else {
return next();
}
};
Every time I trying to to do a signup, this error keeps popping up multiple times:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:372:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (/home/deep/Node_API/node_modules/express/lib/response.js:794:10)
at ServerResponse.location (/home/deep/Node_API/node_modules/express/lib/response.js:915:15)
at ServerResponse.redirect (/home/deep/Node_API/node_modules/express/lib/response.js:953:18)
at sessionChecker (/home/deep/Node_API/server/middlewares/session.middleware.js:5:13)
at Layer.handle [as handle_request] (/home/deep/Node_API/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/home/deep/Node_API/node_modules/express/lib/router/index.js:328:13)
at /home/deep/Node_API/node_modules/express/lib/router/index.js:286:9
at Function.process_params (/home/deep/Node_API/node_modules/express/lib/router/index.js:346:12)
I'm trying to make a session management app using express-session. It should be able to preserve the session and if the session exists it should redirect to the dashboard else should redirect to the login page.
UPDATE:
As robert suggested in the comments, the issue was here:
app.use((req, res, next) => {
if (req.session.user && req.cookies.user_sid) {
res.redirect("/dashboard");
}
return next();
});
I changed it to:
else {
return next();
}
The error is fixed, but it is still not getting redirected to the dashboard after registering a user. Instead, gets called in the following manner multiple times:
GET /dashboard 302 246.426 ms - 64
GET /dashboard 302 247.005 ms - 64
GET /dashboard 302 245.620 ms - 64
GET /dashboard 302 247.940 ms - 64
GET /dashboard 302 246.114 ms - 64
I checked that this doesn't even get called:
app.route("/dashboard").get(sessionChecker, (req, res) => {
if (req.session.user && req.cookies.user_sid) {
res.render("pages/dashboard");
} else {
res.redirect("/login");
}
});
My registerUser controller:
const registerUser = catchAsync(async (req, res, next) => {
const { email, password } = req.body;
const user = await pool.query("select * from find_user_by_email($1)", [
email,
]);
if (user.rowCount) {
res.error = "User already exists";
return next(500);
}
const saltHash = generatePassword(password);
const response = await pool.query("select * from create_user($1, $2, $3)", [
email,
saltHash.salt,
saltHash.hash,
]);
if (response.rowCount) {
req.session.user = { email: response.rows[0].create_user };
res.redirect("/dashboard");
} else {
res.redirect("/register");
}
});
UPDATE(FIX):
It turns out redirects shouldn't be used when we are rendering template files. I replaced all res.redirect with res.render.
I've created a react app that runs on port:3000 and an express app that runs on port:3001.
I am using express-session and connect-mongo to handle user sessions. When I set a user session in /login it was recorded in MongoDB as expected. But when I query for req.session.user later in a different path/route, for example /channels it returns undefined.
This is how my app.js looks
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const {Server} = require("socket.io");
const io = new Server(server);
const port = process.env.PORT || 3001;
const cors = require("cors");
const path = require('path');
const session = require('express-session');
const bodyParser = require('body-parser');
const oneDay = 1000 * 60 * 60 * 24;
const MongoStore = require('connect-mongo');
const md5 = require('md5');
const hash = '=:>q(g,JhR`CK|acXbsDd*pR{/x7?~0o%?9|]AZW[p:VZ(hR%$A5ep ib.&BLo]g';
app.use(session({
secret: hash,
saveUninitialized: false,
resave: false,
store: MongoStore.create({
mongoUrl: 'mongodb://localhost:27017/chat',
ttl: 14 * 24 * 60 * 60 // = 14 days. Default
})
}));
app.use(
cors({
origin: true,
credentials: true,
optionsSuccessStatus: 200
}));
// create application/json parser
const jsonParser = bodyParser.json({limit: '50mb'})
// create application/x-www-form-urlencoded parser
const urlencodedParser = bodyParser.urlencoded({limit: '50mb', extended: false})
app.post('/login', jsonParser, (req, res) => {
db.users.find({email: req.body.email}).toArray().then(user => {
if (user.length < 1) {
res.send({success: false, error: 'NOT_FOUND', message: 'Invalid login info!'});
} else {
user = user[0];
if (user.password === req.body.password) {
db.users.updateOne({"email": user.email}, {$set: {"online": "1"}}).then(ret => {
req.session.user = user.email;
req.session.userdata = user;
res.json(<=user data=>);
});
}
}
})
});
app.post('/channels', async (req, res) => {
if (!req.session.user) {// THIS IS ALWAYS TRUE; EVEN AFTER SUCCESSFUL LOGIN
res.json({logout: true});
return;
}
const user = JSON.parse(req.session.userdata);
const channels = db.channels.find({contacts: {$all: [user._id]}}).toArray().then(channels => {
let allch = {};
channels.map(function (channel) {
channel.id = channel._id.toString();
channel.notif = 0;
allch[channel.id] = channel;
});
res.json(allch);
});
});
When You fetch from front-end for specific route, don't forget to include in options: "credentials: "include" ", like here:
const options = {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(searchInput),
credentials: "include",
};
fetch("http://localhost:4000/sendSearchInput", options)
.then((res) => res.json())
.then((data) => {
console.log(data);
});
Edit:
Note - This should be included in each request from the client that either sets or reads the 'express-session' middleware (req.session.x).
(Not just reads)
I think you will need to call req.session.save() yourself when you want to update the session store with the current data.
When I try to login which sets a cookie and then I refresh the page I don't get any response from apollo server and every requests made by graphql client are kept (pending) status.
After I remove a cookie, everything seems to work fine. I'm not even sure how can I debug this and have a little experience with backend so any advice would be helpful.
Here is how I setup connection from client:
const link = createHttpLink({
uri: 'http://localhost:5000/graphql',
credentials: 'include',
});
const apolloLink = ApolloLink.from([
errorLink,
link
]);
const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: apolloLink,
});
And server:
useContainer(Container);
const establishDatabaseConnection = async (): Promise<void> => {
try {
await createDatabaseConnection();
} catch (error) {
console.error(error);
}
};
const initExpressGraphql = async () => {
const app = express();
const redis = new Redis();
const RedisStore = connectRedis(session);
const corsOptions = {
origin: 'http://localhost:3000',
credentials: true,
};
const schema = await buildSchema({
resolvers: RESOLVERS,
container: Container,
});
const apolloServer = new ApolloServer({
schema: schema as GraphQLSchema,
context: ({ req, res }: any) => ({ req, res }),
introspection: true,
plugins: [
ApolloServerLoaderPlugin({
typeormGetConnection: getConnection, // for use with TypeORM
}),
],
});
app.use(
session({
store: new RedisStore({
client: redis as any,
}),
name: 'rds',
secret: 'verysecretdata',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 1000 * 60 * 60 * 24 * 7 * 365, // 7 years
sameSite: 'lax',
},
})
);
apolloServer.applyMiddleware({
app,
cors: corsOptions
})
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
};
const startServer = async (): Promise<void> => {
await establishDatabaseConnection();
initExpressGraphql();
};
startServer();
My issue got solved after I restarted redis server
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}`);
});
});