From 5b2a05bc795f3eb0ccdab6a5559bb8434a8b3609 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 9 Aug 2024 13:14:20 -0700 Subject: [PATCH 01/18] Save JWT to user on login --- Server/controllers/authController.js | 2 ++ Server/models/user.js | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 02eb1bea4..988a74174 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -142,6 +142,8 @@ const loginController = async (req, res, next) => { // Happy path, return token const token = issueToken(userWithoutPassword); + user.authToken = token; + await user.save(); // reset avatar image userWithoutPassword.avatarImage = user.avatarImage; diff --git a/Server/models/user.js b/Server/models/user.js index 977cabc15..dee89b5f7 100644 --- a/Server/models/user.js +++ b/Server/models/user.js @@ -40,6 +40,9 @@ const UserSchema = mongoose.Schema( default: "user", enum: ["user", "admin"], }, + authToken: { + type: String, + }, }, { timestamps: true, From 9fe75c92dd466ff6c3b04a78bb7e8721f25e4986 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 9 Aug 2024 13:19:05 -0700 Subject: [PATCH 02/18] Add check for JWT on login --- Server/controllers/authController.js | 4 ++++ Server/utils/messages.js | 1 + 2 files changed, 5 insertions(+) diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 988a74174..7597416ec 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -135,6 +135,10 @@ const loginController = async (req, res, next) => { throw new Error(errorMessages.AUTH_INCORRECT_PASSWORD); } + if (user.authToken) { + throw new Error(errorMessages.AUTH_ALREADY_LOGGED_IN); + } + // Remove password from user object. Should this be abstracted to DB layer? const userWithoutPassword = { ...user._doc }; delete userWithoutPassword.password; diff --git a/Server/utils/messages.js b/Server/utils/messages.js index d04052607..590dd4eb1 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -7,6 +7,7 @@ const errorMessages = { UNAUTHORIZED: "Unauthorized access", AUTH_ADMIN_EXISTS: "Admin already exists", AUTH_INVITE_NOT_FOUND: "Invite not found", + AUTH_ALREADY_LOGGED_IN: "User already logged in", //Error handling middleware UNKNOWN_SERVICE: "Unknown service", From 68164b9362d3a358c795a01d87489a3d76cb4eb8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 9 Aug 2024 13:22:16 -0700 Subject: [PATCH 03/18] Add post request to logout --- Client/src/Components/NavBar/index.jsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Client/src/Components/NavBar/index.jsx b/Client/src/Components/NavBar/index.jsx index 08eb91580..2756f7ffd 100644 --- a/Client/src/Components/NavBar/index.jsx +++ b/Client/src/Components/NavBar/index.jsx @@ -21,6 +21,8 @@ import UserSvg from "../../assets/icons/user.svg?react"; import TeamSvg from "../../assets/icons/user-two.svg?react"; import LogoutSvg from "../../assets/icons/logout.svg?react"; import { Stack, useScrollTrigger } from "@mui/material"; +import axiosIntance from "../../Utils/axiosConfig"; +import axios from "axios"; const icons = { Profile: , @@ -93,10 +95,20 @@ function NavBar() { * Handles logging out the user * */ - const logout = () => { + const logout = async () => { // Clear auth state dispatch(clearAuthState()); dispatch(clearUptimeMonitorState()); + await axiosIntance.post( + "/auth/logout", + {}, + { + headers: { + Authorization: `Bearer ${authState.authToken}`, + "Content-Type": "application/json", + }, + } + ); navigate("/login"); }; From 9f4d23b1b40e5c36d5d0cb4ec9f2e944b0779afb Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 9 Aug 2024 13:33:13 -0700 Subject: [PATCH 04/18] Add logout route and controller, remove JWT from user on logout --- Client/src/Components/NavBar/index.jsx | 3 ++- Server/controllers/authController.js | 21 +++++++++++++++++++++ Server/routes/authRoute.js | 2 ++ Server/utils/messages.js | 1 + 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Client/src/Components/NavBar/index.jsx b/Client/src/Components/NavBar/index.jsx index 2756f7ffd..57699c97c 100644 --- a/Client/src/Components/NavBar/index.jsx +++ b/Client/src/Components/NavBar/index.jsx @@ -99,9 +99,10 @@ function NavBar() { // Clear auth state dispatch(clearAuthState()); dispatch(clearUptimeMonitorState()); + // Make request to BE to remove JWT from user await axiosIntance.post( "/auth/logout", - {}, + { user: authState.user }, { headers: { Authorization: `Bearer ${authState.authToken}`, diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 7597416ec..65c925561 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -163,6 +163,26 @@ const loginController = async (req, res, next) => { } }; +const logoutController = async (req, res, next) => { + try { + // Get user + const { user } = req.body; + const userToLogout = await req.db.getUserByEmail( + { body: { email: user.email } }, + res + ); + userToLogout.authToken = null; + await userToLogout.save(); + + return res + .status(200) + .json({ success: true, msg: successMessages.AUTH_LOGOUT_USER }); + } catch (error) { + error.service = SERVICE_NAME; + next(error); + } +}; + const userEditController = async (req, res, next) => { try { await editUserParamValidation.validateAsync(req.params); @@ -530,6 +550,7 @@ const getAllUsersController = async (req, res) => { module.exports = { registerController, loginController, + logoutController, userEditController, inviteController, inviteVerifyController, diff --git a/Server/routes/authRoute.js b/Server/routes/authRoute.js index b01e90294..224b4501f 100644 --- a/Server/routes/authRoute.js +++ b/Server/routes/authRoute.js @@ -9,6 +9,7 @@ const User = require("../models/user"); const { registerController, loginController, + logoutController, userEditController, recoveryRequestController, validateRecoveryTokenController, @@ -23,6 +24,7 @@ const { //Auth routes router.post("/register", upload.single("profileImage"), registerController); router.post("/login", loginController); +router.post("/logout", logoutController); router.put( "/user/:userId", upload.single("profileImage"), diff --git a/Server/utils/messages.js b/Server/utils/messages.js index 590dd4eb1..40d7205e5 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -56,6 +56,7 @@ const successMessages = { // Auth Controller AUTH_CREATE_USER: "User created successfully", AUTH_LOGIN_USER: "User logged in successfully", + AUTH_LOGOUT_USER: "User logged out successfully", AUTH_UPDATE_USER: "User updated successfully", AUTH_CREATE_RECOVERY_TOKEN: "Recovery token created successfully", AUTH_VERIFY_RECOVERY_TOKEN: "Recovery token verified successfully", From a2ff5d9b560a5411226c376cf15743b2109eb13f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 9 Aug 2024 13:53:10 -0700 Subject: [PATCH 05/18] Revoke token from user on JWT verification failure in middleware --- Server/db/mongo/modules/userModule.js | 1 - Server/middleware/verifyJWT.js | 29 ++++++++++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js index de7d54ebf..144295b6d 100644 --- a/Server/db/mongo/modules/userModule.js +++ b/Server/db/mongo/modules/userModule.js @@ -27,7 +27,6 @@ const insertUser = async (req, res) => { const avatar = await GenerateAvatarImage(req.file); userData.avatarImage = avatar; } - console.log(userData); const newUser = new UserModel(userData); await newUser.save(); return await UserModel.findOne({ _id: newUser._id }) diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index 1dbbe8d99..e6171bda3 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -3,6 +3,8 @@ const logger = require("../utils/logger"); const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; const { errorMessages } = require("../utils/messages"); +const { parse } = require("path"); +const User = require("../models/user"); /** * Verifies the JWT token * @function @@ -32,14 +34,27 @@ const verifyJWT = (req, res, next) => { const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); // Verify the token's authenticity - jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => { + jwt.verify(parsedToken, process.env.JWT_SECRET, async (err, decoded) => { if (err) { - logger.error(errorMessages.INVALID_AUTH_TOKEN, { - service: SERVICE_NAME, - }); - return res - .status(401) - .json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN }); + try { + const userId = jwt.decode(parsedToken)._id; + await User.updateOne({ _id: userId }, { $unset: { authToken: null } }); + logger.error(errorMessages.INVALID_AUTH_TOKEN, { + service: SERVICE_NAME, + }); + return res + .status(401) + .json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN }); + } catch (error) { + logger.error(errorMessages.UNKNOWN_ERROR, { + service: SERVICE_NAME, + error: error, + }); + error.status = 401; + error.service = SERVICE_NAME; + next(error); + return; + } } //Add the user to the request object for use in the route req.user = decoded; From 32bfc2fe0d397dd67d3921731766d46a257a3316 Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Fri, 9 Aug 2024 19:01:35 -0400 Subject: [PATCH 06/18] Update README.md --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index d3e822f94..d73109925 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,7 @@

An open source server monitoring application

- - -![bluewave-uptime-screenshot](https://github.com/user-attachments/assets/765bf060-e734-45e6-bd00-2a2eb37c9c02) - +![1](https://github.com/user-attachments/assets/207c74a0-a494-42c5-ab8d-3d7c1e7ead8a) BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time. From 5851787c1323e5d5ce1b5ffa3d4b05446b061f84 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 9 Aug 2024 23:17:17 -0700 Subject: [PATCH 07/18] Stop double rendering, add monitors to useEffect dependency array --- Client/src/Pages/Incidents/IncidentTable/index.jsx | 4 ++++ Client/src/Pages/Incidents/index.jsx | 1 + 2 files changed, 5 insertions(+) diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index d16c23733..18cb376ac 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -30,6 +30,9 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { useEffect(() => { const fetchPage = async () => { + if (!monitors || Object.keys(monitors).length === 0) { + return; + } try { let url = `/checks/${selectedMonitor}?sortOrder=desc&filter=${filter}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`; @@ -52,6 +55,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { }, [ authToken, user, + monitors, selectedMonitor, filter, paginationController.page, diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index 515f8ec0c..21061ffc7 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -47,6 +47,7 @@ const Incidents = () => { useEffect(() => { const fetchMonitors = async () => { + console.log("fetching monitors"); setLoading(true); const res = await axiosInstance.get( `/monitors/user/${authState.user._id}?status=false&limit=1`, From c599b7ce90535b0a076e0743e426fc4d17341c5b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 9 Aug 2024 23:19:53 -0700 Subject: [PATCH 08/18] removed console.log --- Client/src/Pages/Incidents/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index 21061ffc7..515f8ec0c 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -47,7 +47,6 @@ const Incidents = () => { useEffect(() => { const fetchMonitors = async () => { - console.log("fetching monitors"); setLoading(true); const res = await axiosInstance.get( `/monitors/user/${authState.user._id}?status=false&limit=1`, From 0cc7751d2e74ec63c9c4acf7ec66cf0c3c29e3ad Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 10 Aug 2024 10:20:06 -0700 Subject: [PATCH 09/18] Decoupled JWT from DB --- Client/src/Components/NavBar/index.jsx | 2 +- Server/controllers/authController.js | 7 ++----- Server/db/mongo/MongoDB.js | 2 ++ Server/db/mongo/modules/userModule.js | 10 ++++++++++ Server/middleware/verifyJWT.js | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Client/src/Components/NavBar/index.jsx b/Client/src/Components/NavBar/index.jsx index 57699c97c..95123d975 100644 --- a/Client/src/Components/NavBar/index.jsx +++ b/Client/src/Components/NavBar/index.jsx @@ -102,7 +102,7 @@ function NavBar() { // Make request to BE to remove JWT from user await axiosIntance.post( "/auth/logout", - { user: authState.user }, + { email: authState.user.email }, { headers: { Authorization: `Bearer ${authState.authToken}`, diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 65c925561..0f89e3371 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -166,11 +166,8 @@ const loginController = async (req, res, next) => { const logoutController = async (req, res, next) => { try { // Get user - const { user } = req.body; - const userToLogout = await req.db.getUserByEmail( - { body: { email: user.email } }, - res - ); + const { email } = req.body; + const userToLogout = await req.db.getUserByEmail({ body: { email } }, res); userToLogout.authToken = null; await userToLogout.save(); diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index ed1b9e4b1..fa84cd98a 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -37,6 +37,7 @@ const { updateUser, deleteUser, getAllUsers, + logoutUser, } = require("./modules/userModule"); //**************************************** @@ -133,6 +134,7 @@ module.exports = { updateUser, deleteUser, getAllUsers, + logoutUser, requestInviteToken, getInviteToken, requestRecoveryToken, diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js index 144295b6d..d626bcb9a 100644 --- a/Server/db/mongo/modules/userModule.js +++ b/Server/db/mongo/modules/userModule.js @@ -150,10 +150,20 @@ const getAllUsers = async (req, res) => { } }; +const logoutUser = async (userId) => { + try { + await UserModel.updateOne({ _id: userId }, { $unset: { authToken: null } }); + return true; + } catch (error) { + throw error; + } +}; + module.exports = { insertUser, getUserByEmail, updateUser, deleteUser, getAllUsers, + logoutUser, }; diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index e6171bda3..cfbf4b46c 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -38,7 +38,7 @@ const verifyJWT = (req, res, next) => { if (err) { try { const userId = jwt.decode(parsedToken)._id; - await User.updateOne({ _id: userId }, { $unset: { authToken: null } }); + await req.db.logoutUser(userId); logger.error(errorMessages.INVALID_AUTH_TOKEN, { service: SERVICE_NAME, }); From 573b9c2809b2388b9ac0851bf574d4745d1ac572 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 10 Aug 2024 10:38:14 -0700 Subject: [PATCH 10/18] Added pagination resets --- Client/src/Pages/Incidents/IncidentTable/index.jsx | 7 +++++++ .../src/Pages/Monitors/Details/PaginationTable/index.jsx | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index 18cb376ac..95230bb72 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -28,6 +28,13 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { rowsPerPage: 12, }); + useEffect(() => { + setPaginationController({ + ...paginationController, + page: 0, + }); + }, [filter, selectedMonitor]); + useEffect(() => { const fetchPage = async () => { if (!monitors || Object.keys(monitors).length === 0) { diff --git a/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx b/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx index 7f59705b1..28a668977 100644 --- a/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx +++ b/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx @@ -27,6 +27,13 @@ const PaginationTable = ({ monitorId, dateRange }) => { rowsPerPage: 5, }); + useEffect(() => { + setPaginationController({ + ...paginationController, + page: 0, + }); + }, [dateRange]); + useEffect(() => { const fetchPage = async () => { try { From ed78682a4c7717035032abeb3d18f0e2b0b616f8 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Sat, 10 Aug 2024 17:13:31 -0400 Subject: [PATCH 11/18] Initial landing register page design --- Client/src/Components/Button/index.jsx | 2 + Client/src/Pages/Auth/Login.jsx | 30 +++--- Client/src/Pages/Auth/Register/Register.jsx | 111 +++++++++++++++++--- Client/src/Pages/Auth/index.css | 65 +++--------- Client/src/assets/Images/bwl-logo-2.svg | 5 - 5 files changed, 132 insertions(+), 81 deletions(-) delete mode 100644 Client/src/assets/Images/bwl-logo-2.svg diff --git a/Client/src/Components/Button/index.jsx b/Client/src/Components/Button/index.jsx index bba101dc1..485973498 100644 --- a/Client/src/Components/Button/index.jsx +++ b/Client/src/Components/Button/index.jsx @@ -58,6 +58,7 @@ const Button = ({ disabled, img, onClick, + props, sx, }) => { const { variant, color } = levelConfig[level]; @@ -84,6 +85,7 @@ const Button = ({ }, ...sx, }} + {...props} > {img && img} {label} diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx index 2868b5d75..78b1d7957 100644 --- a/Client/src/Pages/Auth/Login.jsx +++ b/Client/src/Pages/Auth/Login.jsx @@ -117,6 +117,7 @@ const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => { mr: theme.gap.xs, }, }} + props={{ tabIndex: -1 }} />