From c136d2597413d835afb62a77e7a299c9d7d8f588 Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Fri, 20 Feb 2026 19:09:15 -0500 Subject: [PATCH 1/2] Harden feed/profile routes against invalid IDs and null profiles --- dbTools/profile.js | 15 +++++++------ middleware/sessionChecker.js | 27 +++++++++++++++++------- routes/post.js | 41 ++++++++++++++++++++++++------------ routes/profile.js | 26 +++++++++++++++++------ 4 files changed, 74 insertions(+), 35 deletions(-) diff --git a/dbTools/profile.js b/dbTools/profile.js index 356ded5..e1f3dcf 100644 --- a/dbTools/profile.js +++ b/dbTools/profile.js @@ -96,16 +96,18 @@ userDB = (DB) => { DB.getFriendsFriends = async (profileId, limit = 10) => { const profile = await DB.getProfile(profileId); if (!profile) return []; - let ids = profile.following.map((id) => DB.ObjectID(id)); + const following = Array.isArray(profile.following) ? profile.following : []; + let ids = following.filter((id) => DB.ObjectID.isValid(id)).map((id) => DB.ObjectID(id)); let alreadyFollowingMap = {}; alreadyFollowingMap[profileId] = 1; //skip that profile - profile.following.forEach(id => { + following.forEach(id => { if (!alreadyFollowingMap[id]) alreadyFollowingMap[id] = 1; }) return DB.profileCols.find({ _id: { $in: ids } }).project({ following: 1 }).limit(limit).toArray().then(profiles => { let friendsOfFriendsMap = {}; profiles.forEach(p => { - p.following.forEach(followingId => { + const related = Array.isArray(p.following) ? p.following : []; + related.forEach(followingId => { if (alreadyFollowingMap[followingId]) return 0; if (!friendsOfFriendsMap[followingId]) friendsOfFriendsMap[followingId] = 0; friendsOfFriendsMap[followingId] = friendsOfFriendsMap[followingId] + 1; @@ -312,9 +314,10 @@ userDB = (DB) => { DB.getFollowingGroups = async (profileid) => { const profile = await DB.getProfile(profileid); let ids = []; - for (id in profile.following) { + const following = Array.isArray(profile?.following) ? profile.following : []; + for (id in following) { try { - let oId = DB.ObjectID(profile.following[id]); + let oId = DB.ObjectID(following[id]); let checkProfile = await DB.getProfileCache(oId) if (checkProfile && checkProfile.isGroup && !checkProfile.isChat) { ids.push(oId) @@ -482,4 +485,4 @@ userDB = (DB) => { } -module.exports = userDB; \ No newline at end of file +module.exports = userDB; diff --git a/middleware/sessionChecker.js b/middleware/sessionChecker.js index dfdf823..2d753ec 100644 --- a/middleware/sessionChecker.js +++ b/middleware/sessionChecker.js @@ -2,19 +2,30 @@ const { getSessionId, getUserId, getProfileId } = require('../utils/sessionUtils const { client_logger } = require('../utils/analyticsLogger'); const { cookiesOptions } = require('../config/cookiesOptions'); const MongoDB = require("../mongoDB.js"); +const { ObjectId } = require("mongodb"); const sessionChecker = async (req, res, next) => { - const session_id = getSessionId(req); - const user_sid = getUserId(req); - let profile_id = getProfileId(req); + try { + const session_id = getSessionId(req); + const user_sid = getUserId(req); + let profile_id = getProfileId(req); - if (session_id && user_sid) { - DB = await MongoDB.getDB; + if (!session_id || !user_sid) { + return res.redirect('/login'); + } + if (!ObjectId.isValid(session_id) || !ObjectId.isValid(user_sid)) { + return res.redirect('/login'); + } + + const DB = await MongoDB.getDB; const userInfo = await DB.checkSessionOnDB(session_id, user_sid); req.userInfo = userInfo; if (!await DB.getProfileCache(profile_id)) { const latestProfile = await DB.latestProfile(user_sid); + if (!latestProfile || !latestProfile._id) { + return res.redirect('/login'); + } res.cookie('profile_id', latestProfile._id, cookiesOptions); profile_id = latestProfile._id; } @@ -23,16 +34,16 @@ const sessionChecker = async (req, res, next) => { if (!userInfo) return res.redirect('/login'); - // Log Request client_logger.capture({ distinctId: user_sid, event: 'server@' + req.method + '@' + req.originalUrl, }); next(); - } else { + } catch (error) { + console.error("Session checker error", error); return res.redirect('/login'); } }; -module.exports = sessionChecker; \ No newline at end of file +module.exports = sessionChecker; diff --git a/routes/post.js b/routes/post.js index eb9bec5..3aaa08a 100644 --- a/routes/post.js +++ b/routes/post.js @@ -8,7 +8,10 @@ const Notifications = require("./../notifications.js"); DB.getDB.then((DB) => { const getProfileId = (req) => { - return DB.ObjectID(req.cookies.profile_id || req.query.profile_id || req.body.profile_id); + const rawProfileId = req.cookies.profile_id || req.query.profile_id || req.body.profile_id || req.profileInfo?._id; + if (!rawProfileId) return null; + if (!DB.ObjectID.isValid(rawProfileId)) return null; + return DB.ObjectID(rawProfileId); }; const postBelongToProfile = (post, profileid) => { @@ -155,12 +158,17 @@ DB.getDB.then((DB) => { * $ref: '#/components/schemas/Post' */ router.get("/organic", async (req, res) => { - const profileid = getProfileId(req); - let organicPosts = await DB.getFeed(profileid); - //Add non-organic posts - const nonOrganicPosts = await generateNonOrganicPosts(req, profileid); - const posts = mergePosts(organicPosts, nonOrganicPosts); - return res.json(posts); + try { + const profileid = getProfileId(req); + if (!profileid) return res.status(400).json([]); + let organicPosts = await DB.getFeed(profileid); + const nonOrganicPosts = await generateNonOrganicPosts(req, profileid); + const posts = mergePosts(organicPosts || [], nonOrganicPosts || []); + return res.json(posts); + } catch (error) { + console.error("Error loading organic feed", error); + return res.status(500).json([]); + } }); /** @@ -182,12 +190,17 @@ DB.getDB.then((DB) => { * $ref: '#/components/schemas/Post' */ router.get("/", async (req, res) => { - const profileid = getProfileId(req); - //Add non-organic posts - const nonOrganicPosts = await generateNonOrganicPosts(req, profileid); - let promotionalPosts = await DB.getPromotionalPosts(profileid); - const posts = mergePosts(promotionalPosts, nonOrganicPosts); - return res.json(posts); + try { + const profileid = getProfileId(req); + if (!profileid) return res.status(400).json([]); + const nonOrganicPosts = await generateNonOrganicPosts(req, profileid); + let promotionalPosts = await DB.getPromotionalPosts(profileid); + const posts = mergePosts(promotionalPosts || [], nonOrganicPosts || []); + return res.json(posts); + } catch (error) { + console.error("Error loading feed", error); + return res.status(500).json([]); + } }); /** @@ -980,4 +993,4 @@ DB.getDB.then((DB) => { }); -module.exports = router \ No newline at end of file +module.exports = router diff --git a/routes/profile.js b/routes/profile.js index 88c95bc..8e65b00 100644 --- a/routes/profile.js +++ b/routes/profile.js @@ -795,12 +795,24 @@ DB.getDB.then((DB) => { * $ref: '#/components/schemas/Profile' */ router.get("/:id", async (req, res) => { - let profileId = req.params.id; - let profile = await DB.getProfile(profileId); - return res.json({ - status: "ok", - ...profile - }); + try { + let profileId = req.params.id; + let profile = await DB.getProfile(profileId); + if (!profile || !profile._id) { + return res.status(404).json({ + status: "Profile not found", + }); + } + return res.json({ + status: "ok", + ...profile + }); + } catch (error) { + console.error("Error loading profile", error); + return res.status(500).json({ + status: "Internal server error" + }); + } }); /** @@ -913,4 +925,4 @@ DB.getDB.then((DB) => { }); -module.exports = router \ No newline at end of file +module.exports = router From ea864b27d458a2d21ebcc1635798366da60f3df8 Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Fri, 20 Feb 2026 19:25:21 -0500 Subject: [PATCH 2/2] Harden local/prod cookie policy and Mongo connection settings --- config/cookiesOptions.js | 12 ++++++++---- config/corsOptions.js | 6 +++++- mongoDB.js | 23 ++++++++++++++++++++--- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/config/cookiesOptions.js b/config/cookiesOptions.js index ca36a00..958787d 100644 --- a/config/cookiesOptions.js +++ b/config/cookiesOptions.js @@ -1,8 +1,12 @@ +const isProduction = process.env.NODE_ENV === "production"; +const forceSecureCookie = process.env.COOKIE_SECURE === "true"; +const secure = forceSecureCookie || isProduction; + const cookiesOptions = { - maxAge: 1000 * 60 * 60 * 24 * 90, // would expire after 30 days + maxAge: 1000 * 60 * 60 * 24 * 90, // would expire after 90 days httpOnly: true, // The cookie only accessible by the web server - sameSite: 'none', // This and secure are required for properly - secure: true, // manage cockies in cros-domain + sameSite: secure ? 'none' : 'lax', + secure, }; -module.exports = { cookiesOptions }; \ No newline at end of file +module.exports = { cookiesOptions }; diff --git a/config/corsOptions.js b/config/corsOptions.js index 57ea14c..4d3f269 100644 --- a/config/corsOptions.js +++ b/config/corsOptions.js @@ -1,6 +1,10 @@ var corsOptions = { origin: [ 'http://localhost:8080', + 'http://localhost:8081', + 'http://127.0.0.1:3000', + 'http://127.0.0.1:8080', + 'http://127.0.0.1:8081', 'http://localhost:3000', "https://social.emmint.com", "https://fellowship.emmint.com", @@ -9,4 +13,4 @@ var corsOptions = { credentials: true }; -module.exports = { corsOptions }; \ No newline at end of file +module.exports = { corsOptions }; diff --git a/mongoDB.js b/mongoDB.js index 845ac46..f6711d0 100644 --- a/mongoDB.js +++ b/mongoDB.js @@ -11,15 +11,32 @@ const paymentDB = require("./dbTools/payments.js"); const songsDB = require("./dbTools/songs.js"); console.log("Connecting to MongoDB..."); +const nodeMajorVersion = parseInt((process.versions.node || "0").split(".")[0], 10); +if (nodeMajorVersion >= 22) { + console.warn("Warning: mongodb@3.x is not fully tested on Node.js 22+. Prefer Node.js 20 LTS for local stability."); +} + +const mongoConnectOptions = { + useNewUrlParser: true, + useUnifiedTopology: true, + serverSelectionTimeoutMS: 10000, + connectTimeoutMS: 10000, + socketTimeoutMS: 45000, + keepAlive: true, +}; const getDB = new Promise((resolve, reject) => { const DB = {ObjectID: mongo.ObjectID}; - MongoClient.connect(mongoUrl, function(err, db) { + MongoClient.connect(mongoUrl, mongoConnectOptions, function(err, db) { if (err) return reject(err); console.log("Connected to DB!"); DB.db = db; DB.ObjectID = ObjectID; + + DB.db.on("close", () => console.error("MongoDB connection closed")); + DB.db.on("reconnect", () => console.log("MongoDB reconnected")); + DB.db.on("error", (error) => console.error("MongoDB connection error", error)); DB.usersCol = db.db(DBName).collection("users"); DB.tokensCol = db.db(DBName).collection("tokens"); @@ -31,7 +48,7 @@ const getDB = new Promise((resolve, reject) => { const doc = await DB.tokensCol.findOne({"_id":temp_id}); if(doc && doc.uid == user_sid){ const userMongoId = new mongo.ObjectID(user_sid); - const userInfo = await DB.usersCol.findOne({"_id": userMongoId}, {fields: {password: 0}}); + const userInfo = await DB.usersCol.findOne({"_id": userMongoId}, {projection: {password: 0}}); return userInfo; } return false; @@ -126,4 +143,4 @@ const getDB = new Promise((resolve, reject) => { }); }); -exports.getDB = getDB; \ No newline at end of file +exports.getDB = getDB;