浏览代码

Feature: Implement logout functionality and integrate Redis for session management

BedirhanOZCAN 1 月之前
父节点
当前提交
cc3729c702

+ 5 - 2
src/actions/auth/index.ts

@@ -1,6 +1,9 @@
 export {
-    default as login 
+    default as login
 } from "./login";
 export {
-    default as register 
+    default as register
 } from "./register";
+export {
+    default as logout
+} from "./logout";

+ 40 - 0
src/actions/auth/logout/index.ts

@@ -0,0 +1,40 @@
+import jwt from "jsonwebtoken";
+import {
+    User
+} from "../../../models/User";
+import redis from "../../../config/redis";
+import {
+    LogoutResult
+} from "./types";
+
+export const logout = async (userId: string, token: string): Promise<LogoutResult> => {
+    try {
+        await User.findByIdAndUpdate(userId, {
+            refreshToken: null
+        });
+
+        const decoded = jwt.decode(token) as { exp?: number };
+
+        if (decoded && decoded.exp) {
+            const currentTime = Math.floor(Date.now() / 1000);
+            const timeToExpire = decoded.exp - currentTime;
+
+            if (timeToExpire > 0) {
+                await redis.setex(`bl_${token}`, timeToExpire, "blacklisted");
+            }
+        }
+
+        return {
+            message: "Logout successful",
+            code: 200,
+        };
+    } catch (error) {
+        console.error("Logout action error:", error);
+        return {
+            message: "Logout failed",
+            code: 500,
+        };
+    }
+};
+
+export default logout;

+ 4 - 0
src/actions/auth/logout/types.ts

@@ -0,0 +1,4 @@
+export interface LogoutResult {
+    message: string;
+    code: number;
+}

+ 14 - 0
src/config/redis.ts

@@ -0,0 +1,14 @@
+import Redis from "ioredis";
+
+const redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379");
+
+redis.on("connect", () => {
+    console.log("Redis Connected Successfully");
+});
+
+redis.on("error", (error) => {
+    console.error("Redis connection error:", error);
+    process.exit(1);
+});
+
+export default redis;

+ 39 - 5
src/controllers/authController.ts

@@ -3,18 +3,24 @@ import {
     Request
 } from "express";
 import {
-    register as _register 
+    register as _register
 } from "../actions/auth/register";
 import {
-    login as _login 
+    login as _login
 } from "../actions/auth/login";
+import {
+    logout as _logout
+} from "../actions/auth/logout";
+import {
+    AuthRequest
+} from "../middlewares/authMiddleware";
 
 export const register = async (req: Request, res: Response): Promise<void> => {
     try {
         const {
             phoneNumber,
             companyName,
-            firstName, 
+            firstName,
             lastName,
             password,
             mail
@@ -23,7 +29,7 @@ export const register = async (req: Request, res: Response): Promise<void> => {
         const result = await _register({
             phoneNumber,
             companyName,
-            firstName, 
+            firstName,
             lastName,
             password,
             mail
@@ -38,7 +44,7 @@ export const register = async (req: Request, res: Response): Promise<void> => {
         console.error("Register error:", error);
         res.status(500).json({
             message: "Internal server error",
-            code: 500, 
+            code: 500,
         });
     }
 };
@@ -71,4 +77,32 @@ export const login = async (req: Request, res: Response): Promise<void> => {
                 code: 500,
             });
     }
+};
+
+export const logout = async (req: AuthRequest, res: Response): Promise<void> => {
+    try {
+        const userId = req.userId;
+        const token = req.token;
+
+        if (!userId || !token) {
+            res.status(401).json({
+                message: "Unauthorized",
+                code: 401
+            });
+            return;
+        }
+
+        const result = await _logout(userId, token);
+
+        res.status(result.code).json({
+            message: result.message,
+            code: result.code,
+        });
+    } catch (error) {
+        console.error("Logout controller error:", error);
+        res.status(500).json({
+            message: "Server error",
+            code: 500,
+        });
+    }
 };

+ 3 - 1
src/index.ts

@@ -3,9 +3,10 @@ import cors from "cors";
 import "reflect-metadata";
 import dotenv from "dotenv";
 import {
-    connectDB 
+    connectDB
 } from "./config/db";
 import routes from "./routes";
+import redis from "./config/redis";
 
 dotenv.config();
 
@@ -19,6 +20,7 @@ app.use("/api", routes);
 
 const start = async () => {
     await connectDB();
+    await redis.ping();
     app.listen(PORT, () => {
         console.log(`Server running on port ${PORT}`);
     });

+ 24 - 7
src/middlewares/authMiddleware.ts

@@ -1,31 +1,48 @@
 import {
-    Request, Response, NextFunction 
+    Request, Response, NextFunction
 } from "express";
 import jwt from "jsonwebtoken";
+import redis from "../config/redis";
 
 export interface AuthRequest extends Request {
-  userId?: string;
+    userId?: string;
+    token?: string;
 }
 
-export const authMiddleware = (req: AuthRequest, res: Response, next: NextFunction): void => {
+export const authMiddleware = async (req: AuthRequest, res: Response, next: NextFunction): Promise<void> => {
     try {
         const authHeader = req.headers.authorization;
 
         if (!authHeader || !authHeader.startsWith("Bearer ")) {
             res.status(401).json({
-                success: false, message: "Token bulunamadı" 
+                message: "Token not found",
+                code: 401
             });
             return;
         }
 
         const token = authHeader.split(" ")[1];
-        const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as { userId: string };
 
-        req.userId = decoded.userId;
+        const isBlacklisted = await redis.get(`bl_${token}`);
+
+        if (isBlacklisted) {
+            res.status(401).json({
+                message: "Your session has ended, please log in again.",
+                code: 401
+            });
+            return;
+        }
+
+        const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as { userID: string };
+
+        req.userId = decoded.userID;
+        req.token = token;
+
         next();
     } catch (error) {
         res.status(401).json({
-            success: false, message: "Geçersiz token" 
+            message: "Invalid or expired tokens",
+            code: 401
         });
     }
 };

+ 5 - 0
src/routes/authRoutes.ts

@@ -3,11 +3,16 @@ import {
 } from "express";
 import {
     register,
+    logout,
     login
 } from "../controllers/authController";
+import {
+    authMiddleware
+} from "../middlewares/authMiddleware";
 
 const router = Router();
 
+router.post("/logout", authMiddleware, logout);
 router.post("/register", register);
 router.post("/login", login);