diff --git a/public/src/components/ChatContainer.jsx b/public/src/components/ChatContainer.jsx index fbed7c007..77592e3fa 100644 --- a/public/src/components/ChatContainer.jsx +++ b/public/src/components/ChatContainer.jsx @@ -5,45 +5,43 @@ import Logout from "./Logout"; import { v4 as uuidv4 } from "uuid"; import axios from "axios"; import { sendMessageRoute, recieveMessageRoute } from "../utils/APIRoutes"; +import userById from "../utils/backendCall"; export default function ChatContainer({ currentChat, socket }) { const [messages, setMessages] = useState([]); const scrollRef = useRef(); const [arrivalMessage, setArrivalMessage] = useState(null); - useEffect(async () => { - const data = await JSON.parse( - localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY) - ); - const response = await axios.post(recieveMessageRoute, { - from: data._id, - to: currentChat._id, - }); - setMessages(response.data); + useEffect(() => { + async function fetchData() { + const user = await userById(); + const response = await axios.post(recieveMessageRoute, { + from: user._id, + to: currentChat._id, + }); + setMessages(response.data); + } + fetchData(); }, [currentChat]); useEffect(() => { const getCurrentChat = async () => { if (currentChat) { - await JSON.parse( - localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY) - )._id; + await userById()._id; } }; getCurrentChat(); }, [currentChat]); const handleSendMsg = async (msg) => { - const data = await JSON.parse( - localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY) - ); + const user = await userById(); socket.current.emit("send-msg", { to: currentChat._id, - from: data._id, + from: user._id, msg, }); await axios.post(sendMessageRoute, { - from: data._id, + from: user._id, to: currentChat._id, message: msg, }); diff --git a/public/src/components/Contacts.jsx b/public/src/components/Contacts.jsx index 583efbc2b..c9d638839 100644 --- a/public/src/components/Contacts.jsx +++ b/public/src/components/Contacts.jsx @@ -1,18 +1,22 @@ import React, { useState, useEffect } from "react"; import styled from "styled-components"; import Logo from "../assets/logo.svg"; +import userById from "../utils/backendCall"; export default function Contacts({ contacts, changeChat }) { const [currentUserName, setCurrentUserName] = useState(undefined); const [currentUserImage, setCurrentUserImage] = useState(undefined); const [currentSelected, setCurrentSelected] = useState(undefined); - useEffect(async () => { - const data = await JSON.parse( - localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY) - ); - setCurrentUserName(data.username); - setCurrentUserImage(data.avatarImage); + + useEffect(() => { + async function fetchData() { + const user = await userById(); + setCurrentUserName(user.username); + setCurrentUserImage(user.avatarImage); + } + fetchData(); }, []); + const changeCurrentChat = (index, contact) => { setCurrentSelected(index); changeChat(contact); diff --git a/public/src/components/Logout.jsx b/public/src/components/Logout.jsx index fe7371a99..35c3446ec 100644 --- a/public/src/components/Logout.jsx +++ b/public/src/components/Logout.jsx @@ -4,13 +4,18 @@ import { BiPowerOff } from "react-icons/bi"; import styled from "styled-components"; import axios from "axios"; import { logoutRoute } from "../utils/APIRoutes"; +import userById from "../utils/backendCall"; export default function Logout() { const navigate = useNavigate(); const handleClick = async () => { - const id = await JSON.parse( - localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY) - )._id; - const data = await axios.get(`${logoutRoute}/${id}`); + const user = await userById(); + const data = await axios.get(`${logoutRoute}/${user._id}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem( + process.env.REACT_APP_LOCALHOST_KEY + )}`, + }, + }); if (data.status === 200) { localStorage.clear(); navigate("/login"); diff --git a/public/src/components/SetAvatar.jsx b/public/src/components/SetAvatar.jsx index cf65a58bb..62780eb40 100644 --- a/public/src/components/SetAvatar.jsx +++ b/public/src/components/SetAvatar.jsx @@ -7,6 +7,7 @@ import { ToastContainer, toast } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import { useNavigate } from "react-router-dom"; import { setAvatarRoute } from "../utils/APIRoutes"; +import userById from "../utils/backendCall"; export default function SetAvatar() { const api = `https://api.multiavatar.com/4645646`; const navigate = useNavigate(); @@ -21,29 +22,40 @@ export default function SetAvatar() { theme: "dark", }; - useEffect(async () => { - if (!localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY)) + useEffect(() => { + if (!localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY)) { navigate("/login"); + } }, []); const setProfilePicture = async () => { if (selectedAvatar === undefined) { toast.error("Please select an avatar", toastOptions); } else { - const user = await JSON.parse( - localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY) + const user = await userById(); + const { data } = await axios.post( + `${setAvatarRoute}/${user._id}`, + { + image: avatars[selectedAvatar], + }, + { + headers: { + Authorization: `Bearer ${localStorage.getItem( + process.env.REACT_APP_LOCALHOST_KEY + )}`, + }, + } ); - const { data } = await axios.post(`${setAvatarRoute}/${user._id}`, { - image: avatars[selectedAvatar], - }); - if (data.isSet) { user.isAvatarImageSet = true; user.avatarImage = data.image; localStorage.setItem( - process.env.REACT_APP_LOCALHOST_KEY, - JSON.stringify(user) + "ImageInfo", + JSON.stringify({ + isAvatarSet: user.isAvatarImageSet, + avatarImage: user.avatarImage, + }) ); navigate("/"); } else { @@ -52,17 +64,20 @@ export default function SetAvatar() { } }; - useEffect(async () => { - const data = []; - for (let i = 0; i < 4; i++) { - const image = await axios.get( - `${api}/${Math.round(Math.random() * 1000)}` - ); - const buffer = new Buffer(image.data); - data.push(buffer.toString("base64")); + useEffect(() => { + async function fetchData() { + const data = []; + for (let i = 0; i < 4; i++) { + const image = await axios.get( + `${api}/${Math.round(Math.random() * 1000)}` + ); + const buffer = new Buffer(image.data); + data.push(buffer.toString("base64")); + } + setAvatars(data); + setIsLoading(false); } - setAvatars(data); - setIsLoading(false); + fetchData(); }, []); return ( <> diff --git a/public/src/components/Welcome.jsx b/public/src/components/Welcome.jsx index d1d9ba26e..48fe6e4e6 100644 --- a/public/src/components/Welcome.jsx +++ b/public/src/components/Welcome.jsx @@ -1,14 +1,15 @@ import React, { useState, useEffect } from "react"; import styled from "styled-components"; import Robot from "../assets/robot.gif"; +import userById from "../utils/backendCall"; export default function Welcome() { const [userName, setUserName] = useState(""); - useEffect(async () => { - setUserName( - await JSON.parse( - localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY) - ).username - ); + useEffect(() => { + async function fetchData() { + const user = await userById(); + setUserName(user.username); + } + fetchData(); }, []); return ( diff --git a/public/src/pages/Chat.jsx b/public/src/pages/Chat.jsx index 015558cc3..5c21a47f2 100644 --- a/public/src/pages/Chat.jsx +++ b/public/src/pages/Chat.jsx @@ -7,6 +7,7 @@ import { allUsersRoute, host } from "../utils/APIRoutes"; import ChatContainer from "../components/ChatContainer"; import Contacts from "../components/Contacts"; import Welcome from "../components/Welcome"; +import userById from "../utils/backendCall"; export default function Chat() { const navigate = useNavigate(); @@ -14,17 +15,19 @@ export default function Chat() { const [contacts, setContacts] = useState([]); const [currentChat, setCurrentChat] = useState(undefined); const [currentUser, setCurrentUser] = useState(undefined); - useEffect(async () => { - if (!localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY)) { - navigate("/login"); - } else { - setCurrentUser( - await JSON.parse( - localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY) - ) - ); + useEffect(() => { + async function fetchData() { + if (!localStorage.getItem(process.env.REACT_APP_LOCALHOST_KEY)) { + navigate("/login"); + } else { + const user = await userById(); + setCurrentUser(user); + } } + fetchData(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { if (currentUser) { socket.current = io(host); @@ -32,16 +35,27 @@ export default function Chat() { } }, [currentUser]); - useEffect(async () => { - if (currentUser) { - if (currentUser.isAvatarImageSet) { - const data = await axios.get(`${allUsersRoute}/${currentUser._id}`); - setContacts(data.data); - } else { - navigate("/setAvatar"); + useEffect(() => { + async function fetchData() { + if (currentUser) { + if (currentUser.isAvatarImageSet) { + const data = await axios.get(`${allUsersRoute}/${currentUser._id}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem( + process.env.REACT_APP_LOCALHOST_KEY + )}`, + }, + }); + setContacts(data.data); + } else { + navigate("/setAvatar"); + } } } + fetchData(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentUser]); + const handleChatChange = (chat) => { setCurrentChat(chat); }; diff --git a/public/src/pages/Login.jsx b/public/src/pages/Login.jsx index d5a12ed9e..f6920b8ee 100644 --- a/public/src/pages/Login.jsx +++ b/public/src/pages/Login.jsx @@ -51,11 +51,7 @@ export default function Login() { toast.error(data.msg, toastOptions); } if (data.status === true) { - localStorage.setItem( - process.env.REACT_APP_LOCALHOST_KEY, - JSON.stringify(data.user) - ); - + localStorage.setItem(process.env.REACT_APP_LOCALHOST_KEY, data.token); navigate("/"); } } diff --git a/public/src/pages/Register.jsx b/public/src/pages/Register.jsx index 486e919b3..34c7e2626 100644 --- a/public/src/pages/Register.jsx +++ b/public/src/pages/Register.jsx @@ -75,10 +75,7 @@ export default function Register() { toast.error(data.msg, toastOptions); } if (data.status === true) { - localStorage.setItem( - process.env.REACT_APP_LOCALHOST_KEY, - JSON.stringify(data.user) - ); + localStorage.setItem(process.env.REACT_APP_LOCALHOST_KEY, data.token); navigate("/"); } } diff --git a/public/src/utils/APIRoutes.js b/public/src/utils/APIRoutes.js index 47702ba94..9bf6164db 100644 --- a/public/src/utils/APIRoutes.js +++ b/public/src/utils/APIRoutes.js @@ -6,3 +6,4 @@ export const allUsersRoute = `${host}/api/auth/allusers`; export const sendMessageRoute = `${host}/api/messages/addmsg`; export const recieveMessageRoute = `${host}/api/messages/getmsg`; export const setAvatarRoute = `${host}/api/auth/setavatar`; +export const userByIdURL = `${host}/api/auth/userById`; diff --git a/public/src/utils/backendCall.js b/public/src/utils/backendCall.js new file mode 100644 index 000000000..5e64195f6 --- /dev/null +++ b/public/src/utils/backendCall.js @@ -0,0 +1,19 @@ +import axios from "axios"; +import { userByIdURL } from "./APIRoutes"; + +const userById = async () => { + try { + const response = await axios.get(`${userByIdURL}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem( + process.env.REACT_APP_LOCALHOST_KEY + )}`, + }, + }); + return response.data; + } catch (err) { + console.log(err); + } +}; + +export default userById; diff --git a/server/.env b/server/.env index ef47b5c41..ddd7ad1ca 100644 --- a/server/.env +++ b/server/.env @@ -1,2 +1,3 @@ PORT=5000 -MONGO_URL="mongodb://localhost:27017/chat" \ No newline at end of file +MONGO_URL="mongodb://localhost:27017/chat" +JWT_SECRET="your-secret-key" \ No newline at end of file diff --git a/server/.env.example b/server/.env.example index ef47b5c41..ddd7ad1ca 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,2 +1,3 @@ PORT=5000 -MONGO_URL="mongodb://localhost:27017/chat" \ No newline at end of file +MONGO_URL="mongodb://localhost:27017/chat" +JWT_SECRET="your-secret-key" \ No newline at end of file diff --git a/server/controllers/userController.js b/server/controllers/userController.js index 88e87b862..728153ffb 100644 --- a/server/controllers/userController.js +++ b/server/controllers/userController.js @@ -1,5 +1,6 @@ const User = require("../models/userModel"); const bcrypt = require("bcrypt"); +const generateToken = require("../utils/generateToken"); module.exports.login = async (req, res, next) => { try { @@ -10,8 +11,9 @@ module.exports.login = async (req, res, next) => { const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) return res.json({ msg: "Incorrect Username or Password", status: false }); - delete user.password; - return res.json({ status: true, user }); + const userId = user._id; + const token = generateToken(userId); + return res.json({ status: true, token }); } catch (ex) { next(ex); } @@ -32,8 +34,9 @@ module.exports.register = async (req, res, next) => { username, password: hashedPassword, }); - delete user.password; - return res.json({ status: true, user }); + const userId = user._id; + const token = generateToken(userId); + return res.json({ status: true, token }); } catch (ex) { next(ex); } @@ -41,7 +44,7 @@ module.exports.register = async (req, res, next) => { module.exports.getAllUsers = async (req, res, next) => { try { - const users = await User.find({ _id: { $ne: req.params.id } }).select([ + const users = await User.find({ _id: { $ne: req.userId } }).select([ "email", "username", "avatarImage", @@ -53,9 +56,19 @@ module.exports.getAllUsers = async (req, res, next) => { } }; +module.exports.userById = async (req, res, next) => { + try { + const user = await User.findById(req.userId, { password: 0 }); + if (!user) return res.json({ msg: "User not found" }); + return res.json(user); + } catch (ex) { + next(ex); + } +}; + module.exports.setAvatar = async (req, res, next) => { try { - const userId = req.params.id; + const userId = req.userId; const avatarImage = req.body.image; const userData = await User.findByIdAndUpdate( userId, @@ -76,8 +89,8 @@ module.exports.setAvatar = async (req, res, next) => { module.exports.logOut = (req, res, next) => { try { - if (!req.params.id) return res.json({ msg: "User id is required " }); - onlineUsers.delete(req.params.id); + if (!req.userId) return res.json({ msg: "User id is required " }); + onlineUsers.delete(req.userId); return res.status(200).send(); } catch (ex) { next(ex); diff --git a/server/middlewares/authMiddleware.js b/server/middlewares/authMiddleware.js new file mode 100644 index 000000000..139db62e0 --- /dev/null +++ b/server/middlewares/authMiddleware.js @@ -0,0 +1,18 @@ +const verfiyToken = require("../utils/verfiyToken"); + +const authMiddleware = (req, res, next) => { + const auth = req.headers?.authorization; + if (!auth || !auth.startsWith("Bearer ")) { + return res.status(401).json({ msg: "Headers are not there" }); + } + const token = auth.split("Bearer ")[1]; + try { + const decodedToken = verfiyToken(token); + req.userId = decodedToken.userId; + next(); + } catch (err) { + return res.status(401).json({ msg: "Unauthorized" }); + } +}; + +module.exports = authMiddleware; diff --git a/server/package.json b/server/package.json index c4951dd0a..cb76bb633 100644 --- a/server/package.json +++ b/server/package.json @@ -14,6 +14,7 @@ "cors": "^2.8.5", "dotenv": "^16.0.0", "express": "^4.17.2", + "jsonwebtoken": "^9.0.2", "mongoose": "^6.2.1", "nodemon": "^2.0.15", "socket.io": "^4.4.1" diff --git a/server/routes/auth.js b/server/routes/auth.js index d60d17b65..5913a7bb2 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -4,14 +4,17 @@ const { getAllUsers, setAvatar, logOut, + userById, } = require("../controllers/userController"); +const authMiddleware = require("../middlewares/authMiddleware"); const router = require("express").Router(); router.post("/login", login); router.post("/register", register); -router.get("/allusers/:id", getAllUsers); -router.post("/setavatar/:id", setAvatar); -router.get("/logout/:id", logOut); +router.get("/allusers/:id", authMiddleware, getAllUsers); +router.post("/setavatar/:id", authMiddleware, setAvatar); +router.get("/userById", authMiddleware, userById); +router.get("/logout/:id", authMiddleware, logOut); module.exports = router; diff --git a/server/utils/generateToken.js b/server/utils/generateToken.js new file mode 100644 index 000000000..bd2aa555e --- /dev/null +++ b/server/utils/generateToken.js @@ -0,0 +1,7 @@ +const jwt = require("jsonwebtoken"); + +const generateToken = (id) => { + return jwt.sign({ userId: id }, process.env.JWT_SECRET); +}; + +module.exports = generateToken; diff --git a/server/utils/verfiyToken.js b/server/utils/verfiyToken.js new file mode 100644 index 000000000..88b0f64a2 --- /dev/null +++ b/server/utils/verfiyToken.js @@ -0,0 +1,7 @@ +const jwt = require("jsonwebtoken"); + +const verfiyToken = (token) => { + return jwt.verify(token, process.env.JWT_SECRET); +}; + +module.exports = verfiyToken;