diff --git a/auth/authEmail.js b/auth/authEmail.js new file mode 100644 index 0000000..a55b28e --- /dev/null +++ b/auth/authEmail.js @@ -0,0 +1,222 @@ +const MongoDB = require("../mongoDB.js"); +const { client_logger } = require('../utils/analyticsLogger'); +const bcrypt = require('bcrypt'); +const crypto = require('crypto'); +const { getSessionId, getUserId, getProfileId } = require('../utils/sessionUtils.js'); +const { cookiesOptions } = require('../config/cookiesOptions'); +const Notifications = require("../notifications"); + +// Object Definitions +const Post = require("../def/post.js") +const Profile = require("../def/profile.js"); + +// Function to Singup new users. An user is a combination of a user obj and a profile. +// When new users are subscribed, they have a single profile, which is the personal one. +// Other profiles can be link to that user, like groups or courses. +const signup = async function (req, res) { + const username = req.query.username || req.body.username; + const password = req.query.password || req.body.password; + const email = req.query.email || req.body.email; + const profile = req.query.profile || req.body.profile; + if (!username || !password || !email) return res.json({ status: "Incomplete information!" }); + // Check if the new user has an invitation. + const DB = await MongoDB.getDB; + if (!await DB.getInvitation(email)) { + client_logger.capture({ + distinctId: 'app_level', + event: 'server@' + req.method + '@' + req.originalUrl + '@NotInvited', + properties: { + username: username, + email: email, + } + }); + return res.json({ status: "Not invitation found!" }); + } + let isUserAlreadyRegistered = await DB.getUser(email); + if (isUserAlreadyRegistered && isUserAlreadyRegistered._id) return res.json({ status: "This user is already registered" }); + // Hash password to be stored on the DB. + // TODO: I think this is missing a Salt factor to improve security + const hashedPassword = await bcrypt.hash(password, 10); + const newUserObject = await DB.newUser({ + username: username.toLowerCase(), + email: email.toLowerCase(), + password: hashedPassword + }); + // If newUserObject it's an error message, we check by looking toLowerCase function + if (!newUserObject.toLowerCase) { + let user = { + userid: newUserObject.insertedId, + profile: profile, + } + // Filter the provided information by the template, and adding to the DB, and login. + const userObj = new Profile(user); + await DB.newProfile(userObj); + client_logger.capture({ + distinctId: newUserObject.insertedId, + event: 'server@' + req.method + '@' + req.originalUrl, + }); + // TODO: this might fail, add catch scenarios + return await login(req, res); + } + // Error return + client_logger.capture({ + distinctId: 'app_level', + event: 'server@' + req.method + '@' + req.originalUrl + '@error', + properties: { + ustatus: newUserObject, + } + }); + return res.json({ status: newUserObject }) +} + +// Function for user Login, it returns the coockies/json for user_id session_id and profile_id +const login = async function (req, res) { + // Check if user is already logged in and redirect to root if so. + const session_id = getSessionId(req); + const user_sid = getUserId(req); + if (session_id && user_sid) { + const userInfo = await DB.checkSessionOnDB(session_id, user_sid); + if (userInfo) return res.redirect('/'); + } + const username = req.body.username || req.query.username; + const password = req.body.password || req.query.password || ""; + const DB = await MongoDB.getDB; + const user = await DB.getUser(username); + if (!user) { + client_logger.capture({ + distinctId: 'app_level', + event: 'server@' + req.method + '@' + req.originalUrl + '@userNotFound', + properties: { + username: username, + } + }); + return res.json({ status: "user not founded" }); + } + // TODO: Also add salt parameter here. + const isSamePassword = await bcrypt.compare(password, user.password); + if (!isSamePassword) return res.json({ status: "incorrect password" }); + try { + // Store a new session loging on DB, and use ID as session ID + const sessionObj = await DB.newSession(user._id); + // Create coockies with information for Auth + res.cookie('user_sid', user._id, cookiesOptions); + res.cookie('session_id', sessionObj.insertedId, cookiesOptions); + // Chooses the most recent update profile as current active profile + const latestUpdatedProfile = await DB.latestProfile(user._id); + res.cookie('profile_id', latestUpdatedProfile._id, cookiesOptions); + client_logger.identify({ + distinctId: user._id, + properties: { + name: latestUpdatedProfile.profile.firstName, + } + }); + client_logger.capture({ + distinctId: user._id, + event: 'server@' + req.method + '@' + req.originalUrl, + }); + return res.json({ + status: "ok", + user_sid: user._id, + session_id: sessionObj.insertedId, + profile_id: latestUpdatedProfile._id + }); + } catch (error) { + console.error(error); + client_logger.capture({ + distinctId: 'app_level', + event: 'server@' + req.method + '@' + req.originalUrl + '@error', + }); + return res.json({ status: "Error on this User Profile, please contact admin." }); + } + +} + +// route for user logout +const logout = async function (req, res) { + const session_id = getSessionId(req); + const user_sid = getUserId(req); + if (session_id && user_sid) { + res.clearCookie('session_id'); + res.clearCookie('user_sid'); + //remove from DB + const DB = await MongoDB.getDB; + DB.removeSession(session_id); + client_logger.capture({ + distinctId: user_sid, + event: 'server@' + req.method + '@' + req.originalUrl, + }); + res.redirect('/'); + } else { + res.redirect('/login'); + } +} + +// Util function for generating new random password for users. +// TODO: Probably we can do someting better here. +function generatePassword() { + var length = 8, + charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + retVal = ""; + for (var i = 0, n = charset.length; i < length; ++i) { + retVal += charset.charAt(Math.floor(Math.random() * n)); + } + return retVal; +} + +const resetPassword = async function (req, res) { + const session_id = getSessionId(req); + const user_sid = getUserId(req); + const DB = await MongoDB.getDB; + if (session_id && user_sid) { + // Sadly reusing this endpoint to change password to legged in users. + // TODO: Move change password logic to its own endpoint. + const userInfo = await DB.checkSessionOnDB(session_id, user_sid); + if (userInfo) { + const password = req.body.password; + const hashedPassword = await bcrypt.hash(password, 10); + // TODO: Add salt to password here as well. + DB.resetUserPassword(userInfo.username, hashedPassword); + return res.json({ + status: "ok", + details: 'password changed!' // This should be an enum that syncs with clients. + }); + } + } + // Logic for non-logged in users. + const username = req.body.username; + const user = await DB.getUser(username); + if (!user) return res.json({ status: "user not founded" }); + const password = generatePassword(); + const hashedPassword = await bcrypt.hash(password, 10); + // TODO: Add salt to password here as well. + // TODO: We need to limit this to every 2 hours or something like this. + // TODO: Move this template to the Notif file. + DB.resetUserPassword(username, hashedPassword); + Notifications.sendEmail(username, "Your new credentials", + ` +

