From 2434840c5071c801f10ea2f22469f90d8af20679 Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Wed, 26 Jun 2024 22:59:26 -0400 Subject: [PATCH] Improving comments --- index.js | 101 +++++++++++++++++++++++++++++++++-------------- notifications.js | 8 ++++ 2 files changed, 79 insertions(+), 30 deletions(-) diff --git a/index.js b/index.js index 29ec9a9..4e401d3 100644 --- a/index.js +++ b/index.js @@ -1,21 +1,28 @@ +// This is the backend main file for the Fellowship social app. +// It uses mongo as DB, and the connection url should be defined on the .env file +// Most of the API endpoints require authentication, which is checked by sessionChecker function. + +// 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; +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"); +const Notifications = require("./notifications"); // Custom Notification Methods const webPush = require('web-push'); +// 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', "https://social.emmint.com", "https://fellowship.emmint.com"], + origin: ['http://localhost:8080', "https://social.emmint.com", "https://fellowship.emmint.com"], credentials: true }; @@ -42,9 +49,11 @@ const getProfileId = function (req) { return profile_id; } -// Definitions +// 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'); const songsRoute = require('./routes/songs.js'); @@ -52,7 +61,7 @@ const paymentsRoute = require('./routes/payments.js'); const subsplashRoute = require('./routes/subsplash.js'); const bibleRoute = require('./routes/bible.js'); - +// Get DB.getDB.then((DB) => { app.use(DB.logger) @@ -87,6 +96,7 @@ 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({}); @@ -94,34 +104,42 @@ DB.getDB.then((DB) => { DB.setWebSubscription(profileid, subscription); }); - // route for user signup + // 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: "fail" }); - //Check if the new user has an invitation + // Check if the new user has an invitation. + // TODO: Alert admin of signup attempts via email. if (!await DB.getInvitation(email)) return res.json({ status: "Not invitation found!" }); - //const hashedPassword = await bcrypt.hash(password, 10); + // 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 response = await DB.newUser({ + const newUserObject = await DB.newUser({ username: username.toLowerCase(), email: email.toLowerCase(), password: hashedPassword }); - if (!response.toLowerCase) { + // If newUserObject it's an error message, we check by looking toLowerCase function + if (!newUserObject.toLowerCase) { let user = { - userid: response.insertedId, + userid: toLowerCase.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); + // TODO: this might fail, add catch scenarios return await login(req, res); } + // Error return return res.json({ status: response }) } - app.route('/signup').get(async (req, res) => { + 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); @@ -135,8 +153,9 @@ DB.getDB.then((DB) => { secure: true, // manage cockies in cros-domain }; - // route for user Login + // 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) { @@ -147,19 +166,22 @@ DB.getDB.then((DB) => { const password = req.body.password || req.query.password || ""; const user = await DB.getUser(username); if (!user) return res.json({ status: "user not founded" }); - const samePass = await bcrypt.compare(password, user.password); - if (!samePass) return res.json({ status: "incorrect password" }); - const doc = await DB.newSession(user._id); + // TODO: Also add salt parameter here. + const isSamePassword = await bcrypt.compare(password, user.password); + if (!isSamePassword) return res.json({ status: "incorrect password" }); + // 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', doc.insertedId, cookiesOptions); - //Chooses the most recent update profile - const latestProfile = await DB.latestProfile(user._id); + 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', latestProfile._id, cookiesOptions); return res.json({ status: "ok", user_sid: user._id, - session_id: doc.insertedId, - profile_id: latestProfile._id + session_id: sessionObj.insertedId, + profile_id: latestUpdatedProfile._id }); } app.route('/login').get(async (req, res) => { @@ -168,6 +190,8 @@ DB.getDB.then((DB) => { 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", @@ -177,29 +201,35 @@ DB.getDB.then((DB) => { } 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){ + 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!' + 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); - //We need to limit this to every 2 hours or something like this. Notifications.sendEmail(username, "Your new credentials", `

Hello,

@@ -210,16 +240,26 @@ DB.getDB.then((DB) => { `) return res.json({ status: "ok", - details: 'Check your email for new password' + details: 'Check your email for new password' // Enum of details? }); }); + // 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 || profile.userid != user_sid) return res.json({ - status: "profile does not belong to the logged user", - }); + if (!profile) { + return res.json({ + status: "profile does not exists", + }); + } + 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", @@ -227,6 +267,8 @@ DB.getDB.then((DB) => { }); }); + + // TODO: Not sure when is this used. Maybe for Notifs? app.post('/token/', sessionChecker, async (req, res) => { const profileid = getProfileId(req); let token = req.body.token @@ -263,7 +305,6 @@ DB.getDB.then((DB) => { //Public Routes app.use('/subsplash', subsplashRoute); - // route for handling 404 requests(unavailable routes) app.use(function (req, res, next) { diff --git a/notifications.js b/notifications.js index 3628dbf..7fedd3f 100644 --- a/notifications.js +++ b/notifications.js @@ -11,6 +11,7 @@ const sendWebNotification = async (subscription, title, body) => { webPush.sendNotification(subscription, payload); } +// Expo API to send push notifications, this code was a provided snipped const sendPushNotification = async (profileToken, body, data) => { if (!profileToken) return 0; let expo = new Expo(); @@ -94,6 +95,10 @@ const sendPushNotification = async (profileToken, body, data) => { })(); } + +// API to send emails from specific domain. +// TODO: Replace by a generic function, and specifics should be provided +// via enviroment variables. const sendEmail = async (to, subject, html) => { let transporter = nodemailer.createTransport({ host: "mail.emmint.com", @@ -114,6 +119,9 @@ const sendEmail = async (to, subject, html) => { if (info && info.messageId) console.log("Email sent: %s", info.messageId); }; +// Specific Notification templates +// TODO: This should be replaced by a system of tempaltes. + const yourBookmarkedPostGotACommentTemplate = (post, userEmail, postProfile, senderProfile, bookedProfile, message) => { let subject = senderProfile.profile.firstName + " commented on the post you follow"; let html = `