From 5b2a05bc795f3eb0ccdab6a5559bb8434a8b3609 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 9 Aug 2024 13:14:20 -0700 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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;