From f54d19636f05904f65094ee92811fcbed42ce784 Mon Sep 17 00:00:00 2001
From: IceTeaXXD <13521024@std.stei.itb.ac.id>
Date: Wed, 15 Nov 2023 23:28:10 +0700
Subject: [PATCH] feat: redis

---
 controllers/auth.controller.ts | 170 +++++++++++++++++++++++++--------
 package.json                   |   2 +
 redis.ts                       |  11 +++
 server.ts                      |  15 +++
 yarn.lock                      |  58 ++++++++++-
 5 files changed, 217 insertions(+), 39 deletions(-)
 create mode 100644 redis.ts

diff --git a/controllers/auth.controller.ts b/controllers/auth.controller.ts
index 2a07a02..739a867 100644
--- a/controllers/auth.controller.ts
+++ b/controllers/auth.controller.ts
@@ -8,8 +8,9 @@ import {
   TokenExpiredError,
   JsonWebTokenError
 } from "jsonwebtoken"
-
 const prismaClient = new PrismaClient()
+import { client } from "../redis"
+import { promisify } from "util"
 
 const generateAccessToken = (
   user_id: number,
@@ -190,10 +191,7 @@ export const handleLogout = async (
   }
 }
 
-export const handleRefreshToken = async (
-  req: Request,
-  res: Response
-): Promise<void> => {
+export const handleRefreshToken = async (req: Request, res: Response) => {
   const cookies = req.cookies
   if (!cookies?.jwt) {
     res.sendStatus(401)
@@ -201,48 +199,144 @@ export const handleRefreshToken = async (
   }
 
   const refreshToken: string = cookies.jwt
+
   try {
-    const findUser = await prismaClient.user.findFirst({
-      where: {
-        refreshToken: refreshToken
-      }
-    })
+    const redisData = await client.get(refreshToken)
 
-    if (!findUser) {
-      res.sendStatus(403)
-      return
-    }
+    console.log("handleRefreshToken")
+    if (redisData) {
+      // Data found in Redis
+      console.log("Data found in Redis")
+      const decoded = JSON.parse(redisData)
+      const { user_id, email, name, roles, accessToken } = decoded
+      res.cookie("accToken", accessToken, {
+        maxAge: 2 * 60 * 1000
+      })
+      res.json({ user_id, email, name, roles, accessToken })
+    } else {
+      // Data not found in Redis
+      console.log("Data not found in Redis")
+      try {
+        const findUser = await prismaClient.user.findFirst({
+          where: {
+            refreshToken: refreshToken
+          }
+        })
 
-    verify(
-      refreshToken,
-      process.env.REFRESH_TOKEN_SECRET as string,
-      (err, decoded: any) => {
-        if (err || !findUser || findUser.email !== decoded.email) {
+        if (!findUser) {
           res.sendStatus(403)
           return
         }
-        const name = findUser.name
-        const email = findUser.email
-        const roles = findUser.role
-        const accessTokenSecret: string = String(
-          process.env.ACCESS_TOKEN_SECRET
-        )
-        const accessToken = generateAccessToken(
-          findUser.user_id,
-          findUser.name,
-          findUser.email,
-          roles,
-          accessTokenSecret
+
+        verify(
+          refreshToken,
+          process.env.REFRESH_TOKEN_SECRET as string,
+          (err, decoded: any) => {
+            if (err || !findUser || findUser.email !== decoded.email) {
+              res.sendStatus(403)
+              return
+            }
+            const name = findUser.name
+            const email = findUser.email
+            const roles = findUser.role
+            const accessTokenSecret: string = String(
+              process.env.ACCESS_TOKEN_SECRET
+            )
+            const accessToken = generateAccessToken(
+              findUser.user_id,
+              findUser.name,
+              findUser.email,
+              roles,
+              accessTokenSecret
+            )
+            // Save the refreshToken in Redis
+            client.set(
+              refreshToken,
+              JSON.stringify({
+                user_id: findUser.user_id,
+                email,
+                name,
+                roles,
+                accessToken
+              })
+            )
+            res.cookie("accToken", accessToken, {
+              maxAge: 2 * 60 * 1000
+            })
+            res.json({
+              user_id: findUser.user_id,
+              email,
+              name,
+              roles,
+              accessToken
+            })
+          }
         )
-        res.cookie("accToken", accessToken, {
-          maxAge: 2 * 60 * 1000
-        })
-        res.json({ user_id: findUser.user_id, email, name, roles, accessToken })
+      } catch (error) {
+        console.error(error)
+        res.sendStatus(500)
+        return
       }
-    )
-  } catch (error) {
-    console.error(error)
+    }
+  } catch (redisErr) {
+    console.error("Error reading from Redis:", redisErr)
     res.sendStatus(500)
     return
   }
 }
+// export const handleRefreshToken = async (
+//   req: Request,
+//   res: Response
+// ): Promise<void> => {
+//   const cookies = req.cookies
+//   if (!cookies?.jwt) {
+//     res.sendStatus(401)
+//     return
+//   }
+
+//   const refreshToken: string = cookies.jwt
+//   try {
+//     const findUser = await prismaClient.user.findFirst({
+//       where: {
+//         refreshToken: refreshToken
+//       }
+//     })
+
+//     if (!findUser) {
+//       res.sendStatus(403)
+//       return
+//     }
+
+//     verify(
+//       refreshToken,
+//       process.env.REFRESH_TOKEN_SECRET as string,
+//       (err, decoded: any) => {
+//         if (err || !findUser || findUser.email !== decoded.email) {
+//           res.sendStatus(403)
+//           return
+//         }
+//         const name = findUser.name
+//         const email = findUser.email
+//         const roles = findUser.role
+//         const accessTokenSecret: string = String(
+//           process.env.ACCESS_TOKEN_SECRET
+//         )
+//         const accessToken = generateAccessToken(
+//           findUser.user_id,
+//           findUser.name,
+//           findUser.email,
+//           roles,
+//           accessTokenSecret
+//         )
+//         res.cookie("accToken", accessToken, {
+//           maxAge: 2 * 60 * 1000
+//         })
+//         res.json({ user_id: findUser.user_id, email, name, roles, accessToken })
+//       }
+//     )
+//   } catch (error) {
+//     console.error(error)
+//     res.sendStatus(500)
+//     return
+//   }
+// }
diff --git a/package.json b/package.json
index 6a4fa6c..f3cc10c 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
   },
   "dependencies": {
     "@prisma/client": "^5.5.2",
+    "@redis/client": "^1.5.11",
     "@types/bcrypt": "^5.0.1",
     "@types/cookie": "^0.5.3",
     "@types/js-cookie": "^3.0.5",
@@ -45,6 +46,7 @@
     "multer": "^1.4.5-lts.1",
     "mysql2": "^3.6.2",
     "nodemon": "^3.0.1",
+    "redis": "^4.6.10",
     "util": "^0.12.5",
     "xml2js": "^0.6.2"
   }
diff --git a/redis.ts b/redis.ts
new file mode 100644
index 0000000..bfd3d96
--- /dev/null
+++ b/redis.ts
@@ -0,0 +1,11 @@
+import { createClient } from 'redis';
+import dotenv from 'dotenv';
+
+dotenv.config();
+
+export const client = createClient({
+    socket: {
+        host: "127.0.0.1",
+        port: 6379,
+    }
+});
diff --git a/server.ts b/server.ts
index a4833fa..dd72027 100644
--- a/server.ts
+++ b/server.ts
@@ -2,6 +2,7 @@ import verifyJWT from "./middleware/verifyJWT"
 import express from "express"
 import cors from "cors"
 import dotenv from "dotenv"
+import { client } from "./redis"
 import { sync } from "./polling/sync"
 dotenv.config()
 
@@ -51,3 +52,17 @@ app.listen(PORT, () => {
   console.log(`⚡️[server]: Server is running at http://localhost:${PORT}`)
   sync()
 })
+
+const start = async () => {
+  try {
+    await client.connect()
+    app.listen(PORT, () => {
+      console.log(`⚡️[server]: Server is running at http://localhost:${PORT}`)
+      sync()
+    })
+  } catch (error) {
+    console.error(error)
+  }
+}
+
+start()
diff --git a/yarn.lock b/yarn.lock
index a0fe20f..4849081 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -59,6 +59,40 @@
   resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.5.2.tgz#fe0d2361a48c7d59568ccf0d35c75432594e1ac1"
   integrity sha512-Be5hoNF8k+lkB3uEMiCHbhbfF6aj1GnrTBnn5iYFT7GEr3TsOEp1soviEcBR0tYCgHbxjcIxJMhdbvxALJhAqg==
 
+"@redis/bloom@1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.2.0.tgz#d3fd6d3c0af3ef92f26767b56414a370c7b63b71"
+  integrity sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==
+
+"@redis/client@1.5.11", "@redis/client@^1.5.11":
+  version "1.5.11"
+  resolved "https://registry.yarnpkg.com/@redis/client/-/client-1.5.11.tgz#5ee8620fea56c67cb427228c35d8403518efe622"
+  integrity sha512-cV7yHcOAtNQ5x/yQl7Yw1xf53kO0FNDTdDU6bFIMbW6ljB7U7ns0YRM+QIkpoqTAt6zK5k9Fq0QWlUbLcq9AvA==
+  dependencies:
+    cluster-key-slot "1.1.2"
+    generic-pool "3.9.0"
+    yallist "4.0.0"
+
+"@redis/graph@1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@redis/graph/-/graph-1.1.0.tgz#cc2b82e5141a29ada2cce7d267a6b74baa6dd519"
+  integrity sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==
+
+"@redis/json@1.0.6":
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/@redis/json/-/json-1.0.6.tgz#b7a7725bbb907765d84c99d55eac3fcf772e180e"
+  integrity sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==
+
+"@redis/search@1.1.5":
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/@redis/search/-/search-1.1.5.tgz#682b68114049ff28fdf2d82c580044dfb74199fe"
+  integrity sha512-hPP8w7GfGsbtYEJdn4n7nXa6xt6hVZnnDktKW4ArMaFQ/m/aR7eFvsLQmG/mn1Upq99btPJk+F27IQ2dYpCoUg==
+
+"@redis/time-series@1.0.5":
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/@redis/time-series/-/time-series-1.0.5.tgz#a6d70ef7a0e71e083ea09b967df0a0ed742bc6ad"
+  integrity sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==
+
 "@tsconfig/node10@^1.0.7":
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
@@ -427,6 +461,11 @@ chownr@^2.0.0:
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
   integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
 
+cluster-key-slot@1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
+  integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==
+
 color-support@^1.1.2:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
@@ -776,6 +815,11 @@ generate-function@^2.3.1:
   dependencies:
     is-property "^1.0.2"
 
+generic-pool@3.9.0:
+  version "3.9.0"
+  resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.9.0.tgz#36f4a678e963f4fdb8707eab050823abc4e8f5e4"
+  integrity sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==
+
 get-intrinsic@^1.0.2, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b"
@@ -1478,6 +1522,18 @@ readdirp@~3.6.0:
   dependencies:
     picomatch "^2.2.1"
 
+redis@^4.6.10:
+  version "4.6.10"
+  resolved "https://registry.yarnpkg.com/redis/-/redis-4.6.10.tgz#07f6ea2b2c5455b098e76d1e8c9b3376114e9458"
+  integrity sha512-mmbyhuKgDiJ5TWUhiKhBssz+mjsuSI/lSZNPI9QvZOYzWvYGejtb+W3RlDDf8LD6Bdl5/mZeG8O1feUGhXTxEg==
+  dependencies:
+    "@redis/bloom" "1.2.0"
+    "@redis/client" "1.5.11"
+    "@redis/graph" "1.1.0"
+    "@redis/json" "1.0.6"
+    "@redis/search" "1.1.5"
+    "@redis/time-series" "1.0.5"
+
 rimraf@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@@ -1827,7 +1883,7 @@ xtend@^4.0.0:
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
   integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
 
-yallist@^4.0.0:
+yallist@4.0.0, yallist@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
   integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
-- 
GitLab