From 2b80adb5a2945b73a8cb19cf006155805f179c12 Mon Sep 17 00:00:00 2001 From: Vivek Kumar Date: Fri, 3 Apr 2026 12:07:25 +0530 Subject: [PATCH 1/8] feat:implement-redis-otp --- server/config/redis.js | 8 ++ server/models/Otp.js | 10 -- server/models/User.js | 2 +- server/modules/auth/controller.js | 2 +- server/modules/auth/repository.js | 41 +++----- server/modules/auth/service.js | 168 +++++++++++------------------- server/package-lock.json | 88 ++++++++++++++++ server/package.json | 1 + 8 files changed, 175 insertions(+), 145 deletions(-) create mode 100644 server/config/redis.js delete mode 100644 server/models/Otp.js diff --git a/server/config/redis.js b/server/config/redis.js new file mode 100644 index 0000000..66559fc --- /dev/null +++ b/server/config/redis.js @@ -0,0 +1,8 @@ +import Redis from "ioredis"; + +const redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379"); + +redis.on("connect", () => console.log("Connected to Redis")); +redis.on("error", (err) => console.error("Redis Error:", err)); + +export default redis; \ No newline at end of file diff --git a/server/models/Otp.js b/server/models/Otp.js deleted file mode 100644 index ecff596..0000000 --- a/server/models/Otp.js +++ /dev/null @@ -1,10 +0,0 @@ -import mongoose from "mongoose"; - -const otpSchema = new mongoose.Schema({ - email: { type: String, required: true, lowercase: true, index: true }, - otp: { type: String, required: true }, // stored hashed - purpose: { type: String, enum: ["signup", "forgot-password"], required: true }, - createdAt: { type: Date, default: Date.now, expires: 600 } // TTL 10 minutes -}); - -export default mongoose.model("Otp", otpSchema); diff --git a/server/models/User.js b/server/models/User.js index c54d147..a3778d4 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -6,7 +6,7 @@ const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true, lowercase: true, index: true }, password: { type: String, required: true, minlength: 6, select: false }, role: { type: String, enum: ["user", "admin"], default: "user" }, - isVerified: { type: Boolean, default: false }, + // isVerified: { type: Boolean, default: false }, authProvider: { type: String, enum: ["local", "google", "github"], default: "local" }, // Profile Info diff --git a/server/modules/auth/controller.js b/server/modules/auth/controller.js index 6a6a2c9..4a2d654 100644 --- a/server/modules/auth/controller.js +++ b/server/modules/auth/controller.js @@ -6,7 +6,7 @@ class AuthController { static async register(req, res, next) { try { const result = await AuthService.register(req.body); - res.status(201).json(ApiResponse.success(result.message, result.user)); + res.status(201).json(ApiResponse.success(result.message)); } catch (error) { next(error instanceof ApiError ? error : new ApiError(500, error.message)); } diff --git a/server/modules/auth/repository.js b/server/modules/auth/repository.js index 743a8af..55df2d2 100644 --- a/server/modules/auth/repository.js +++ b/server/modules/auth/repository.js @@ -1,5 +1,5 @@ import User from "../../models/User.js"; -import Otp from "../../models/Otp.js"; +import redis from "../../config/redis.js"; class AuthRepository { static async createUser(userData) { @@ -15,41 +15,32 @@ class AuthRepository { return await User.findOne({ email }); } - static async findUserById(id) { - return await User.findById(id); + // --- Redis OTP Methods --- + static async storePendingUser(email, data, ttl = 600) { + // Stores user data and OTP in Redis with a 10-minute expiry + await redis.set(`pending_user:${email}`, JSON.stringify(data), "EX", ttl); } - - static async updateUserVerification(email) { - return await User.findOneAndUpdate( - { email }, - { isVerified: true }, - { new: true } - ); + static async getPendingUser(email) { + const data = await redis.get(`pending_user:${email}`); + return data ? JSON.parse(data) : null; } - static async updateUserPassword(email, hashedPassword) { - return await User.findOneAndUpdate( - { email }, - { password: hashedPassword }, - { new: true } - ); + static async deletePendingUser(email) { + await redis.del(`pending_user:${email}`); } - static async createOtp({ email, otp, purpose }) { - // Delete any existing OTPs for same email+purpose - await Otp.deleteMany({ email, purpose }); - - const otpRecord = new Otp({ email, otp, purpose }); - return await otpRecord.save(); + static async storeOtp(email, purpose, otp, ttl = 600) { + await redis.set(`otp:${purpose}:${email}`, otp, "EX", ttl); } - static async findOtp(email, purpose) { - return await Otp.findOne({ email, purpose }).sort({ createdAt: -1 }); + static async getOtp(email, purpose) { + return await redis.get(`otp:${purpose}:${email}`); } static async deleteOtp(email, purpose) { - return await Otp.deleteMany({ email, purpose }); + await redis.del(`otp:${purpose}:${email}`); } } export default AuthRepository; + diff --git a/server/modules/auth/service.js b/server/modules/auth/service.js index 778f55f..b33dcbc 100644 --- a/server/modules/auth/service.js +++ b/server/modules/auth/service.js @@ -15,78 +15,42 @@ class AuthService { // Hash password const hashedPassword = await bcrypt.hash(password, 10); - - // Create user - const user = await AuthRepository.createUser({ - name, - email, - password: hashedPassword, - isVerified: false - }); - // Generate OTP const plainOtp = generateOTP(); const hashedOtp = await bcrypt.hash(plainOtp, 4); - // Store hashed OTP - await AuthRepository.createOtp({ + // Store user data + hashed OTP in Redis instead of creating a DB record + await AuthRepository.storePendingUser(email, { + name, email, - otp: hashedOtp, - purpose: "signup" + password: hashedPassword, + otp: hashedOtp }); // Send verification email with plain OTP await sendVerificationOTP(email, plainOtp); - return { - message: "Registration successful. Please check your email for OTP verification.", - user: { - id: user._id, - name: user.name, - email: user.email, - isVerified: user.isVerified - } - }; + return { message: "OTP sent to your email for verification." }; } static async verifyOtp({ email, otp }) { - // Find OTP record - const otpRecord = await AuthRepository.findOtp(email, "signup"); - if (!otpRecord) { - throw new ApiError(400, "OTP expired or not found"); - } - - // Compare OTP - const isValidOtp = await bcrypt.compare(otp, otpRecord.otp); - if (!isValidOtp) { - throw new ApiError(400, "Invalid OTP"); - } + const pendingUser = await AuthRepository.getPendingUser(email); + if (!pendingUser) throw new ApiError(400, "OTP expired or invalid session"); - // Mark user as verified - await AuthRepository.updateUserVerification(email); + const isValidOtp = await bcrypt.compare(otp, pendingUser.otp); + if (!isValidOtp) throw new ApiError(400, "Invalid OTP"); - // Delete OTP record - await AuthRepository.deleteOtp(email, "signup"); - - // Get user and generate token - const user = await AuthRepository.findUserByEmailWithoutPassword(email); - const token = generateAccessToken({ - userId: user._id, - email: user.email, - role: user.role + // Persist to MongoDB ONLY after verification + const user = await AuthRepository.createUser({ + name: pendingUser.name, + email: pendingUser.email, + password: pendingUser.password }); - return { - message: "Email verified successfully", - token, - user: { - id: user._id, - name: user.name, - email: user.email, - role: user.role, - isVerified: true - } - }; + await AuthRepository.deletePendingUser(email); + + const token = generateAccessToken({ userId: user._id, email: user.email, role: user.role }); + return { message: "Email verified and account created", token, user }; } static async login({ email, password }) { @@ -102,11 +66,6 @@ class AuthService { throw new ApiError(401, "Invalid credentials"); } - // Check if verified - if (!user.isVerified) { - throw new ApiError(403, "Please verify your email first"); - } - // Generate token const token = generateAccessToken({ userId: user._id, @@ -118,19 +77,7 @@ class AuthService { user.activity.lastActive = new Date(); await user.save(); - return { - message: "Login successful", - token, - user: { - id: user._id, - name: user.name, - email: user.email, - role: user.role, - isVerified: user.isVerified, - profile: user.profile, - handles: user.handles - } - }; + return { message: "Login successful", token, user }; } static async forgotPassword({ email }) { @@ -144,12 +91,8 @@ class AuthService { const plainOtp = generateOTP(); const hashedOtp = await bcrypt.hash(plainOtp, 4); - // Store hashed OTP - await AuthRepository.createOtp({ - email, - otp: hashedOtp, - purpose: "forgot-password" - }); + // Store hashed OTP in Redis + await AuthRepository.storeOtp(email, "forgot-password", hashedOtp); // Send password reset email await sendPasswordResetOTP(email, plainOtp); @@ -160,14 +103,14 @@ class AuthService { } static async resetPassword({ email, otp, newPassword }) { - // Find OTP record - const otpRecord = await AuthRepository.findOtp(email, "forgot-password"); - if (!otpRecord) { + // Retrieve OTP record from Redis + const storedHashedOtp = await AuthRepository.getOtp(email, "forgot-password"); + if (!storedHashedOtp) { throw new ApiError(400, "Invalid or expired OTP"); } // Verify OTP - const isValidOtp = await bcrypt.compare(otp, otpRecord.otp); + const isValidOtp = await bcrypt.compare(otp, storedHashedOtp); if (!isValidOtp) { throw new ApiError(400, "Invalid or expired OTP"); } @@ -178,7 +121,7 @@ class AuthService { // Update user password await AuthRepository.updateUserPassword(email, hashedPassword); - // Delete OTP record + // Delete OTP record from Redis await AuthRepository.deleteOtp(email, "forgot-password"); return { @@ -187,36 +130,45 @@ class AuthService { } static async resendOtp({ email, purpose }) { - // Find user - const user = await AuthRepository.findUserByEmailWithoutPassword(email); - if (!user) { - throw new ApiError(404, "User not found"); - } - - // Check if already verified for signup - if (purpose === "signup" && user.isVerified) { - throw new ApiError(400, "Already verified"); - } - - // Delete existing OTPs - await AuthRepository.deleteOtp(email, purpose); - // Generate new OTP const plainOtp = generateOTP(); const hashedOtp = await bcrypt.hash(plainOtp, 4); - // Store hashed OTP - await AuthRepository.createOtp({ - email, - otp: hashedOtp, - purpose - }); - - // Send appropriate email if (purpose === "signup") { + // Check if user already exists in DB (meaning they are already verified) + const user = await AuthRepository.findUserByEmailWithoutPassword(email); + if (user) { + throw new ApiError(400, "Already verified"); + } + + // Find pending registration in Redis + const pendingUser = await AuthRepository.getPendingUser(email); + if (!pendingUser) { + throw new ApiError(404, "No pending registration found. Please register again."); + } + + // Update the OTP in Redis for the pending user + pendingUser.otp = hashedOtp; + await AuthRepository.storePendingUser(email, pendingUser); + + // Send verification email await sendVerificationOTP(email, plainOtp); - } else { + + } else if (purpose === "forgot-password") { + // Find user + const user = await AuthRepository.findUserByEmailWithoutPassword(email); + if (!user) { + throw new ApiError(404, "User not found"); + } + + // Overwrite existing OTP in Redis + await AuthRepository.storeOtp(email, purpose, hashedOtp); + + // Send password reset email await sendPasswordResetOTP(email, plainOtp); + + } else { + throw new ApiError(400, "Invalid OTP purpose"); } return { @@ -225,4 +177,4 @@ class AuthService { } } -export default AuthService; +export default AuthService; \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index a55a727..7b5d768 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -14,6 +14,7 @@ "cors": "^2.8.6", "dotenv": "^17.3.1", "express": "^5.2.1", + "ioredis": "^5.10.1", "jsonwebtoken": "^9.0.3", "mongoose": "^9.3.3", "nodemailer": "^8.0.4", @@ -29,6 +30,12 @@ "node": ">=18.0.0" } }, + "node_modules/@ioredis/commands": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz", + "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==", + "license": "MIT" + }, "node_modules/@mongodb-js/saslprep": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", @@ -152,6 +159,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/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/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -226,6 +242,15 @@ } } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -536,6 +561,30 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ioredis": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz", + "integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "1.5.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -603,12 +652,24 @@ "node": ">=18.0.0" } }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "license": "MIT" }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -950,6 +1011,27 @@ "node": ">= 0.10" } }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -1142,6 +1224,12 @@ "memory-pager": "^1.0.2" } }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", diff --git a/server/package.json b/server/package.json index 5780ae9..25ad192 100644 --- a/server/package.json +++ b/server/package.json @@ -16,6 +16,7 @@ "cors": "^2.8.6", "dotenv": "^17.3.1", "express": "^5.2.1", + "ioredis": "^5.10.1", "jsonwebtoken": "^9.0.3", "mongoose": "^9.3.3", "nodemailer": "^8.0.4", From ca575071a460fb90d367def1d2f95da6998c2dcf Mon Sep 17 00:00:00 2001 From: Vivek Kumar Date: Sun, 5 Apr 2026 23:07:40 +0530 Subject: [PATCH 2/8] feat:implement-redis-otp complete --- docker-compose.yaml | 26 ++ frontend/package-lock.json | 10 - server/Dockerfile.dev | 8 + server/REDIS.md | 28 ++ server/config/redis.js | 16 +- server/modules/auth/repository.js | 30 +- server/modules/auth/service.js | 1 - server/package-lock.json | 451 +++++++++++++++++++++++++----- server/package.json | 6 +- server/server.js | 2 + 10 files changed, 484 insertions(+), 94 deletions(-) create mode 100644 docker-compose.yaml create mode 100644 server/Dockerfile.dev create mode 100644 server/REDIS.md diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..94a1a3f --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,26 @@ +services: + mongo: + image: mongo + restart: always + volumes: + - mongo_data:/data/db + env_file: ./server/.env + redis: + image: redis + restart: always + backend: + build: + context: ./server + dockerfile: Dockerfile.dev + depends_on: + - mongo + - redis + ports: + - "8000:8000" + volumes: + - /app/node_modules + - ./server:/app + env_file: + - ./server/.env +volumes: + mongo_data: \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c63a78b..4634d27 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -58,7 +58,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1112,7 +1111,6 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1159,7 +1157,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1285,7 +1282,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1593,7 +1589,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2711,7 +2706,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2781,7 +2775,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2791,7 +2784,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3067,7 +3059,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -3192,7 +3183,6 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev new file mode 100644 index 0000000..68bc889 --- /dev/null +++ b/server/Dockerfile.dev @@ -0,0 +1,8 @@ +FROM node:lts-alpine + +WORKDIR /app +COPY ./package.json ./ +RUN npm install +COPY ./ ./ + +CMD ["npm", "run", "server"] \ No newline at end of file diff --git a/server/REDIS.md b/server/REDIS.md new file mode 100644 index 0000000..170c3f4 --- /dev/null +++ b/server/REDIS.md @@ -0,0 +1,28 @@ +# Feature: Redis-Based OTP Verification & Auth Flow Refactoring 🚀 + +## 📝 Description +This PR refactors the authentication and registration flow to utilize **Redis** for temporary data storage (OTPs and pending user sessions) instead of MongoDB. This architectural change significantly improves database hygiene by preventing the database from being cluttered with unverified or abandoned accounts. + +## ✨ Key Changes & Architectural Improvements + +1. **Optimized User Creation (Database Hygiene)** + - Removed the `isVerified` field from the `User` model. + - **Old Flow:** Users were created in MongoDB immediately upon registration with `isVerified: false`. + - **New Flow:** User data is now temporarily cached in Redis under the key `pending_user:` with a 10-minute expiration. The user is ONLY saved to MongoDB permanently *after* they successfully verify their OTP. + +2. **Redis-Powered OTP Management** + - Replaced the MongoDB `Otp.js` model with Redis key-value storage (`otp::`). + - Leverages Redis's native TTL (Time-To-Live) capabilities to automatically expire OTPs and pending users after 10 minutes, reducing database load and removing the need for MongoDB TTL indexes. + - Safely deleted the obsolete `server/models/Otp.js` file. + +3. **Refactored Auth Modules** + - Updated `AuthRepository` to include Redis cache interactions (`storePendingUser`, `getPendingUser`, `storeOtp`, etc.). + - Refactored `AuthService` and `AuthController` to seamlessly handle registration, forgot password, reset password, and resend OTP flows using the new Redis architecture. + +## 🛠 Prerequisites for Reviewers/Maintainers +Because of the new Redis dependency, the environment setup has been updated. To run this locally: + +1. Ensure you have a Redis server running locally or via Docker. +2. Add the following to your `server/.env` file: + ```env + REDIS_URL=redis://localhost:6379 # Or your respective Redis connection string \ No newline at end of file diff --git a/server/config/redis.js b/server/config/redis.js index 66559fc..d7c8bae 100644 --- a/server/config/redis.js +++ b/server/config/redis.js @@ -1,8 +1,14 @@ -import Redis from "ioredis"; +import { createClient } from 'redis'; -const redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379"); +export const redisClient = createClient({ url: process.env.REDIS_URL }); -redis.on("connect", () => console.log("Connected to Redis")); -redis.on("error", (err) => console.error("Redis Error:", err)); +redisClient.on('error', (err) => console.error('Redis Client Error:', err)); -export default redis; \ No newline at end of file +export const connectToRedis = async () => { + try { + await redisClient.connect(); + console.log("Connected to Redis cache."); + } catch (error) { + console.error("Failed to connect to Redis:", error); + } +}; \ No newline at end of file diff --git a/server/modules/auth/repository.js b/server/modules/auth/repository.js index 55df2d2..4a131ae 100644 --- a/server/modules/auth/repository.js +++ b/server/modules/auth/repository.js @@ -1,44 +1,56 @@ import User from "../../models/User.js"; -import redis from "../../config/redis.js"; +import {redisClient} from "../../config/redis.js"; class AuthRepository { static async createUser(userData) { const user = new User(userData); return await user.save(); } - + static async updateUserPassword(email, hashedPassword) { + return await User.findOneAndUpdate( + { email }, + { password: hashedPassword }, + { new: true } + ); + } static async findUserByEmail(email) { return await User.findOne({ email }).select("+password"); } static async findUserByEmailWithoutPassword(email) { - return await User.findOne({ email }); + try{ + const res = await User.findOne({ email }); + return res; + }catch(error){ + console.log("Error from repositry findUserByEmailWithoutPassword: ",error); + } + } // --- Redis OTP Methods --- static async storePendingUser(email, data, ttl = 600) { // Stores user data and OTP in Redis with a 10-minute expiry - await redis.set(`pending_user:${email}`, JSON.stringify(data), "EX", ttl); + await redisClient.set(`pending_user:${email}`, JSON.stringify(data), "EX", ttl); } static async getPendingUser(email) { - const data = await redis.get(`pending_user:${email}`); + const data = await redisClient.get(`pending_user:${email}`); return data ? JSON.parse(data) : null; } static async deletePendingUser(email) { - await redis.del(`pending_user:${email}`); + await redisClient.del(`pending_user:${email}`); } static async storeOtp(email, purpose, otp, ttl = 600) { - await redis.set(`otp:${purpose}:${email}`, otp, "EX", ttl); + await redisClient.set(`otp:${purpose}:${email}`, otp, "EX", ttl); } static async getOtp(email, purpose) { - return await redis.get(`otp:${purpose}:${email}`); + return await redisClient.get(`otp:${purpose}:${email}`); } static async deleteOtp(email, purpose) { - await redis.del(`otp:${purpose}:${email}`); + await redisClient.del(`otp:${purpose}:${email}`); } } diff --git a/server/modules/auth/service.js b/server/modules/auth/service.js index b33dcbc..be48cf8 100644 --- a/server/modules/auth/service.js +++ b/server/modules/auth/service.js @@ -26,7 +26,6 @@ class AuthService { password: hashedPassword, otp: hashedOtp }); - // Send verification email with plain OTP await sendVerificationOTP(email, plainOtp); diff --git a/server/package-lock.json b/server/package-lock.json index 7b5d768..c562eaa 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -14,10 +14,11 @@ "cors": "^2.8.6", "dotenv": "^17.3.1", "express": "^5.2.1", - "ioredis": "^5.10.1", "jsonwebtoken": "^9.0.3", "mongoose": "^9.3.3", "nodemailer": "^8.0.4", + "nodemon": "^3.1.14", + "redis": "^5.11.0", "zod": "^4.3.6" } }, @@ -30,12 +31,6 @@ "node": ">=18.0.0" } }, - "node_modules/@ioredis/commands": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz", - "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==", - "license": "MIT" - }, "node_modules/@mongodb-js/saslprep": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", @@ -45,6 +40,74 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@redis/bloom": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.11.0.tgz", + "integrity": "sha512-KYiVilAhAFN3057afUb/tfYJpsEyTkQB+tQcn5gVVA7DgcNOAj8lLxe4j8ov8BF6I9C1Fe/kwlbuAICcTMX8Lw==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.11.0" + } + }, + "node_modules/@redis/client": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz", + "integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@node-rs/xxhash": "^1.1.0" + }, + "peerDependenciesMeta": { + "@node-rs/xxhash": { + "optional": true + } + } + }, + "node_modules/@redis/json": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.11.0.tgz", + "integrity": "sha512-1iAy9kAtcD0quB21RbPTbUqqy+T2Uu2JxucwE+B4A+VaDbIRvpZR6DMqV8Iqaws2YxJYB3GC5JVNzPYio2ErUg==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.11.0" + } + }, + "node_modules/@redis/search": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.11.0.tgz", + "integrity": "sha512-g1l7f3Rnyk/xI99oGHIgWHSKFl45Re5YTIcO8j/JE8olz389yUFyz2+A6nqVy/Zi031VgPDWscbbgOk8hlhZ3g==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.11.0" + } + }, + "node_modules/@redis/time-series": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.11.0.tgz", + "integrity": "sha512-TWFeOcU4xkj0DkndnOyhtxvX1KWD+78UHT3XX3x3XRBUGWeQrKo3jqzDsZwxbggUgf9yLJr/akFHXru66X5UQA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.11.0" + } + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -73,6 +136,28 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/bcryptjs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", @@ -82,6 +167,18 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -106,6 +203,30 @@ "url": "https://opencollective.com/express" } }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bson": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz", @@ -159,6 +280,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/cluster-key-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", @@ -242,15 +387,6 @@ } } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -398,6 +534,18 @@ "url": "https://opencollective.com/express" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -437,6 +585,20 @@ "node": ">= 0.8" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -483,6 +645,18 @@ "node": ">= 0.4" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -495,6 +669,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -555,43 +738,67 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "license": "ISC" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ioredis": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz", - "integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==", + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "license": "MIT", "dependencies": { - "@ioredis/commands": "1.5.1", - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.4", - "denque": "^2.1.0", - "lodash.defaults": "^4.2.0", - "lodash.isarguments": "^3.1.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">=12.22.0" + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=0.12.0" } }, "node_modules/is-promise": { @@ -652,24 +859,12 @@ "node": ">=18.0.0" } }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "license": "MIT" - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "license": "MIT" }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "license": "MIT" - }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -767,6 +962,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mongodb": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz", @@ -889,6 +1099,43 @@ "node": ">=6.0.0" } }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -950,6 +1197,18 @@ "url": "https://opencollective.com/express" } }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -963,6 +1222,12 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1011,25 +1276,32 @@ "node": ">= 0.10" } }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, "engines": { - "node": ">=4" + "node": ">=8.10.0" } }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "node_modules/redis": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.11.0.tgz", + "integrity": "sha512-YwXjATVDT+AuxcyfOwZn046aml9jMlQPvU1VXIlLDVAExe0u93aTfPYSeRgG4p9Q/Jlkj+LXJ1XEoFV+j2JKcQ==", "license": "MIT", "dependencies": { - "redis-errors": "^1.0.0" + "@redis/bloom": "5.11.0", + "@redis/client": "5.11.0", + "@redis/json": "5.11.0", + "@redis/search": "5.11.0", + "@redis/time-series": "5.11.0" }, "engines": { - "node": ">=4" + "node": ">= 18" } }, "node_modules/router": { @@ -1215,6 +1487,18 @@ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", "license": "MIT" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -1224,12 +1508,6 @@ "memory-pager": "^1.0.2" } }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", - "license": "MIT" - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -1239,6 +1517,30 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1248,6 +1550,15 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", @@ -1274,6 +1585,12 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/server/package.json b/server/package.json index 25ad192..d4cc91f 100644 --- a/server/package.json +++ b/server/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "server": "nodemon server.js" }, "keywords": [], "author": "", @@ -16,10 +17,11 @@ "cors": "^2.8.6", "dotenv": "^17.3.1", "express": "^5.2.1", - "ioredis": "^5.10.1", "jsonwebtoken": "^9.0.3", "mongoose": "^9.3.3", "nodemailer": "^8.0.4", + "nodemon": "^3.1.14", + "redis": "^5.11.0", "zod": "^4.3.6" } } diff --git a/server/server.js b/server/server.js index 812011b..bd2ce03 100644 --- a/server/server.js +++ b/server/server.js @@ -1,11 +1,13 @@ import app from './app.js'; import connectDB from './config/db.js'; import './config/env.js'; +import { connectToRedis } from './config/redis.js'; const PORT = process.env.PORT || 5000; const startServer = async () => { await connectDB(); + await connectToRedis(); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); From ba7870d0561236b8311718bc5e07c785a782592c Mon Sep 17 00:00:00 2001 From: agentVivek Date: Tue, 7 Apr 2026 23:20:50 +0530 Subject: [PATCH 3/8] Update server/modules/auth/service.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- server/modules/auth/service.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/modules/auth/service.js b/server/modules/auth/service.js index be48cf8..30caf73 100644 --- a/server/modules/auth/service.js +++ b/server/modules/auth/service.js @@ -76,7 +76,10 @@ class AuthService { user.activity.lastActive = new Date(); await user.save(); - return { message: "Login successful", token, user }; + const sanitizedUser = user.toObject(); + delete sanitizedUser.password; + + return { message: "Login successful", token, user: sanitizedUser }; } static async forgotPassword({ email }) { From 89672cedb5a988de353b0f8015749faea228e34f Mon Sep 17 00:00:00 2001 From: agentVivek Date: Tue, 7 Apr 2026 23:22:04 +0530 Subject: [PATCH 4/8] Update server/modules/auth/service.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- server/modules/auth/service.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/modules/auth/service.js b/server/modules/auth/service.js index 30caf73..846e3d2 100644 --- a/server/modules/auth/service.js +++ b/server/modules/auth/service.js @@ -49,7 +49,10 @@ class AuthService { await AuthRepository.deletePendingUser(email); const token = generateAccessToken({ userId: user._id, email: user.email, role: user.role }); - return { message: "Email verified and account created", token, user }; + const sanitizedUser = user.toObject ? user.toObject() : { ...user }; + delete sanitizedUser.password; + + return { message: "Email verified and account created", token, user: sanitizedUser }; } static async login({ email, password }) { From 301cf719b82c98a0e3c0cb006c107a1faac29fd4 Mon Sep 17 00:00:00 2001 From: agentVivek Date: Tue, 7 Apr 2026 23:24:13 +0530 Subject: [PATCH 5/8] Update server/modules/auth/repository.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- server/modules/auth/repository.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/server/modules/auth/repository.js b/server/modules/auth/repository.js index 4a131ae..8982090 100644 --- a/server/modules/auth/repository.js +++ b/server/modules/auth/repository.js @@ -18,13 +18,7 @@ class AuthRepository { } static async findUserByEmailWithoutPassword(email) { - try{ - const res = await User.findOne({ email }); - return res; - }catch(error){ - console.log("Error from repositry findUserByEmailWithoutPassword: ",error); - } - + return await User.findOne({ email }); } // --- Redis OTP Methods --- From a87e07bfaaf691d9f0c764fe89b0b50d8087a656 Mon Sep 17 00:00:00 2001 From: agentVivek Date: Tue, 7 Apr 2026 23:25:51 +0530 Subject: [PATCH 6/8] Update server/package.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- server/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/package.json b/server/package.json index d4cc91f..dec5ddf 100644 --- a/server/package.json +++ b/server/package.json @@ -20,8 +20,10 @@ "jsonwebtoken": "^9.0.3", "mongoose": "^9.3.3", "nodemailer": "^8.0.4", - "nodemon": "^3.1.14", "redis": "^5.11.0", "zod": "^4.3.6" + }, + "devDependencies": { + "nodemon": "^3.1.14" } } From 6f1599000cb149b5ee3336f13d6ba00db772b3fa Mon Sep 17 00:00:00 2001 From: agentVivek Date: Tue, 7 Apr 2026 23:26:14 +0530 Subject: [PATCH 7/8] Update server/config/redis.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- server/config/redis.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/config/redis.js b/server/config/redis.js index d7c8bae..55770b8 100644 --- a/server/config/redis.js +++ b/server/config/redis.js @@ -10,5 +10,6 @@ export const connectToRedis = async () => { console.log("Connected to Redis cache."); } catch (error) { console.error("Failed to connect to Redis:", error); + throw error; } }; \ No newline at end of file From 05d6d5c2098e83912967e27109ad0d415a227626 Mon Sep 17 00:00:00 2001 From: agentVivek Date: Tue, 7 Apr 2026 23:26:47 +0530 Subject: [PATCH 8/8] Update server/models/User.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- server/models/User.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/models/User.js b/server/models/User.js index a3778d4..647f932 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -6,7 +6,6 @@ const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true, lowercase: true, index: true }, password: { type: String, required: true, minlength: 6, select: false }, role: { type: String, enum: ["user", "admin"], default: "user" }, - // isVerified: { type: Boolean, default: false }, authProvider: { type: String, enum: ["local", "google", "github"], default: "local" }, // Profile Info