Files
tabungin/WHATSAPP_OTP_IMPLEMENTATION.md
dwindown 249f3a9d7d feat: remove OTP gate from transactions, fix categories auth, add implementation plan
- Remove OtpGateGuard from transactions controller (OTP verified at login)
- Fix categories controller to use authenticated user instead of TEMP_USER_ID
- Add comprehensive implementation plan document
- Update .env.example with WEB_APP_URL
- Prepare for admin dashboard development
2025-10-11 14:00:11 +07:00

8.4 KiB

📱 WhatsApp OTP & Phone Number Implementation

Completed Features:

1. Google Avatar Fix

  • Problem: Avatar not loading for Google OAuth users
  • Fix: Always update avatar from Google profile (not just when null)
  • File: apps/api/src/auth/auth.service.ts

2. Phone Number Field

  • Added phone field to User model
  • Unique constraint on phone number
  • Migration created and applied

3. WhatsApp OTP System

  • Full WhatsApp OTP implementation with mode support
  • Check number validity
  • Send OTP (test/live modes)
  • Verify OTP
  • Enable/disable WhatsApp OTP

📊 Database Changes:

Schema Updates (schema.prisma):

model User {
  // ... existing fields
  phone              String?       @unique
  otpEmailEnabled    Boolean       @default(false)
  otpWhatsappEnabled Boolean       @default(false)
  otpTotpEnabled     Boolean       @default(false)
  otpTotpSecret      String?
}

Migration:

  • Migration created: 20251010132022_add_phone_and_whatsapp_otp
  • Applied successfully
  • Prisma Client regenerated

🔧 Backend Implementation:

1. OTP Service (otp.service.ts):

New Methods:

// Send WhatsApp OTP
async sendWhatsappOtp(userId: string, mode: 'test' | 'live' = 'test')

// Verify WhatsApp OTP (for setup)
async verifyWhatsappOtp(userId: string, code: string)

// Verify WhatsApp OTP (for login)
async verifyWhatsappOtpForLogin(userId: string, code: string): Promise<boolean>

// Disable WhatsApp OTP
async disableWhatsappOtp(userId: string)

// Check if number is registered on WhatsApp
async checkWhatsappNumber(phone: string)

Webhook Payload Structure:

Email OTP:

{
  "method": "email",
  "mode": "test",  // or "live"
  "to": "user@example.com",
  "subject": "Tabungin - Your OTP Code",
  "message": "Your OTP code is: 123456...",
  "code": "123456"
}

WhatsApp OTP:

{
  "method": "whatsapp",
  "mode": "test",  // or "live"
  "phone": "+1234567890",
  "message": "Your Tabungin OTP code is: 123456...",
  "code": "123456"
}

Check WhatsApp Number:

{
  "method": "whatsapp",
  "mode": "checknumber",
  "phone": "+1234567890"
}

Expected Response:

{
  "isRegistered": true,
  "message": "Number is valid"
}

2. OTP Controller (otp.controller.ts):

New Endpoints:

// Send WhatsApp OTP (for setup in profile)
POST /api/otp/whatsapp/send
Body: { mode?: 'test' | 'live' }
Auth: Required

// Verify WhatsApp OTP (enable feature)
POST /api/otp/whatsapp/verify
Body: { code: string }
Auth: Required

// Disable WhatsApp OTP
POST /api/otp/whatsapp/disable
Auth: Required

// Check if phone number is registered on WhatsApp
POST /api/otp/whatsapp/check
Body: { phone: string }
Auth: Required

Updated Endpoints:

// Get OTP status (now includes phone and whatsappEnabled)
GET /api/otp/status
Response: {
  phone: string | null,
  emailEnabled: boolean,
  whatsappEnabled: boolean,
  totpEnabled: boolean,
  totpSecret?: string
}

3. Users Service (users.service.ts):

New Methods:

async updateProfile(userId: string, data: { 
  name?: string; 
  phone?: string 
})

Features:

  • Update name
  • Update phone number
  • Unique phone validation
  • Error handling for duplicate phone

4. Users Controller (users.controller.ts):

New Endpoints:

// Update user profile
PUT /api/users/profile
Body: { name?: string, phone?: string }
Auth: Required

5. Auth Service (auth.service.ts):

Updated Methods:

Login Flow:

