-
Notifications
You must be signed in to change notification settings - Fork 33
Feature/redis otp implementation #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2b80adb
3676e29
ca57507
ba7870d
89672ce
301cf71
a87e07b
6f15990
05d6d5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| FROM node:lts-alpine | ||
|
|
||
| WORKDIR /app | ||
| COPY ./package.json ./ | ||
| RUN npm install | ||
| COPY ./ ./ | ||
|
|
||
| CMD ["npm", "run", "server"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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:<email>` 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:<purpose>:<email>`). | ||
| - 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { createClient } from 'redis'; | ||
|
|
||
| export const redisClient = createClient({ url: process.env.REDIS_URL }); | ||
|
|
||
| redisClient.on('error', (err) => console.error('Redis Client Error:', err)); | ||
|
|
||
| export const connectToRedis = async () => { | ||
| try { | ||
| await redisClient.connect(); | ||
| console.log("Connected to Redis cache."); | ||
| } catch (error) { | ||
| console.error("Failed to connect to Redis:", error); | ||
|
agentVivek marked this conversation as resolved.
|
||
| throw error; | ||
| } | ||
| }; | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,18 @@ | ||
| import User from "../../models/User.js"; | ||
| import Otp from "../../models/Otp.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"); | ||
| } | ||
|
|
@@ -15,41 +21,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 redisClient.set(`pending_user:${email}`, JSON.stringify(data), "EX", ttl); | ||
| } | ||
|
Comment on lines
+25
to
+28
|
||
|
|
||
| static async updateUserVerification(email) { | ||
| return await User.findOneAndUpdate( | ||
| { email }, | ||
| { isVerified: true }, | ||
| { new: true } | ||
| ); | ||
| static async getPendingUser(email) { | ||
| const data = await redisClient.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 redisClient.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 redisClient.set(`otp:${purpose}:${email}`, otp, "EX", ttl); | ||
| } | ||
|
Comment on lines
+38
to
40
|
||
|
|
||
| static async findOtp(email, purpose) { | ||
| return await Otp.findOne({ email, purpose }).sort({ createdAt: -1 }); | ||
| static async getOtp(email, purpose) { | ||
| return await redisClient.get(`otp:${purpose}:${email}`); | ||
| } | ||
|
|
||
| static async deleteOtp(email, purpose) { | ||
| return await Otp.deleteMany({ email, purpose }); | ||
| await redisClient.del(`otp:${purpose}:${email}`); | ||
| } | ||
| } | ||
|
|
||
| export default AuthRepository; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With docker-compose,
REDIS_URL=redis://localhost:6379from.envwill point to the backend container itself, not theredisservice. Document or setREDIS_URLtoredis://redis:6379for the compose environment to work out-of-the-box.