Hello,

+

This is your new password: ${password}

+

Log in

+

Blessings

+

Emmanuel International Ministries

+`) + client_logger.capture({ + distinctId: user._id, + event: 'server@' + req.method + '@' + req.originalUrl, + properties: { + username: username, + } + }); + return res.json({ + status: "ok", + details: 'Check your email for new password' // Enum of details? + }); +} + + +module.exports = { + signup, + login, + logout, + resetPassword, +} \ No newline at end of file diff --git a/config/cookiesOptions.js b/config/cookiesOptions.js new file mode 100644 index 0000000..ca36a00 --- /dev/null +++ b/config/cookiesOptions.js @@ -0,0 +1,8 @@ +const cookiesOptions = { + maxAge: 1000 * 60 * 60 * 24 * 90, // would expire after 30 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 +}; + +module.exports = { cookiesOptions }; \ No newline at end of file diff --git a/config/corsOptions.js b/config/corsOptions.js new file mode 100644 index 0000000..57ea14c --- /dev/null +++ b/config/corsOptions.js @@ -0,0 +1,12 @@ +var corsOptions = { + origin: [ + 'http://localhost:8080', + 'http://localhost:3000', + "https://social.emmint.com", + "https://fellowship.emmint.com", + "https://aeropi.local", + ], + credentials: true +}; + +module.exports = { corsOptions }; \ No newline at end of file diff --git a/dbTools/logger.js b/dbTools/logger.js deleted file mode 100644 index 07e27e2..0000000 --- a/dbTools/logger.js +++ /dev/null @@ -1,103 +0,0 @@ -const hash = require('object-hash'); -const DBName = "EMI_SOCIAL"; - -postDB = (DB) => { - DB.pathLogs = DB.db.db(DBName).collection("pathLogs"); - DB.profileLogs = DB.db.db(DBName).collection("profileLogs"); - - let loggerPathData = {}; - let loggerProfileData = {}; - DB.logger = async (req, res, next) => { - // hash should not include query parameters - let hashIndx = hash(req.url.split('/')); - // Log url based - if (!loggerPathData[hashIndx]) { - loggerPathData[hashIndx] = { - url: req.url, - urlParts: req.url.split('/'), - count: 1 - } - } else { - loggerPathData[hashIndx].count++; - } - // Log user based - let profile_id = req.cookies?.profile_id; - if (!profile_id) return next(); - if (!loggerProfileData[profile_id]) { - loggerProfileData[profile_id] = {} - } - if (!loggerProfileData[profile_id][hashIndx]) { - loggerProfileData[profile_id][hashIndx] = { - url: req.url, - urlParts: req.url.split('/'), - count: 1 - } - } else { - loggerProfileData[profile_id][hashIndx].count++; - } - //console.log(loggerData); - next(); - }; - - // Update counters in DB - setInterval(async () => { - // Restart global counters - let localPathData = loggerPathData; - loggerPathData = {}; - let localProfileData = loggerProfileData; - loggerProfileData = {}; - return 0; - console.log('updating', localPathData); - Object.keys(localPathData).forEach(key => { - DB.pathLogs.findOne({ hash: key }).then(async (doc) => { - if (doc) { - //Update - const update = { - $inc: { - count: localPathData[key].count - } - }; - console.log('updating one ' + key) - await DB.pathLogs.updateOne({ hash: key }, update); - } else { - //Insert - const update = localPathData[key]; - update.hash = key; - console.log('inserting one ' + key) - await DB.pathLogs.insertOne(update); - } - }).catch(console.error); - }); - //DB.pathLogs. - Object.keys(localProfileData).forEach(key => { - DB.profileLogs.findOne({profile_id: key}).then(async (doc) => { - if (doc) { - //Update for user log - const update = { - $inc: { - //count: localPathData[key].count - } - }; - loggerProfileData[key].forEach((hashIndx)=>{ - //each hash has its counter. - update.$inc['hashIndx'] = { - count: loggerProfileData[key]['hashIndx'].count - }; - }); - console.log('updating one user' + key) - await DB.profileLogs.updateOne({ profile_id: key }, update); - } else { - //Insert - const update = loggerProfileData[key]; - update.profile_id = key; - console.log('inserting one user' + key) - await DB.profileLogs.insertOne(update); - } - }); - }); - - }, 1000 * 60 * 1); //Each 10 mins - -} - -module.exports = postDB; \ No newline at end of file diff --git a/index.js b/index.js index 8ac1fe3..1e4104c 100644 --- a/index.js +++ b/index.js @@ -5,66 +5,33 @@ // Load enviroment keys for stripe, mongo, vimeo, email require('dotenv').config(); const express = require('express'); -const path = require('path'); -const app = express(); -const port = process.env.PORT || 3000; // Setup to run on heroku const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); const cors = require('cors'); -const Notifications = require("./notifications"); // Custom Notification Methods + +// Web Push Notifications const webPush = require('web-push'); -const PostHog = require('posthog-node'); - -const client_logger = new PostHog.PostHog( - process.env.POSTHOG_API_KEY, - { host: 'https://us.i.posthog.com' } -) - -// TODO: we are probably missing a rate limiter here. - const publicVapidKey = process.env.PUBLIC_VAPID_KEY; const privateVapidKey = process.env.PRIVATE_VAPID_KEY; const webPushEmail = process.env.WEB_PUSH_EMAIL; webPush.setVapidDetails('mailto:' + webPushEmail, publicVapidKey, privateVapidKey); -var corsOptions = { - origin: [ - 'http://localhost:8080', - 'http://localhost:3000', - "https://social.emmint.com", - "https://fellowship.emmint.com", - "https://aeropi.local", - ], - credentials: true -}; +const { cookiesOptions } = require('./config/cookiesOptions'); +const { corsOptions } = require('./config/corsOptions'); +const { client_logger } = require('./utils/analyticsLogger.js'); +const { getSessionId, getUserId, getProfileId } = require('./utils/sessionUtils.js'); + +// Server Application +const app = express(); +const port = process.env.PORT || 3000; // Setup to run on heroku app.use(cors(corsOptions)); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cookieParser()); -const bcrypt = require('bcrypt'); -const crypto = require('crypto'); const DB = require("./mongoDB.js"); -// Utilities -const getSessionId = function (req) { - const session_id = req.cookies.session_id || req.query.session_id || req.body.session_id; - return session_id; -} -const getUserId = function (req) { - const user_sid = req.cookies.user_sid || req.query.user_sid || req.body.user_sid; - return user_sid; -} -const getProfileId = function (req) { - const profile_id = req.cookies.profile_id || req.query.profile_id || req.body.profile_id; - return profile_id; -} - -// Object Definitions -const Post = require("./def/post.js") -const Profile = require("./def/profile.js"); - // Routes const profileRoute = require('./routes/profile.js'); const postRoute = require('./routes/post.js'); @@ -73,35 +40,17 @@ const paymentsRoute = require('./routes/payments.js'); const subsplashRoute = require('./routes/subsplash.js'); const bibleRoute = require('./routes/bible.js'); +const sessionChecker = require('./middleware/sessionChecker'); + +const { signup, login, logout, resetPassword } = require('./auth/authEmail.js'); +app.route('/signup').get(signup).post(signup); +app.route('/login').get(login).post(login); +app.get('/logout', logout); +app.route('/resetPassword').post(resetPassword); + // Get DB.getDB.then((DB) => { - app.use(DB.logger) - - // middleware function to check for logged-in users - const sessionChecker = async (req, res, next) => { - const session_id = getSessionId(req); - const user_sid = getUserId(req); - let profile_id = getProfileId(req); - if (session_id && user_sid) { - 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); - res.cookie('profile_id', latestProfile._id, cookiesOptions); - profile_id = latestProfile._id; - } - req.profileInfo = { _id: profile_id } - if (!userInfo) return res.redirect('/login'); - // Log Reuquest - client_logger.capture({ - distinctId: user_sid, - event: 'server@'+req.method+'@'+req.originalUrl, - }); - next(); - } else { - return res.redirect('/login'); - } - }; + console.log("Main logic: DB connected!"); // route for Home-Page app.get('/', sessionChecker, async (req, res) => { @@ -113,227 +62,38 @@ DB.getDB.then((DB) => { res.json({ status: "ok" }); }); - // TODO: Not sure what is this for, and why it doesn't check for auth - app.post('/subscribe', (req, res) => { - const subscription = req.body; - res.status(201).json({}); - const profileid = getProfileId(req); - DB.setWebSubscription(profileid, subscription); - }); - + // Check for an invitation for an email app.get("/invite/:email", async (req, res) => { - const email = req.params.email; - //validate email? - if(!email) return res.json({status: "provide valid email"}); - let r = await DB.getInvitation(email); - if(!r) return res.json({status: "no invitation found with that email"}); - let isUserAlreadyRegistered = await DB.getUser(email); - if(isUserAlreadyRegistered && isUserAlreadyRegistered._id) return res.json({status: "This user is already registered"}); - client_logger.capture({ - distinctId: 'app_level', - event: 'server@'+req.method+'@'+req.originalUrl, - }); - return res.json({status: "ok", ... r}); - }); - - // Function to Singup new users. An user is a combination of a user obj and a profile. - // When new users are subscribed, they have a single profile, which is the personal one. - // Other profiles can be link to that user, like groups or courses. - const signup = async function (req, res) { - const username = req.query.username || req.body.username; - const password = req.query.password || req.body.password; - const email = req.query.email || req.body.email; - const profile = req.query.profile || req.body.profile; - if (!username || !password || !email) return res.json({ status: "Incomplete information!" }); - // Check if the new user has an invitation. - // TODO: Alert admin of signup attempts via email. - if (!await DB.getInvitation(email)){ - client_logger.capture({ - distinctId: 'app_level', - event: 'server@'+req.method+'@'+req.originalUrl+'@NotInvited', - properties: { - username: username, - email: email, - } - }); - return res.json({ status: "Not invitation found!" }); - } - let isUserAlreadyRegistered = await DB.getUser(email); - if(isUserAlreadyRegistered && isUserAlreadyRegistered._id) return res.json({status: "This user is already registered"}); - // Hash password to be stored on the DB. - // TODO: I think this is missing a Salt factor to improve security - const hashedPassword = await bcrypt.hash(password, 10); - const newUserObject = await DB.newUser({ - username: username.toLowerCase(), - email: email.toLowerCase(), - password: hashedPassword - }); - // If newUserObject it's an error message, we check by looking toLowerCase function - if (!newUserObject.toLowerCase) { - let user = { - userid: newUserObject.insertedId, - profile: profile, - } - // Filter the provided information by the template, and adding to the DB, and login. - const userObj = new Profile(user); - await DB.newProfile(userObj); - client_logger.capture({ - distinctId: newUserObject.insertedId, - event: 'server@'+req.method+'@'+req.originalUrl, - }); - // TODO: this might fail, add catch scenarios - return await login(req, res); - } - // Error return - client_logger.capture({ - distinctId: 'app_level', - event: 'server@'+req.method+'@'+req.originalUrl+'@error', - properties: { - ustatus: newUserObject, - } - }); - return res.json({ status: newUserObject }) - } - app.route('/signup').get(async (req, res) => { // Allow get and post to test on browser. - return await signup(req, res); - }).post(async (req, res) => { - return await signup(req, res); - }); - - const cookiesOptions = { - maxAge: 1000 * 60 * 60 * 24 * 90, // would expire after 30 days - httpOnly: true, // The cookie only accessible by the web server - //signed: true // Indicates if the cookie should be signed - sameSite: 'none', // This and secure are required for properly - secure: true, // manage cockies in cros-domain - }; - - // Function for user Login, it returns the coockies/json for user_id session_id and profile_id - const login = async function (req, res) { - // Check if user is already logged in and redirect to root if so. - const session_id = getSessionId(req); - const user_sid = getUserId(req); - if (session_id && user_sid) { - const userInfo = await DB.checkSessionOnDB(session_id, user_sid); - if (userInfo) return res.redirect('/'); - } - const username = req.body.username || req.query.username; - const password = req.body.password || req.query.password || ""; - const user = await DB.getUser(username); - if (!user){ - client_logger.capture({ - distinctId: 'app_level', - event: 'server@'+req.method+'@'+req.originalUrl+'@userNotFound', - properties: { - username: username, - } - }); - return res.json({ status: "user not founded" }); - } - // TODO: Also add salt parameter here. - const isSamePassword = await bcrypt.compare(password, user.password); - if (!isSamePassword) return res.json({ status: "incorrect password" }); try { - // Store a new session loging on DB, and use ID as session ID - const sessionObj = await DB.newSession(user._id); - // Create coockies with information for Auth - res.cookie('user_sid', user._id, cookiesOptions); - res.cookie('session_id', sessionObj.insertedId, cookiesOptions); - // Chooses the most recent update profile as current active profile - const latestUpdatedProfile = await DB.latestProfile(user._id); - res.cookie('profile_id', latestUpdatedProfile._id, cookiesOptions); - client_logger.identify({ - distinctId: user._id, - properties: { - name: latestUpdatedProfile.profile.firstName, - } - }); - client_logger.capture({ - distinctId: user._id, - event: 'server@'+req.method+'@'+req.originalUrl, - }); - return res.json({ - status: "ok", - user_sid: user._id, - session_id: sessionObj.insertedId, - profile_id: latestUpdatedProfile._id - }); - } catch (error) { - console.error(error); + const email = req.params.email.trim().toLowerCase(); + // Validate email format + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return res.status(400).json({ status: "error", message: "Provide a valid email" }); + } + // Check for an invitation + const invitation = await DB.getInvitation(email); + if (!invitation) { + return res.status(404).json({ status: "error", message: "No invitation found for this email" }); + } + // Check if the user is already registered + const existingUser = await DB.getUser(email); + if (existingUser?._id) { + return res.status(409).json({ status: "error", message: "This user is already registered" }); + } + // Capture event in logging client_logger.capture({ distinctId: 'app_level', - event: 'server@'+req.method+'@'+req.originalUrl+'@error', + event: `server@${req.method}@${req.originalUrl}`, }); - return res.json({ status: "Error on this User Profile, please contact admin." }); + // Respond with invitation details + return res.json({ status: "ok", invitation }); + } catch (error) { + console.error("Error processing invite check:", error); + return res.status(500).json({ status: "error", message: "Internal server error" }); } - - } - app.route('/login').get(async (req, res) => { - return await login(req, res); - }).post(async (req, res) => { - return await login(req, res); }); - // Util function for generating new random password for users. - // TODO: Probably we can do someting better here. - function generatePassword() { - var length = 8, - charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", - retVal = ""; - for (var i = 0, n = charset.length; i < length; ++i) { - retVal += charset.charAt(Math.floor(Math.random() * n)); - } - return retVal; - } - // Reset password will generate a new password and sent it as email to users - app.route('/resetPassword').post(async (req, res) => { - const session_id = getSessionId(req); - const user_sid = getUserId(req); - if (session_id && user_sid) { - // Sadly reusing this endpoint to change password to legged in users. - // TODO: Move change password logic to its own endpoint. - const userInfo = await DB.checkSessionOnDB(session_id, user_sid); - if (userInfo) { - const password = req.body.password; - const hashedPassword = await bcrypt.hash(password, 10); - // TODO: Add salt to password here as well. - DB.resetUserPassword(userInfo.username, hashedPassword); - return res.json({ - status: "ok", - details: 'password changed!' // This should be an enum that syncs with clients. - }); - } - } - // Logic for non-logged in users. - const username = req.body.username; - const user = await DB.getUser(username); - if (!user) return res.json({ status: "user not founded" }); - const password = generatePassword(); - const hashedPassword = await bcrypt.hash(password, 10); - // TODO: Add salt to password here as well. - // TODO: We need to limit this to every 2 hours or something like this. - // TODO: Move this template to the Notif file. - DB.resetUserPassword(username, hashedPassword); - Notifications.sendEmail(username, "Your new credentials", - ` -

