Przeglądaj źródła

Feature: Implement refresh token functionality and update related authentication actions

emrecevik106 1 miesiąc temu
rodzic
commit
5f81b1b718

+ 1 - 1
docker-compose.yml

@@ -5,7 +5,7 @@ services:
     image: mongo:latest
     container_name: qrmenu_mongo
     ports:
-      - "27017:27017"
+      - "27018:27017"
     volumes:
       - mongo_data:/data/db
     restart: unless-stopped

+ 3 - 0
src/actions/auth/index.ts

@@ -10,3 +10,6 @@ export {
 export {
     default as me
 } from "./me";
+export {
+    default as refreshToken 
+} from "./refreshToken";

+ 3 - 3
src/actions/auth/login/index.ts

@@ -17,7 +17,7 @@ import {
     formatValidationErrors
 } from "../../../utils";
 
-export const login = async (input: LoginInput): Promise<LoginResult> => {
+const login = async (input: LoginInput): Promise<LoginResult> => {
     const dto = plainToInstance(LoginInput, input);
     const errors = await validate(dto);
 
@@ -78,7 +78,7 @@ export const login = async (input: LoginInput): Promise<LoginResult> => {
         }
     );
 
-    await redis.setex(`user:${user._id.toString()}`, 14400, accessToken);
+    await redis.setex(user._id.toString(), 14400, accessToken);
 
     const refreshToken = jwt.sign(
         {
@@ -87,7 +87,7 @@ export const login = async (input: LoginInput): Promise<LoginResult> => {
             userId: user._id,
             mail: user.mail
         },
-        process.env.JWT_REFRESH_SECRET as string || process.env.JWT_SECRET as string,
+        process.env.JWT_SECRET as string,
         {
             expiresIn: "30d"
         }

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

@@ -6,13 +6,13 @@ import {
     LogoutResult
 } from "./types";
 
-export const logout = async (userId: string, token: string): Promise<LogoutResult> => {
+const logout = async (userId: string, token: string): Promise<LogoutResult> => {
     try {
         await User.findByIdAndUpdate(userId, {
             refreshToken: null
         });
 
-        await redis.del(`user:${userId}`);
+        await redis.del(`${userId}`);
 
         return {
             message: "Logout successful",

+ 1 - 1
src/actions/auth/me/index.ts

@@ -5,7 +5,7 @@ import {
     MeResult
 } from "./types";
 
-export const me = async (userId: string): Promise<MeResult> => {
+const me = async (userId: string): Promise<MeResult> => {
     try {
         const user = await User.findById(userId).select("-password -refreshToken");
 

+ 102 - 0
src/actions/auth/refreshToken/index.ts

@@ -0,0 +1,102 @@
+import jwt from "jsonwebtoken";
+import redis from "../../../config/redis";
+import {
+    User 
+} from "../../../models/User";
+import {
+    RefreshTokenResult 
+} from "./types";
+
+const refreshToken = async (userId: string, token: string): Promise<RefreshTokenResult> => {
+    try {
+        if (!userId || !token) {
+            return {
+                message: "userId and refreshToken required",
+                code: 400
+            };
+        }
+
+        const user = await User.findById(userId);
+        if (!user) {
+            return {
+                message: "user-not-found",
+                code: 404
+            };
+        }
+
+        let decoded: {
+            companyName: string;
+            fullName: string;
+            userId: string;
+            mail: string;
+        };
+
+        try {
+            decoded = jwt.verify(
+                token,
+                process.env.JWT_SECRET as string
+            ) as typeof decoded;
+        } catch {
+            return {
+                message: "invalid-refresh-token",
+                code: 401
+            };
+        }
+
+        if (decoded.userId !== userId) {
+            return {
+                message: "invalid-refresh-token", 
+                code: 401
+            };
+        }
+
+        const newAccessToken = jwt.sign(
+            {
+                companyName: user.companyName,
+                fullName: user.fullName,
+                userId: user._id,
+                mail: user.mail,
+            },
+            process.env.JWT_SECRET as string,
+            {
+                expiresIn: "4h" 
+            }
+        );
+
+        const newRefreshToken = jwt.sign(
+            {
+                companyName: user.companyName,
+                fullName: user.fullName,
+                userId: user._id,
+                mail: user.mail,
+            },
+            process.env.JWT_SECRET as string,
+            {
+                expiresIn: "30d" 
+            }
+        );
+
+        await redis.del(`${userId}`);
+        await redis.setex(`${userId}`, 14400, newAccessToken);
+
+        user.refreshToken = newRefreshToken;
+        await user.save();
+
+        return {
+            code: 200,
+            message: "token-refreshed",
+            payload: {
+                accessToken: newAccessToken,
+                refreshToken: newRefreshToken,
+            },
+        };
+    } catch (error) {
+        console.error("RefreshToken action error:", error);
+        return {
+            message: "internal-server-error",
+            code: 500
+        };
+    }
+};
+
+export default refreshToken;

+ 8 - 0
src/actions/auth/refreshToken/types.ts

@@ -0,0 +1,8 @@
+export interface RefreshTokenResult {
+    code: number;
+    message: string;
+    payload?: {
+        accessToken: string;
+        refreshToken: string;
+    };
+}

+ 1 - 1
src/actions/auth/register/index.ts

@@ -14,7 +14,7 @@ import {
     formatValidationErrors 
 } from "../../../utils";
 
-export const register = async (input: RegisterInput): Promise<RegisterResult> => {
+const register = async (input: RegisterInput): Promise<RegisterResult> => {
     const dto = plainToInstance(RegisterInput, input);
     const errors = await validate(dto);
 

+ 29 - 10
src/controllers/authController.ts

@@ -3,17 +3,12 @@ import {
     Request
 } from "express";
 import {
-    register as _register
-} from "../actions/auth/register";
-import {
-    login as _login
-} from "../actions/auth/login";
-import {
-    logout as _logout
-} from "../actions/auth/logout";
-import {
+    refreshToken as _refreshToken,
+    register as _register,
+    logout as _logout,
+    login as _login,
     me as _me
-} from "../actions/auth/me";
+} from "../actions/auth";
 import {
     AuthRequest
 } from "../middlewares/authMiddleware";
@@ -147,4 +142,28 @@ export const me = async (req: AuthRequest, res: Response): Promise<void> => {
             message: "Server error", code: 500
         });
     }
+};
+
+export const refreshTokenController = async (req: Request, res: Response): Promise<void> => {
+    try {
+        const {
+            userId, refreshToken 
+        } = req.body;
+
+        const result = await _refreshToken(userId, refreshToken);
+
+        res.status(result.code).json({
+            message: result.message,
+            code: result.code,
+            ...(result.payload && {
+                payload: result.payload,
+            }),
+        });
+    } catch (error) {
+        console.error("RefreshToken controller error:", error);
+        res.status(500).json({
+            message: "internal-server-error",
+            code: 500,
+        });
+    }
 };

+ 1 - 1
src/middlewares/authMiddleware.ts

@@ -42,7 +42,7 @@ export const authMiddleware = async (req: AuthRequest, res: Response, next: Next
             return;
         }
 
-        const cachedToken = await redis.get(decoded.userId);
+        const cachedToken = await redis.get(`${decoded.userId}`);
         if (!cachedToken) {
             res.status(401).json({
                 message: "expired-token", code: 401

+ 6 - 2
src/routes/authRoutes.ts

@@ -2,6 +2,7 @@ import {
     Router
 } from "express";
 import {
+    refreshTokenController,
     register,
     logout,
     login,
@@ -22,9 +23,12 @@ router.post("/login", login);
 router.get("/validate-token", authMiddleware, (req:AuthRequest, res) => {
     res.status(200).json({
         message: "token-valid",
-        code: 200,
-        context: req.context
+        context: req.context,
+        code: 200
     });
 });
 
+router.post("/refresh-token", refreshTokenController);
+
+
 export default router;