From 8166245ab12ee76697766238c8a610bb42b45408 Mon Sep 17 00:00:00 2001 From: Joaquin Date: Tue, 13 May 2025 13:17:18 -0700 Subject: [PATCH 1/4] feature/basic functionality added --- src/public/scripts/geolocation.js | 71 +++++++++++++++++-------------- src/router/user.js | 16 +++++-- src/views/partials/header.ejs | 6 ++- 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index 7fce450..8dad938 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -5,38 +5,47 @@ function getLocation() { } function update(data) { - document.getElementById("loading").style = "display: none"; - document.getElementById("currencyExchange").style = "display: flex"; - - document.getElementById("yourFlag").src = `/static/svgs/flags/${data.data.country}.svg` - document.getElementById("yourFlag").alt = data.data.country; - document.getElementById("flagTag").innerHTML = `(${data.data.country})`; - - document.getElementById("exchangeFlag").src = `/static/svgs/flags/USD.svg`; - document.getElementById("exchangeFlag").alt = "USD"; - document.getElementById("exFlagTag").innerHTML = "(USD)"; + if (data.data.message != "error") { + document.getElementById("loading").style = "display: none"; + document.getElementById("currencyExchange").style = "display: flex"; - document.getElementById("dropdown-country-button").value = data.data.toCurrencyRates["USD"]; - - updateExchange(document.getElementById("dropdown-country-button").value); - - const dropdown = document.getElementById("dropdown"); - let countries = Object.keys(data.data.toCurrencyRates); - - countries.forEach(item => { - if (dropdown.querySelector(`button[value="${data.data.toCurrencyRates[item]}"]`)) { - return; - } - - const listItem = document.createElement("li"); - listItem.innerHTML = ``; - - dropdown.appendChild(listItem); - }); + document.getElementById("yourFlag").src = `/static/svgs/flags/${data.data.country}.svg` + document.getElementById("yourFlag").alt = data.data.country; + document.getElementById("flagTag").innerHTML = `(${data.data.country})`; + + document.getElementById("exchangeFlag").src = `/static/svgs/flags/USD.svg`; + document.getElementById("exchangeFlag").alt = "USD"; + document.getElementById("exFlagTag").innerHTML = "(USD)"; + + document.getElementById("dropdown-country-button").value = data.data.toCurrencyRates["USD"]; + + updateExchange(document.getElementById("dropdown-country-button").value); + + const dropdown = document.getElementById("dropdown"); + let countries = Object.keys(data.data.toCurrencyRates); + + countries.forEach(item => { + if (dropdown.querySelector(`button[value="${data.data.toCurrencyRates[item]}"]`)) { + return; + } + + const listItem = document.createElement("li"); + listItem.innerHTML = ``; + + dropdown.appendChild(listItem); + }); + } else { + document.getElementById("loading").style = "display: none"; + document.getElementById("exFrom").style = "display: none"; + document.getElementById("dropdown-country-button").style = "display: none"; + document.getElementById("currencyExchange").style = "display: flex"; + document.getElementById("exchange").innerHTML = "Country not supported"; + document.getElementById("exchange").className = "block rounded-lg text-center p-2.5 w-50 z-20 text-sm border bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:border-blue-500"; + } } function switchButton(clickedButton) { diff --git a/src/router/user.js b/src/router/user.js index e4511ad..3638396 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -544,11 +544,19 @@ module.exports = (middleware, users, plans, assets) => { country = data.results[0].formatted_address; let results = await getRates(country); - req.session.geoData = { - country: results.abbreviation, - toCurrencyRates: results.exRates, - geoData: req.session.geoData + + if (!results.exRates) { + req.session.geoData = { + message: "error" + } + } else { + req.session.geoData = { + country: results.abbreviation, + toCurrencyRates: results.exRates, + geoData: req.session.geoData + } } + } return res.status(status.Ok).send({ data: req.session.geoData }); diff --git a/src/views/partials/header.ejs b/src/views/partials/header.ejs index 90410ca..43747eb 100644 --- a/src/views/partials/header.ejs +++ b/src/views/partials/header.ejs @@ -17,12 +17,12 @@ + <% } else if(geoData.message == "error") { %> +

error

<% } else { %>
From 37712881237aded2aa6e1caaf0ccb6d4dad6c5cf Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Wed, 14 May 2025 09:34:32 -0700 Subject: [PATCH 2/4] fix/login+signup+logout session bugs --- app.js | 2 -- src/auth/authentication.js | 44 ++++++++++++++++++++++++++++++-------- src/auth/forgotPass.js | 5 +++-- src/auth/middleware.js | 42 ++++++++++++++++++++++++++++++------ src/router/user.js | 43 ++++++++++++++++++------------------- 5 files changed, 94 insertions(+), 42 deletions(-) diff --git a/app.js b/app.js index 56eb793..741673f 100644 --- a/app.js +++ b/app.js @@ -83,7 +83,6 @@ app.get('/forgotPassword', (req, res) => { return res.status(status.Ok); }); - // Reset with token given to user via email app.get('/reset/:token', async (req, res) => { const token = req.params.token; @@ -106,7 +105,6 @@ app.get('/reset/:token', async (req, res) => { }); }); - // 404 handler - keep the actual notFound route please // REALLY DONT DELETE THIS app.get('/notFound', (req, res) => { diff --git a/src/auth/authentication.js b/src/auth/authentication.js index d65e9c6..d32f0a8 100644 --- a/src/auth/authentication.js +++ b/src/auth/authentication.js @@ -12,10 +12,18 @@ module.exports = (users) => { const router = require("express").Router(); router.get("/logout", (req, res) => { - req.session.destroy(); - return res.redirect('/login'); + req.session.destroy((err) => { + if (err) { + console.error("Failed to destroy session: ", err); + res.status(status.InternalServerError); + return res.redirect("back"); + } + + res.status(status.Ok); + return res.redirect('/login'); + }); }); - + router.post("/login", async (req, res) => { const credentialSchema = joi.object({ @@ -46,10 +54,19 @@ module.exports = (users) => { req.session.authenticated = true; req.session.userId = user._id; - req.session.email = req.body.email; req.session.errMessage = ""; - res.redirect("/home"); - return res.status(status.Ok); + + req.session.save((err) => { + if (err) { + console.error("Failed to save session: ", err); + req.session.errMessage = "Failed to save session. Please try again."; + res.status(status.InternalServerError); + return res.redirect("/login"); + } + + res.status(status.Ok); + return res.redirect("/home"); + }); }); }); @@ -97,11 +114,20 @@ module.exports = (users) => { } req.session.authenticated = true; - req.session.email = req.body.email; req.session.userId = results.insertedId; - req.session.errMessage = ""; - return res.status(status.Ok).redirect("/home"); + + req.session.save((err) => { + if (err) { + console.error("Failed to save session: ", err); + req.session.errMessage = "Please login"; + res.status(status.Ok); + return res.redirect("/login"); + } + + res.status(status.Ok); + return res.redirect("/home"); + }); }); }); diff --git a/src/auth/forgotPass.js b/src/auth/forgotPass.js index 698a500..e0022c3 100644 --- a/src/auth/forgotPass.js +++ b/src/auth/forgotPass.js @@ -4,6 +4,7 @@ const joi = require('joi'); const nodeMail = require('nodemailer'); const bcrypt = require('bcrypt'); require('dotenv').config(); + const PORT = process.env.PORT; const transporter = nodeMail.createTransport({ @@ -13,7 +14,7 @@ const transporter = nodeMail.createTransport({ pass: process.env.EMAIL_PASS, } }); -// users info + module.exports = (users) => { const router = express.Router(); @@ -73,7 +74,7 @@ module.exports = (users) => { }); const valid = passwordSchema.validate({ password, confirmPassword }); if (valid.error) { - console.log("houston we have a problem"); + console.log("houston we have a problem"); // nice req.session.error = 'Invalid input'; res.status(status.BadRequest); return res.redirect(`/reset/${token}`); diff --git a/src/auth/middleware.js b/src/auth/middleware.js index 5c24807..b641877 100644 --- a/src/auth/middleware.js +++ b/src/auth/middleware.js @@ -1,5 +1,6 @@ const status = require("../util/statuses"); const session = require("express-session"); +const ObjectId = require("mongodb").ObjectId; // Get all names of user routes let userRouter = require("../router/user")((req, res, next) => next(), null, null, null); @@ -18,26 +19,53 @@ const createMiddleware = (users) => { return res.status(status.NotFound).redirect("/notFound"); } - if (!req.session.authenticated || !req.session.email) { + if (!req.session.authenticated || !req.session.userId) { req.session.errMessage = "Please login to view that resource"; res.redirect("/login"); return res.status(status.Unauthorized); } if (!req.session.user) { - let user = await users.findOne({ "email": req.session.email }).then((user) => user); + let user = await users.findOne({ _id: new ObjectId(req.session.userId) }).then((user) => user); if (!user) { - req.session.errMessage = "User not found"; - res.redirect("/login"); - return res.status(status.Unauthorized); + return req.session.destroy((err) => { + if (err) { + console.error("Failed to destroy session: ", err); + req.session.errMessage = "Failed to logout. Please try again"; + res.status(status.InternalServerError); + return res.redirect("/home"); + } + + req.session.errMessage = "User not found"; + res.status(status.NotFound); + res.redirect("/login"); + }); } - + req.session.user = user; + return req.session.save((err) => { + if (err) { + console.error("Failed to save session: ", err); + + return req.session.destroy((err) => { + req.session.errMessage = "An error occured, please login again."; + + if (err) { + console.error("Failed to destroy session: ", err); + } + + res.status(status.InternalServerError); + res.redirect("/login"); + }); + } + + next(); + }); } next(); }; -} +}; module.exports = createMiddleware; diff --git a/src/router/user.js b/src/router/user.js index 16c51f5..da956c2 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -86,7 +86,7 @@ module.exports = (middleware, users, plans, assets) => { }); router.get('/assets', async (req, res) => { - let userAssets = await assets.find({ userId: new ObjectId(req.session.user._id) }).toArray(); + let userAssets = await assets.find({ userId: new ObjectId(req.session.userId) }).toArray(); res.render('assets', { user: req.session.user, errMessage: req.session.errMessage, @@ -99,16 +99,16 @@ module.exports = (middleware, users, plans, assets) => { router.get('/plans', async (req, res) => { try { - const userPlansFromDB = await plans.find({ userId: new ObjectId(req.session.user._id) }).toArray(); + const userPlansFromDB = await plans.find({ userId: new ObjectId(req.session.userId) }).toArray(); // Use a for...of loop for proper async/await behavior in series for updates for (const plan of userPlansFromDB) { - const percentage = await calculatePlanProgress(plan, assets, req.session.user._id); + const percentage = await calculatePlanProgress(plan, assets, req.session.userId); await updatePlanProgressInDB(plan._id, percentage, plans); // Pass the 'plans' collection } // Re-fetch plans to get updated progress for rendering - const updatedUserPlans = await plans.find({ userId: new ObjectId(req.session.user._id) }).toArray(); + const updatedUserPlans = await plans.find({ userId: new ObjectId(req.session.userId) }).toArray(); res.render('plans', { user: req.session.user, @@ -126,18 +126,17 @@ module.exports = (middleware, users, plans, assets) => { try { const planId = req.params.id; - let userAssets = await assets.find({ userId: new ObjectId(req.session.user._id) }).toArray(); - + let userAssets = await assets.find({ userId: new ObjectId(req.session.userId) }).toArray(); if (!ObjectId.isValid(planId)) { req.session.errMessage = "Invalid plan ID format."; return res.status(status.BadRequest).redirect('/plans'); } - const plan = await plans.findOne({ userId: new ObjectId(req.session.user._id), _id: new ObjectId(planId) }); + const plan = await plans.findOne({ userId: new ObjectId(req.session.userId), _id: new ObjectId(planId) }); if (!plan) { - console.log(`Plan not found with ID: ${planId} for user: ${req.session.user.email}`); + console.log(`Plan not found with ID: ${planId} for user: ${req.session.userId}`); req.session.errMessage = "Plan not found or you do not have permission to view it."; return res.status(status.NotFound).redirect('/plans'); } @@ -197,7 +196,7 @@ module.exports = (middleware, users, plans, assets) => { return; } const newPlan = { - userId: new ObjectId(req.session.user._id), + userId: new ObjectId(req.session.userId), name: value.name, retirementAge: value.retirementAge, retirementExpenses: value.retirementExpenses, @@ -207,7 +206,7 @@ module.exports = (middleware, users, plans, assets) => { }; try { - await plans.insertOne({ userId: new ObjectId(req.session.user._id), ...newPlan }); + await plans.insertOne({ userId: new ObjectId(req.session.userId), ...newPlan }); req.session.errMessage = ""; res.redirect('/plans'); } @@ -280,7 +279,7 @@ module.exports = (middleware, users, plans, assets) => { } users.updateOne( - { _id: new ObjectId(req.session.user._id) }, + { _id: new ObjectId(req.session.userId) }, { $set: { financialData: true, @@ -295,13 +294,13 @@ module.exports = (middleware, users, plans, assets) => { } ).then((result) => { if (result.matchedCount === 0) { - console.log(`User not found during questionnaire update: ${req.session.user.email}`); + console.log(`User not found during questionnaire update: ${req.session.userId}`); req.session.errMessage = "User session invalid. Please log in again."; res.status(status.NotFound).redirect("/login"); return; } if (result.modifiedCount === 0 && result.matchedCount === 1) { - console.log(`User questionnaire data unchanged (already up-to-date): ${req.session.user.email}`); + console.log(`User questionnaire data unchanged (already up-to-date): ${req.session.userId}`); } req.session.user.financialData = true; @@ -333,7 +332,7 @@ module.exports = (middleware, users, plans, assets) => { if (valid.err) { req.session.errMessage = "Invalid input", - res.status(status.BadRequest); + res.status(status.BadRequest); return res.redirect("/profile"); } @@ -351,17 +350,17 @@ module.exports = (middleware, users, plans, assets) => { } users.updateOne( - { email: req.session.email }, + { _id: new ObjectId(req.session.userId) }, { $set: update } ).then((result) => { if (result.matchedCount === 0) { - console.log(`User not found during account update: ${req.session.email}`); + console.log(`User not found during account update: ${req.session.userId}`); req.session.errMessage = "User session invalid. Please log in again."; res.status(status.NotFound).redirect("/login"); return; } if (result.modifiedCount === 0 && result.matchedCount === 1) { - console.log(`User account data unchanged (already up-to-date): ${req.session.email}`); + console.log(`User account data unchanged (already up-to-date): ${req.session.userId}`); } req.session.errMessage = ""; @@ -393,7 +392,7 @@ module.exports = (middleware, users, plans, assets) => { } let newAsset = { - userId: new ObjectId(req.session.user._id), + userId: new ObjectId(req.session.userId), ...req.body, updatedAt: new Date(), }; @@ -437,7 +436,7 @@ module.exports = (middleware, users, plans, assets) => { return res.redirect("/assets"); } - if (req.body.userId != req.session.user._id) { + if (req.body.userId != req.session.userId) { req.session.errMessage = "Cannot change asset owner", res.status(status.BadRequest); return res.redirect("/assets"); @@ -507,20 +506,20 @@ module.exports = (middleware, users, plans, assets) => { router.post("/deleteUser", (req, res) => { // not as critical if results aren't as expected only if crashing - assets.deleteMany({ userId: new ObjectId(req.session.user._id) }).catch((err) => { + assets.deleteMany({ userId: new ObjectId(req.session.userId) }).catch((err) => { console.error("Error deleting user assets: ", err); req.session.errMessage = "An error occured while deleting your account. Please try again."; return res.status(status.InternalServerError).redirect("/profile"); }); - plans.deleteMany({ userId: new ObjectId(req.session.user._id) }).catch((err) => { + plans.deleteMany({ userId: new ObjectId(req.session.userId) }).catch((err) => { console.error("Error deleting user assets: ", err); req.session.errMessage = "An error occured while deleting your account. Please try again."; return res.status(status.InternalServerError).redirect("/profile"); }); users.deleteOne( - { _id: new ObjectId(req.session.user._id) }, + { _id: new ObjectId(req.session.userId) }, ).then((result) => { if (result.deletedCount === 0) { console.error(`User not found: ${req.body.id}`); From 5e2ac9421258a21e1bd43f106c8b2c7a5a8d5c6e Mon Sep 17 00:00:00 2001 From: Joaquin Date: Wed, 14 May 2025 09:37:09 -0700 Subject: [PATCH 3/4] fully implemented error handling for challenge 1 --- src/public/scripts/geolocation.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index 8dad938..7a96166 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -69,10 +69,18 @@ async function getLatestExchange(position) { let lon = position.coords.longitude; const res = await fetch(`/exRates/${lat}/${lon}`); const data = await res.json(); - + update(data); } function error(err) { - console.error("Geolocation error: ", err); + const data = { + data: { + message: "error" + } + } + + console.log(data); + + update(data); } \ No newline at end of file From c12bbe8336874635f6215dea9c28ee4c5689186a Mon Sep 17 00:00:00 2001 From: Joaquin Date: Wed, 14 May 2025 09:40:06 -0700 Subject: [PATCH 4/4] removed debugging code --- src/public/scripts/geolocation.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index 7a96166..94c9c66 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -80,7 +80,5 @@ function error(err) { } } - console.log(data); - update(data); } \ No newline at end of file