Hello,

-

This is your new password: ${password}

-

Log in

-

Blessings

-

Emmanuel International Ministries

-`) - client_logger.capture({ - distinctId: user._id, - event: 'server@'+req.method+'@'+req.originalUrl, - properties: { - username: username, - } - }); - return res.json({ - status: "ok", - details: 'Check your email for new password' // Enum of details? - }); - }); // Change the active profile for the user. app.post('/changeProfile', sessionChecker, async (req, res) => { @@ -359,7 +119,7 @@ DB.getDB.then((DB) => { }); - // TODO: Not sure when is this used. Maybe for Notifs? + // This is the endpoint to refresh the push notification token app.post('/token/', sessionChecker, async (req, res) => { const profileid = getProfileId(req); let token = req.body.token @@ -369,28 +129,15 @@ DB.getDB.then((DB) => { }); }); - // route for user logout - const logout = function (req, res) { - const session_id = getSessionId(req); - const user_sid = getUserId(req); - if (session_id && user_sid) { - res.clearCookie('session_id'); - res.clearCookie('user_sid'); - //remove from DB - DB.removeSession(session_id); - client_logger.capture({ - distinctId: user_sid, - event: 'server@'+req.method+'@'+req.originalUrl, - }); - res.redirect('/'); - } else { - res.redirect('/login'); - } - } - app.get('/logout', (req, res) => { - return logout(req, res); + // Used for webpush notifications + app.post('/subscribe', sessionChecker, async (req, res) => { + const subscription = req.body; + res.status(201).json({}); + const profileid = getProfileId(req); + if (profileid) await DB.setWebSubscription(profileid, subscription); }); + //Private Routes app.use('/user', sessionChecker, profileRoute); app.use('/post', sessionChecker, postRoute); @@ -399,16 +146,18 @@ DB.getDB.then((DB) => { app.use('/songs', sessionChecker, songsRoute); //Public Routes app.use('/subsplash', subsplashRoute); + // route for handling 404 requests(unavailable routes) app.use(function (req, res, next) { res.status(404).send("Sorry can't find that!") }); app.listen(port, () => { - console.log(`Example app listening at http://localhost:${port}`); + console.log(`Fellowship app running at http://localhost:${port}`); }).on('error', (err) => { console.error('Server failed to start:', err); }); }).catch((err) => { + console.error('Error connecting to MongoDB:', err); throw err; }); diff --git a/middleware/sessionChecker.js b/middleware/sessionChecker.js new file mode 100644 index 0000000..dfdf823 --- /dev/null +++ b/middleware/sessionChecker.js @@ -0,0 +1,38 @@ +const { getSessionId, getUserId, getProfileId } = require('../utils/sessionUtils'); +const { client_logger } = require('../utils/analyticsLogger'); +const { cookiesOptions } = require('../config/cookiesOptions'); +const MongoDB = require("../mongoDB.js"); + +const sessionChecker = async (req, res, next) => { + const session_id = getSessionId(req); + const user_sid = getUserId(req); + let profile_id = getProfileId(req); + + if (session_id && user_sid) { + 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); + res.cookie('profile_id', latestProfile._id, cookiesOptions); + profile_id = latestProfile._id; + } + + req.profileInfo = { _id: profile_id }; + + if (!userInfo) return res.redirect('/login'); + + // Log Request + client_logger.capture({ + distinctId: user_sid, + event: 'server@' + req.method + '@' + req.originalUrl, + }); + + next(); + } else { + return res.redirect('/login'); + } +}; + +module.exports = sessionChecker; \ No newline at end of file diff --git a/mongoDB.js b/mongoDB.js index 1ae10c9..845ac46 100644 --- a/mongoDB.js +++ b/mongoDB.js @@ -3,22 +3,24 @@ const MongoClient = mongo.MongoClient; const ObjectID = mongo.ObjectID; const DBName = "EMI_SOCIAL"; const mongoUrl = process.env.MONGO_URL; + +// Extand DB with custom functions const postDB = require("./dbTools/post.js"); const profileDB = require("./dbTools/profile.js"); const paymentDB = require("./dbTools/payments.js"); -const loggerDB = require("./dbTools/logger.js"); const songsDB = require("./dbTools/songs.js"); - +console.log("Connecting to MongoDB..."); const getDB = new Promise((resolve, reject) => { const DB = {ObjectID: mongo.ObjectID}; MongoClient.connect(mongoUrl, function(err, db) { if (err) return reject(err); + console.log("Connected to DB!"); DB.db = db; DB.ObjectID = ObjectID; - console.log("Connected to DB!"); + DB.usersCol = db.db(DBName).collection("users"); DB.tokensCol = db.db(DBName).collection("tokens"); DB.invitationCol = db.db(DBName).collection("invitation"); @@ -118,7 +120,6 @@ const getDB = new Promise((resolve, reject) => { postDB(DB); profileDB(DB); paymentDB(DB); - loggerDB(DB); songsDB(DB); resolve(DB); diff --git a/routes/post.js b/routes/post.js index 9de789e..aa141b0 100644 --- a/routes/post.js +++ b/routes/post.js @@ -1,5 +1,5 @@ -var express = require('express') -var router = express.Router() +var express = require('express'); +var router = express.Router(); const DB = require("./../mongoDB.js"); const Post = require("./../def/post.js"); diff --git a/utils/analyticsLogger.js b/utils/analyticsLogger.js new file mode 100644 index 0000000..279a85c --- /dev/null +++ b/utils/analyticsLogger.js @@ -0,0 +1,8 @@ +const PostHog = require('posthog-node'); + +const client_logger = new PostHog.PostHog( + process.env.POSTHOG_API_KEY, + { host: 'https://us.i.posthog.com' } +) + +module.exports = { client_logger }; \ No newline at end of file diff --git a/utils/sessionUtils.js b/utils/sessionUtils.js new file mode 100644 index 0000000..d4d27b6 --- /dev/null +++ b/utils/sessionUtils.js @@ -0,0 +1,15 @@ +// Utilities +const getSessionId = function (req) { + const session_id = req.cookies.session_id || req.query.session_id || req.body.session_id; + return session_id; +} +const getUserId = function (req) { + const user_sid = req.cookies.user_sid || req.query.user_sid || req.body.user_sid; + return user_sid; +} +const getProfileId = function (req) { + const profile_id = req.cookies.profile_id || req.query.profile_id || req.body.profile_id; + return profile_id; +} + +module.exports = { getSessionId, getUserId, getProfileId }; \ No newline at end of file