diff --git a/Client/src/Components/NavBar/index.jsx b/Client/src/Components/NavBar/index.jsx index 08eb91580..57699c97c 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,21 @@ function NavBar() { * Handles logging out the user * */ - const logout = () => { + const logout = async () => { // 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}`, + "Content-Type": "application/json", + }, + } + ); navigate("/login"); }; diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 02eb1bea4..65c925561 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; @@ -142,6 +146,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; @@ -157,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); @@ -524,6 +550,7 @@ const getAllUsersController = async (req, res) => { module.exports = { registerController, loginController, + logoutController, userEditController, inviteController, inviteVerifyController, 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; 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, 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 d04052607..40d7205e5 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", @@ -55,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",