diff --git a/auth/authEmail.js b/auth/authEmail.js index a55b28e..c714672 100644 --- a/auth/authEmail.js +++ b/auth/authEmail.js @@ -152,15 +152,11 @@ const logout = async function (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; +function generatePassword(length = 12) { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+"; + return Array.from(crypto.randomFillSync(new Uint8Array(length))) + .map((x) => charset[x % charset.length]) + .join(""); } const resetPassword = async function (req, res) { diff --git a/index.js b/index.js index 1e4104c..97066ee 100644 --- a/index.js +++ b/index.js @@ -4,10 +4,47 @@ // Load enviroment keys for stripe, mongo, vimeo, email require('dotenv').config(); + +// Server Application using express const express = require('express'); -const bodyParser = require('body-parser'); -const cookieParser = require('cookie-parser'); +const app = express(); +const port = process.env.PORT || 3000; +// -- Accept request from other origins const cors = require('cors'); +const { corsOptions } = require('./config/corsOptions'); +app.use(cors(corsOptions)); +// -- Parse incoming requests with JSON payloads +const bodyParser = require('body-parser'); +app.use(bodyParser.json()); +// -- Parse incoming requests with urlencoded payloads +app.use(bodyParser.urlencoded({ extended: true })); +// -- Parse cookies +const cookieParser = require('cookie-parser'); +app.use(cookieParser()); + +// Authentication +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); + +// Routes +const profileRoute = require('./routes/profile.js'); +const postRoute = require('./routes/post.js'); +const songsRoute = require('./routes/songs.js'); +const paymentsRoute = require('./routes/payments.js'); +const bibleRoute = require('./routes/bible.js'); +const sessionChecker = require('./middleware/sessionChecker'); +// -- Private Routes +app.use('/user', sessionChecker, profileRoute); +app.use('/post', sessionChecker, postRoute); +app.use('/payments', sessionChecker, paymentsRoute); +app.use('/bible', sessionChecker, bibleRoute); +app.use('/songs', sessionChecker, songsRoute); +// -- Public Routes +const subsplashRoute = require('./routes/subsplash.js'); +app.use('/subsplash', subsplashRoute); // Web Push Notifications const webPush = require('web-push'); @@ -18,37 +55,11 @@ webPush.setVapidDetails('mailto:' + webPushEmail, publicVapidKey, privateVapidKe 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()); - +// Only bind the port if the DB connection is successful const DB = require("./mongoDB.js"); - -// Routes -const profileRoute = require('./routes/profile.js'); -const postRoute = require('./routes/post.js'); -const songsRoute = require('./routes/songs.js'); -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) => { console.log("Main logic: DB connected!"); @@ -59,6 +70,7 @@ DB.getDB.then((DB) => { userInfo: req.userInfo, profileInfo: req.profileInfo }); + // This should not happend, since the sessionChecker should redirect to login res.json({ status: "ok" }); }); @@ -69,17 +81,17 @@ DB.getDB.then((DB) => { // Validate email format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { - return res.status(400).json({ status: "error", message: "Provide a valid email" }); + return res.status(400).json({ status: "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" }); + return res.status(404).json({ status: "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" }); + return res.status(409).json({ status: "This user is already registered" }); } // Capture event in logging client_logger.capture({ @@ -90,35 +102,38 @@ DB.getDB.then((DB) => { 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" }); + return res.status(500).json({ status: "Internal server error" }); } }); // Change the active profile for the user. app.post('/changeProfile', sessionChecker, async (req, res) => { - const user_sid = getUserId(req); - let profile = await DB.getProfile(req.body.profileid); - if (!profile) { - return res.json({ - status: "profile does not exists", - }); + try { + const user_sid = getUserId(req); + const profileId = req.body.profileid; + // Validate input + if (!profileId) { + return res.status(400).json({ status: "Profile ID is required" }); + } + // Fetch profile from DB + const profile = await DB.getProfile(profileId); + if (!profile) { + return res.status(404).json({ status: "Profile does not exist" }); + } + // Check ownership + if (profile.userid !== user_sid) { + return res.status(403).json({ status: "Profile does not belong to the logged-in user" }); + } + // Update active profile cookie + res.cookie('profile_id', profile._id, cookiesOptions); + return res.json({ status: "ok", profile }); + } catch (error) { + console.error("Error changing profile:", error); + return res.status(500).json({ status: "Internal server error" }); } - let isUserOwnerOfTheProfile = profile.userid != user_sid; - if (isUserOwnerOfTheProfile) { - return res.json({ - status: "profile does not belong to the logged user", - }); - } - // Change the coockie for active profile, this will inform all future requests - res.cookie('profile_id', profile._id, cookiesOptions); - return res.json({ - status: "ok", - ...profile - }); }); - // This is the endpoint to refresh the push notification token app.post('/token/', sessionChecker, async (req, res) => { const profileid = getProfileId(req); @@ -137,16 +152,6 @@ DB.getDB.then((DB) => { if (profileid) await DB.setWebSubscription(profileid, subscription); }); - - //Private Routes - app.use('/user', sessionChecker, profileRoute); - app.use('/post', sessionChecker, postRoute); - app.use('/payments', sessionChecker, paymentsRoute); - app.use('/bible', sessionChecker, bibleRoute); - 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!")