diff --git a/client/src/App.tsx b/client/src/App.tsx index 6291726..f1e3919 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,48 +1,39 @@ -import { - BrowserRouter as Router, - Routes, - Route, - Navigate, -} from "react-router-dom"; -import axios from "axios"; -import Home from "./components/Home/Home"; // Has Sidebar + +import { Routes, Route, Navigate } from "react-router-dom"; +import Home from "./components/Home/Home"; import Register from "./components/Authentication/Register"; import Login from "./components/Authentication/Login"; -import MyFiles from "./components/Home/MyFiles"; // Page -import HomeContent from "./components/Home/HomeContent"; // Default Content +import MyFiles from "./components/Home/MyFiles"; +import HomeContent from "./components/Home/HomeContent"; import LandingPage from "./components/Home/LandingPage"; import Analytics from "./components/Home/Analytics"; -// Set up axios defaults -axios.defaults.baseURL = - import.meta.env.VITE_API_BASE_URL || "http://localhost:5000"; +import ProtectedRoute from "./components/ProtectedRoute"; -// Add token to requests automatically -axios.interceptors.request.use((config) => { - const token = localStorage.getItem("authToken"); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; -}); function App() { return ( - + <> {/* Public Routes */} } /> } /> } /> - - {/* Protected Home Layout */} - } /> - }> - } /> {/* /home/myfiles */} + } /> + + {/* Protected Routes */} + + + + } + > + } /> } /> } /> - + ); } -export default App; +export default App; \ No newline at end of file diff --git a/client/src/components/Authentication/Login.tsx b/client/src/components/Authentication/Login.tsx index f32b4f6..6a1cd1c 100644 --- a/client/src/components/Authentication/Login.tsx +++ b/client/src/components/Authentication/Login.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import { useNavigate, Link } from "react-router-dom"; -import axios from "axios"; +// import axios from "axios"; +import api from "../../services/api"; import { Cloud, Shield, @@ -24,29 +25,6 @@ const Login: React.FC = () => { const [loading, setLoading] = useState(false); const [errorMsg, setErrorMsg] = useState(null); - const api = axios.create({ - baseURL: import.meta.env.VITE_API_URL, - }); - - const [theme, setTheme] = useState( - document.documentElement.classList.contains("dark") - ? "dark" : "light", - ); - - const toggleTheme = () => { - const isDark = document.documentElement.classList.contains("dark"); - - if (isDark) { - document.documentElement.classList.remove("dark"); - localStorage.setItem("theme", "light"); - setTheme("light"); - } else { - document.documentElement.classList.add("dark"); - localStorage.setItem("theme", "dark"); - setTheme("dark"); - } - }; - const handleRegisterRedirect = () => { navigate("/register"); }; @@ -61,27 +39,19 @@ const Login: React.FC = () => { setLoading(true); try { - const response = await api.post("/login", { - email, - password, - }); - - localStorage.setItem("authToken", response.data.authToken); - localStorage.setItem("userEmail", email); + const response = await api.post( + "/login", + { email, password }, + { + withCredentials: true, + }, + ); - // Show success message - setErrorMsg(null); - alert("Login successful! Welcome back."); - navigate("/home"); - } catch (error: unknown) { - if (axios.isAxiosError(error)) { - setErrorMsg( - error.response?.data?.error || - "Login failed. Please check your credentials.", - ); - } else { - setErrorMsg("Login failed. Please try again."); + if (response.data.success) { + alert("Login successful! Welcome back."); + navigate("/home"); } + } catch (error: unknown) { } finally { setLoading(false); } @@ -156,7 +126,7 @@ const Login: React.FC = () => {