async login(email: string, password: string) {
  // ... authentication
  
  // Check if OTP required
  const requiresOtp = user.otpEmailEnabled || 
                      user.otpWhatsappEnabled || 
                      user.otpTotpEnabled;
  
  if (requiresOtp) {
    // Send email OTP if enabled
    if (user.otpEmailEnabled) {
      await this.otpService.sendEmailOtp(user.id);
    }
    
    // Send WhatsApp OTP if enabled (LIVE mode)
    if (user.otpWhatsappEnabled) {
      await this.otpService.sendWhatsappOtp(user.id, 'live');
    }
    
    return {
      requiresOtp: true,
      availableMethods: {
        email: user.otpEmailEnabled,
        whatsapp: user.otpWhatsappEnabled,
        totp: user.otpTotpEnabled,
      },
      tempToken: this.generateTempToken(user.id, user.email),
    };
  }
}

Google OAuth Flow:

  • Same logic as login
  • Always updates avatar from Google
  • Sends WhatsApp OTP if enabled

OTP Verification:

async verifyOtpAndLogin(
  tempToken: string,
  otpCode: string,
  method: 'email' | 'whatsapp' | 'totp'
) {
  // ... verify temp token
  
  if (method === 'whatsapp') {
    const isValid = await this.otpService.verifyWhatsappOtpForLogin(
      userId,
      otpCode
    );
    if (!isValid) {
      throw new UnauthorizedException('Invalid WhatsApp OTP');
    }
  }
  
  // ... generate full JWT
}

📝 Mode Parameter Usage:

Email OTP:

  • mode: "test" - For setup in Profile page (logs to console)
  • mode: "live" - For login page (sends actual email)

WhatsApp OTP:

  • mode: "checknumber" - Check if number is registered on WhatsApp
  • mode: "test" - For setup in Profile page (logs to console)
  • mode: "live" - For login page (sends actual WhatsApp message)

🔄 Complete Flow:

Setup WhatsApp OTP (Profile Page):

  1. User enters phone number
  2. Frontend calls POST /api/users/profile with { phone: "+1234567890" }
  3. Frontend calls POST /api/otp/whatsapp/check with { phone: "+1234567890" }
  4. If valid, frontend calls POST /api/otp/whatsapp/send with { mode: "test" }
  5. Backend sends OTP via webhook with mode: "test"
  6. User enters OTP code
  7. Frontend calls POST /api/otp/whatsapp/verify with { code: "123456" }
  8. WhatsApp OTP enabled!

Login with WhatsApp OTP:

  1. User logs in with email/password or Google
  2. Backend detects otpWhatsappEnabled: true
  3. Backend calls sendWhatsappOtp(userId, 'live')
  4. Webhook receives request with mode: "live"
  5. User receives WhatsApp message
  6. User enters code on OTP page
  7. Frontend calls POST /api/auth/verify-otp with { method: "whatsapp", code: "123456" }
  8. Login successful!

🎯 Next Steps:

Frontend Implementation (TODO):

  1. Update Profile page to include phone number field
  2. Add WhatsApp OTP setup UI
  3. Add phone number validation
  4. Add "Check Number" button
  5. Update OTP verification page to support WhatsApp
  6. Restore original auth UI design from Git

n8n Webhook Configuration (TODO):

  1. Update webhook to handle method: "whatsapp"
  2. Handle mode: "checknumber" - check if number is registered
  3. Handle mode: "test" - log to console or test endpoint
  4. Handle mode: "live" - send actual WhatsApp message
  5. Return proper response format

📊 API Summary:

Endpoint Method Auth Body Purpose
/api/users/profile PUT { phone } Update phone
/api/otp/whatsapp/check POST { phone } Check number
/api/otp/whatsapp/send POST { mode } Send OTP
/api/otp/whatsapp/verify POST { code } Verify & enable
/api/otp/whatsapp/disable POST - Disable
/api/otp/status GET - Get status
/api/auth/verify-otp POST - { tempToken, code, method } Login verify

Files Modified:

Backend:

  1. prisma/schema.prisma - Added phone and otpWhatsappEnabled
  2. src/otp/otp.service.ts - Added WhatsApp methods
  3. src/otp/otp.controller.ts - Added WhatsApp endpoints
  4. src/users/users.service.ts - Added updateProfile
  5. src/users/users.controller.ts - Added PUT /profile
  6. src/auth/auth.service.ts - Updated login/OAuth flows

Database:

  1. Migration: 20251010132022_add_phone_and_whatsapp_otp

🎉 Status:

Backend Complete - All WhatsApp OTP endpoints implemented
Database Updated - Phone field and WhatsApp OTP flag added
Google Avatar Fixed - Always updates from Google profile
Frontend Pending - Need to add UI components
Auth UI Pending - Need to restore original design from Git


Backend is ready for WhatsApp OTP! Frontend implementation next. 🚀