-
+
-
+ {/* Right Column - Features */} diff --git a/client/src/components/Authentication/Register.tsx b/client/src/components/Authentication/Register.tsx index 4497244..ba2e4ef 100644 --- a/client/src/components/Authentication/Register.tsx +++ b/client/src/components/Authentication/Register.tsx @@ -1,6 +1,8 @@ + import React, { useState } from "react"; import { useNavigate, Link } from "react-router-dom"; import axios from "axios"; +import api from "../../services/api"; import { Shield, Zap, @@ -24,10 +26,6 @@ const Register: React.FC = () => { const [loading, setLoading] = useState(false); const [errorMsg, setErrorMsg] = useState(null); - const api = axios.create({ - baseURL: import.meta.env.VITE_API_URL, - }); - const handleLogin = () => { navigate("/login"); }; @@ -54,7 +52,6 @@ const Register: React.FC = () => { const handleRegister = async () => { setErrorMsg(null); - // Basic validation if (!username || !email || !password) { setErrorMsg("All fields are required."); return; @@ -67,14 +64,15 @@ const Register: React.FC = () => { setLoading(true); try { + const res = await api.post("/register", { username, email, password, }); - if (res.data) { - alert("Registration successful! Please login."); + if (res.data.success) { + alert("Registration successful! Welcome to SecureShare!"); navigate("/home"); } } catch (error: unknown) { @@ -83,7 +81,7 @@ const Register: React.FC = () => { setErrorMsg( error.response?.data?.error || error.response?.data?.message || - "Registration failed. Please check your details.", + "Registration failed. Please check your details." ); } else { setErrorMsg("Registration failed. Please try again."); @@ -166,7 +164,7 @@ const Register: React.FC = () => {

-
+
-
+ {/* Right Column - Features */} @@ -472,4 +479,4 @@ const Register: React.FC = () => { ); }; -export default Register; +export default Register; \ No newline at end of file diff --git a/client/src/components/Home/Home.tsx b/client/src/components/Home/Home.tsx index 9a85ff8..b8e881c 100644 --- a/client/src/components/Home/Home.tsx +++ b/client/src/components/Home/Home.tsx @@ -1,3 +1,4 @@ + import React, { useState, useEffect } from "react"; import { LogOut, @@ -14,6 +15,7 @@ import { } from "lucide-react"; import { Outlet, useNavigate, useLocation } from "react-router-dom"; import HomeContent from "./HomeContent"; +import api from "../../services/api"; const Home: React.FC = () => { @@ -22,31 +24,32 @@ const Home: React.FC = () => { const [isOpen, setIsOpen] = useState(true); const [isMobile, setIsMobile] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); - const [user] = useState({ - email: localStorage.getItem("userEmail") || "user@example.com", + const [user, setUser] = useState({ + email: "Loading...", storage: 3.2, storageLimit: 10, }); - const [theme, setTheme] = useState( - document.documentElement.classList.contains("dark") - ? "dark" - : "light" - ); - - const toggleTheme = () => { - const isDark = document.documentElement.classList.contains("dark"); - - if (isDark) { - document.documentElement.classList.remove("dark"); - localStorage.setItem("theme", "light"); - setTheme("light"); - } else { - document.documentElement.classList.add("dark"); - localStorage.setItem("theme", "dark"); - setTheme("dark"); - } - }; + + useEffect(() => { + const fetchUser = async () => { + try { + const response = await api.get("/auth", { + withCredentials: true, + }); + if (response.data.user) { + setUser({ + email: response.data.user.email, + storage: response.data.storageUsed || 3.2, + storageLimit: response.data.storageLimit || 10, + }); + } + } catch (error) { + console.error("Failed to fetch user:", error); + } + }; + fetchUser(); + }, []); useEffect(() => { const savedTheme = localStorage.getItem("theme"); @@ -97,10 +100,14 @@ const Home: React.FC = () => { }, ]; - const handleLogout = () => { - localStorage.removeItem("authToken"); - localStorage.removeItem("userEmail"); - navigate("/login"); + const handleLogout = async () => { + try { + await api.post("/logout"); + navigate("/login"); + } catch (error) { + console.error("Logout failed:", error); + navigate("/login"); + } }; const handleNavigation = (path: string) => { @@ -194,8 +201,8 @@ const Home: React.FC = () => { {isOpen && (
-
- {user.email.split('@')[0]} +
+ {user.email.split("@")[0]}
{user.email} @@ -294,8 +301,8 @@ const Home: React.FC = () => {
-
- {user.email.split('@')[0]} +
+ {user.email.split("@")[0]}
{user.email} diff --git a/client/src/components/ProtectedRoute.tsx b/client/src/components/ProtectedRoute.tsx new file mode 100644 index 0000000..6b7f332 --- /dev/null +++ b/client/src/components/ProtectedRoute.tsx @@ -0,0 +1,34 @@ +import { Navigate } from "react-router-dom"; +import React from "react"; + + +const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { + + const [isAuthenticated, setIsAuthenticated] = React.useState(null); + + React.useEffect(() => { + const checkAuth = async () => { + try { + const response = await fetch("http://localhost:5000/auth", { + credentials: "include", + }); + setIsAuthenticated(response.ok); + } catch (error) { + setIsAuthenticated(false); + } + }; + checkAuth(); + }, []); + + if (isAuthenticated === null) { + return
Loading...
; // Or a spinner + } + + if (!isAuthenticated) { + return ; + } + + return <>{children}; +}; + +export default ProtectedRoute; \ No newline at end of file diff --git a/client/src/main.tsx b/client/src/main.tsx index 457340a..dd54fc0 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,15 +1,13 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; +import "./index.css"; +import App from "./App"; -const savedTheme = localStorage.getItem('theme') -if (savedTheme === 'dark') { - document.documentElement.classList.add('dark') -} - -createRoot(document.getElementById('root')!).render( +createRoot(document.getElementById("root")!).render( - + + + , -) +); diff --git a/client/src/services/api.ts b/client/src/services/api.ts index 60af0dd..3b04cdb 100644 --- a/client/src/services/api.ts +++ b/client/src/services/api.ts @@ -4,15 +4,7 @@ const API_URL = import.meta.env.VITE_API_URL || "http://localhost:5000"; const api = axios.create({ baseURL: API_URL, -}); - -// Add auth token to requests -api.interceptors.request.use((config) => { - const token = localStorage.getItem("authToken"); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; + withCredentials: true, }); // ==================== ANALYTICS APIs ==================== @@ -68,4 +60,6 @@ export const fileApi = { const response = await api.delete(`/api/files/${fileId}`); return response.data; }, -}; \ No newline at end of file +}; + +export default api; \ No newline at end of file diff --git a/server/config/redis.js b/server/config/redis.js new file mode 100644 index 0000000..ecfc9ad --- /dev/null +++ b/server/config/redis.js @@ -0,0 +1,12 @@ +import { createClient } from "redis"; + +const redisClient = createClient({ + url: process.env.REDIS_URL || "redis://localhost:6379" +}); + +redisClient.on("error", (err) => console.log("Redis Error:", err)); +redisClient.on("connect", () => console.log("Redis Connected Successfully!")); + +await redisClient.connect(); + +export default redisClient; \ No newline at end of file diff --git a/server/controllers/userController.js b/server/controllers/userController.js index 0b20a9a..0e884e5 100644 --- a/server/controllers/userController.js +++ b/server/controllers/userController.js @@ -1,6 +1,7 @@ import User from "../models/UserSchema.js"; import bcrypt from "bcryptjs"; import jwt from "jsonwebtoken"; +import redisClient from "../config/redis.js"; // REGISTER export const registerUser = async (req, res) => { @@ -88,48 +89,41 @@ export const registerUser = async (req, res) => { }; // LOGIN + export const loginUser = async (req, res) => { const { email, password } = req.body; try { - // Validate input if (!email || !password) { return res.status(400).json({ error: "Email and password are required" }); } - // Find user by email (case insensitive) - const user = await User.findOne({ - email: email.toLowerCase().trim(), - }); - + const user = await User.findOne({ email: email.toLowerCase().trim() }); if (!user) { - return res.status(401).json({ error: "Invalid credentials" }); // 401 for unauthorized + return res.status(401).json({ error: "Invalid credentials" }); } - // Compare password const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(401).json({ error: "Invalid credentials" }); } // Generate JWT token - const payload = { - user: { - id: user.id, - email: user.email, - }, - }; + const payload = { user: { id: user.id, email: user.email } }; + const token = jwt.sign(payload, process.env.JWT_SECRET, { + expiresIn: process.env.JWT_EXPIRES_IN || "7d" + }); - const authToken = jwt.sign( - payload, - process.env.JWT_SECRET || process.env.JWT_TOKEN, - { - expiresIn: process.env.JWT_EXPIRES_IN || "1h", - } - ); + res.cookie("token", token, { + httpOnly: true, + secure: false, + sameSite: "lax", + path: "/", + maxAge: 7 * 24 * 60 * 60 * 1000 + }); res.json({ - authToken, + success: true, message: "Login successful", user: { id: user.id, @@ -139,11 +133,39 @@ export const loginUser = async (req, res) => { }); } catch (error) { console.error("Login error:", error.message); - res.status(500).json({ - error: "Server Error", - message: - process.env.NODE_ENV === "development" ? error.message : undefined, + res.status(500).json({ error: "Server Error" }); + } +}; + + +// LOGOUT + +export const logoutUser = async (req, res) => { + try { + const token = req.cookies?.token; + + if (token) { + const decoded = jwt.decode(token); + if (decoded && decoded.exp) { + const ttl = decoded.exp - Math.floor(Date.now() / 1000); + if (ttl > 0) { + await redisClient.setEx(`blacklist:${token}`, ttl, "blocked"); + console.log("✅ Token blacklisted"); + } + } + } + + res.clearCookie("token", { + httpOnly: true, + secure: false, + sameSite: "lax", + path: "/", }); + + res.status(200).json({ success: true, message: "Logged out successfully" }); + } catch (error) { + console.error("Logout error:", error); + res.status(500).json({ error: "Logout failed" }); } }; diff --git a/server/index.js b/server/index.js index 297ea1a..3d54340 100644 --- a/server/index.js +++ b/server/index.js @@ -1,9 +1,11 @@ + import express from "express"; import dotenv from "dotenv"; dotenv.config(); import connectDB from "./config/db.js"; import router from "./routes/routers.js"; import cors from "cors"; +import cookieParser from "cookie-parser"; import multer from "multer"; import { v2 as cloudinary } from "cloudinary"; import streamifier from "streamifier"; @@ -12,10 +14,10 @@ import fileRoutes from "./routes/files.js"; const app = express(); -// CORS setup + app.use( cors({ - origin: true, + origin: "http://localhost:5173", methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], credentials: true, }), @@ -24,18 +26,14 @@ app.use( // Connect to database connectDB(); -// Seed demo user -seedDemoUser(); -// Middleware app.use(express.json()); -// In your server.js or app.js +app.use(cookieParser()); +// Routes app.use("/api/analytics", analyticsRoutes); app.use("/api/track", analyticsRoutes); app.use("/api/files", fileRoutes); - -// Routes app.use("/", router); // Error handling middleware @@ -78,7 +76,7 @@ app.post("/upload", upload.single("file"), async (req, res) => { }; const result = await streamUpload(req.file); - res.json({ url: result.secure_url }); // Public Cloudinary URL + res.json({ url: result.secure_url }); } catch (err) { console.error(err); res.status(500).json({ error: "Upload failed" }); @@ -89,4 +87,4 @@ app.post("/upload", upload.single("file"), async (req, res) => { const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`Server running on port http://localhost:${PORT}`), -); +); \ No newline at end of file diff --git a/server/middleware/authenticationUser.js b/server/middleware/authenticationUser.js index 551da81..501e61a 100644 --- a/server/middleware/authenticationUser.js +++ b/server/middleware/authenticationUser.js @@ -1,28 +1,49 @@ import jwt from "jsonwebtoken"; +import redisClient from "../config/redis.js"; -const authenticateUser = (req, res, next) => { - const authHeader = req.header("Authorization"); +const authenticateUser = async (req, res, next) => { + let token = req.cookies?.token; - // Check if Authorization header exists - if (!authHeader) { - return res.status(401).json({ error: "No token provided, authorization denied" }); + if (!token) { + const authHeader = req.header("Authorization"); + if (authHeader && authHeader.startsWith("Bearer ")) { + token = authHeader.split(" ")[1]; + } } - // Extract token from "Bearer " format - const token = authHeader.split(" ")[1]; if (!token) { - return res.status(401).json({ error: "Invalid token format, authorization denied" }); + return res + .status(401) + .json({ error: "No token provided, authorization denied" }); + } + + try { + const blacklisted = await redisClient.get(`blacklist:${token}`); + if (blacklisted) { + return res + .status(401) + .json({ error: "Token has been invalidated. Please login again." }); + } + } catch (redisError) { + console.error("Redis blacklist check error:", redisError); } try { - // Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded.user; // { id: user.id } + req.token = token; // Store token for potential logout use next(); } catch (err) { console.error("Token verification failed:", err.message); + + if (err.name === "TokenExpiredError") { + return res + .status(401) + .json({ error: "Token expired, please login again" }); + } + return res.status(401).json({ error: "Token is not valid" }); } }; -export default authenticateUser; \ No newline at end of file +export default authenticateUser; diff --git a/server/node_modules/.package-lock.json b/server/node_modules/.package-lock.json index b6ce44a..c1acb3c 100644 --- a/server/node_modules/.package-lock.json +++ b/server/node_modules/.package-lock.json @@ -13,6 +13,78 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@redis/bloom": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/@redis/bloom/-/bloom-5.12.1.tgz", + "integrity": "sha512-PUUfv+ms7jgPSBVoo/DN4AkPHj4D5TZSd6SbJX7egzBplkYUcKmHRE8RKia7UtZ8bSQbLguLvxVO+asKtQfZWA==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/client": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/@redis/client/-/client-5.12.1.tgz", + "integrity": "sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0", + "@opentelemetry/api": ">=1 <2" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@redis/json": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/@redis/json/-/json-5.12.1.tgz", + "integrity": "sha512-eOze75esLve4vfqDel7aMX08CNaiLLQS2fV8mpRN9NxPe1rVR4vQyYiW/OgtGUysF6QOr9ANhfxABKNOJfXdKg==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/search": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/@redis/search/-/search-5.12.1.tgz", + "integrity": "sha512-ItlxbxC9cKI6IU1TLWoczwJCRb6TdmkEpWv05UrPawqaAnWGRu3rcIqsc5vN483T2fSociuyV1UkWIL5I4//2w==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/time-series": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/@redis/time-series/-/time-series-5.12.1.tgz", + "integrity": "sha512-c6JL6E3EcZJuNqKFz+KM+l9l5mpcQiKvTwgA3blt5glWJ8hjDk0yeHN3beE/MpqYIQ8UEX44ItQzgkE/gCBELQ==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -242,6 +314,15 @@ "node": ">=9" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -294,6 +375,25 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmmirror.com/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", @@ -1359,6 +1459,22 @@ "node": ">=8.10.0" } }, + "node_modules/redis": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/redis/-/redis-5.12.1.tgz", + "integrity": "sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.12.1", + "@redis/client": "5.12.1", + "@redis/json": "5.12.1", + "@redis/search": "5.12.1", + "@redis/time-series": "5.12.1" + }, + "engines": { + "node": ">= 18.19.0" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", diff --git a/server/package-lock.json b/server/package-lock.json index 3a59da9..80da2f2 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "bcryptjs": "^3.0.2", "cloudinary": "^2.7.0", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^5.1.0", @@ -19,6 +20,7 @@ "mongoose": "^8.15.1", "multer": "^2.0.2", "path": "^0.12.7", + "redis": "^5.12.1", "streamifier": "^0.1.1" }, "devDependencies": { @@ -37,6 +39,78 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@redis/bloom": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/@redis/bloom/-/bloom-5.12.1.tgz", + "integrity": "sha512-PUUfv+ms7jgPSBVoo/DN4AkPHj4D5TZSd6SbJX7egzBplkYUcKmHRE8RKia7UtZ8bSQbLguLvxVO+asKtQfZWA==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/client": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/@redis/client/-/client-5.12.1.tgz", + "integrity": "sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0", + "@opentelemetry/api": ">=1 <2" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@redis/json": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/@redis/json/-/json-5.12.1.tgz", + "integrity": "sha512-eOze75esLve4vfqDel7aMX08CNaiLLQS2fV8mpRN9NxPe1rVR4vQyYiW/OgtGUysF6QOr9ANhfxABKNOJfXdKg==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/search": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/@redis/search/-/search-5.12.1.tgz", + "integrity": "sha512-ItlxbxC9cKI6IU1TLWoczwJCRb6TdmkEpWv05UrPawqaAnWGRu3rcIqsc5vN483T2fSociuyV1UkWIL5I4//2w==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, + "node_modules/@redis/time-series": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/@redis/time-series/-/time-series-5.12.1.tgz", + "integrity": "sha512-c6JL6E3EcZJuNqKFz+KM+l9l5mpcQiKvTwgA3blt5glWJ8hjDk0yeHN3beE/MpqYIQ8UEX44ItQzgkE/gCBELQ==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + }, + "peerDependencies": { + "@redis/client": "^5.12.1" + } + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -266,6 +340,15 @@ "node": ">=9" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -318,6 +401,25 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmmirror.com/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", @@ -1398,6 +1500,22 @@ "node": ">=8.10.0" } }, + "node_modules/redis": { + "version": "5.12.1", + "resolved": "https://registry.npmmirror.com/redis/-/redis-5.12.1.tgz", + "integrity": "sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.12.1", + "@redis/client": "5.12.1", + "@redis/json": "5.12.1", + "@redis/search": "5.12.1", + "@redis/time-series": "5.12.1" + }, + "engines": { + "node": ">= 18.19.0" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", diff --git a/server/package.json b/server/package.json index be93803..96c14da 100644 --- a/server/package.json +++ b/server/package.json @@ -14,6 +14,7 @@ "dependencies": { "bcryptjs": "^3.0.2", "cloudinary": "^2.7.0", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^5.1.0", @@ -22,6 +23,7 @@ "mongoose": "^8.15.1", "multer": "^2.0.2", "path": "^0.12.7", + "redis": "^5.12.1", "streamifier": "^0.1.1" }, "devDependencies": { diff --git a/server/routes/routers.js b/server/routes/routers.js index 8c9ff39..8453487 100644 --- a/server/routes/routers.js +++ b/server/routes/routers.js @@ -7,7 +7,7 @@ import { import { validation } from "../middleware/validation.js"; import { loginValidation } from "../middleware/loginValidation.js"; import authenticateUser from "../middleware/authenticationUser.js"; - +import { logoutUser } from "../controllers/userController.js"; const router = express.Router(); @@ -17,6 +17,10 @@ router.post("/register", validation, registerUser); // Login router.post("/login", loginValidation, loginUser); +//logout + +router.post("/logout", authenticateUser, logoutUser); + // Get current user router.get("/auth", authenticateUser, getUser);