317 lines
13 KiB
Markdown
317 lines
13 KiB
Markdown
# OTP Tables Analysis - What Your Code Actually Does
|
|
|
|
## Summary: **NO `user_id` in OTP Tables - This is CORRECT**
|
|
|
|
Your code uses **phone number only** for OTP tables. Users are created **AFTER** OTP verification.
|
|
|
|
---
|
|
|
|
## Database Tables
|
|
|
|
### 1. `otp_requests` Table (in `init.sql` lines 94-109)
|
|
**Status: ❌ NOT USED BY YOUR CODE**
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS otp_requests (
|
|
id UUID PRIMARY KEY,
|
|
phone_number VARCHAR(20) NOT NULL, -- NO user_id!
|
|
otp_hash VARCHAR(255) NOT NULL,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
consumed_at TIMESTAMPTZ, -- Extra field for tracking consumption
|
|
attempt_count INT NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
**Note:** This table exists in your database schema but is **never referenced in your code**.
|
|
|
|
---
|
|
|
|
### 2. `otp_codes` Table (ACTUALLY USED)
|
|
**Status: ✅ ACTIVELY USED BY YOUR CODE**
|
|
|
|
#### Database Schema (`init.sql` lines 115-122):
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS otp_codes (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
phone_number VARCHAR(20) NOT NULL, -- NO user_id!
|
|
otp_hash VARCHAR(255) NOT NULL,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
attempt_count INT NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
#### Code Definition (`src/services/otpService.js` lines 34-41):
|
|
```javascript
|
|
CREATE TABLE IF NOT EXISTS otp_codes (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
phone_number VARCHAR(20) NOT NULL, // ← NO user_id!
|
|
otp_hash VARCHAR(255) NOT NULL,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
attempt_count INT NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
**Both schemas match - NO `user_id` column!**
|
|
|
|
---
|
|
|
|
## How Your Code Uses `otp_codes`
|
|
|
|
### Step 1: Request OTP (`/auth/request-otp`)
|
|
|
|
**Location:** `src/routes/authRoutes.js` line 189
|
|
```javascript
|
|
const { code } = await createOtp(normalizedPhone);
|
|
```
|
|
|
|
**Function:** `src/services/otpService.js` lines 82-117
|
|
```javascript
|
|
async function createOtp(phoneNumber) {
|
|
await ensureOtpCodesTable();
|
|
const code = generateOtpCode();
|
|
const expiresAt = new Date(Date.now() + OTP_EXPIRY_MS);
|
|
const otpHash = await bcrypt.hash(code, 10);
|
|
|
|
// Encrypt phone number before storing
|
|
const encryptedPhone = encryptPhoneNumber(phoneNumber);
|
|
|
|
// Delete any existing OTPs for this phone number
|
|
await db.query(
|
|
'DELETE FROM otp_codes WHERE phone_number = $1 OR phone_number = $2',
|
|
[encryptedPhone, phoneNumber]
|
|
);
|
|
|
|
// ← INSERT: Only phone_number, NO user_id!
|
|
await db.query(
|
|
`INSERT INTO otp_codes (phone_number, otp_hash, expires_at, attempt_count)
|
|
VALUES ($1, $2, $3, 0)`,
|
|
[encryptedPhone, otpHash, expiresAt] // ← NO user_id here!
|
|
);
|
|
|
|
return { code };
|
|
}
|
|
```
|
|
|
|
**What happens:**
|
|
- ✅ User requests OTP with **phone number only**
|
|
- ✅ OTP stored in `otp_codes` with **phone_number only**
|
|
- ❌ **NO user_id** because user doesn't exist yet!
|
|
|
|
---
|
|
|
|
### Step 2: Verify OTP (`/auth/verify-otp`)
|
|
|
|
#### Part A: Verify OTP Code
|
|
**Location:** `src/routes/authRoutes.js` line 267
|
|
```javascript
|
|
const result = await verifyOtp(normalizedPhone, code);
|
|
```
|
|
|
|
**Function:** `src/services/otpService.js` lines 131-220
|
|
```javascript
|
|
async function verifyOtp(phoneNumber, code) {
|
|
await ensureOtpCodesTable();
|
|
|
|
const encryptedPhone = encryptPhoneNumber(phoneNumber);
|
|
|
|
// ← SELECT: Lookup by phone_number only!
|
|
const result = await db.query(
|
|
`SELECT id, otp_hash, expires_at, attempt_count, phone_number
|
|
FROM otp_codes
|
|
WHERE phone_number = $1 OR phone_number = $2 // ← NO user_id in WHERE clause!
|
|
ORDER BY created_at DESC
|
|
LIMIT 1`,
|
|
[encryptedPhone, phoneNumber]
|
|
);
|
|
|
|
// ... verification logic ...
|
|
|
|
// ← DELETE: Remove OTP after verification
|
|
await db.query('DELETE FROM otp_codes WHERE id = $1', [otpRecord.id]);
|
|
|
|
return { ok: true };
|
|
}
|
|
```
|
|
|
|
**What happens:**
|
|
- ✅ OTP verified using **phone_number only**
|
|
- ✅ OTP deleted from `otp_codes` table
|
|
- ❌ **Still NO user_id** at this point!
|
|
|
|
---
|
|
|
|
#### Part B: Create/Find User (AFTER OTP Verification)
|
|
**Location:** `src/routes/authRoutes.js` lines 310-335
|
|
```javascript
|
|
// ← This happens AFTER OTP verification succeeds!
|
|
|
|
// find or create user
|
|
const encryptedPhone = encryptPhoneNumber(normalizedPhone);
|
|
const phoneSearchParams = preparePhoneSearchParams(normalizedPhone);
|
|
|
|
let user;
|
|
const found = await db.query(
|
|
`SELECT id, phone_number, name, role, NULL::user_type_enum as user_type,
|
|
COALESCE(token_version, 1) as token_version
|
|
FROM users
|
|
WHERE phone_number = $1 OR phone_number = $2`,
|
|
phoneSearchParams
|
|
);
|
|
|
|
if (found.rows.length === 0) {
|
|
// ← CREATE USER HERE (after OTP verified!)
|
|
const inserted = await db.query(
|
|
`INSERT INTO users (phone_number) // ← Only phone_number, user gets auto-generated UUID id
|
|
VALUES ($1)
|
|
RETURNING id, phone_number, name, role, NULL::user_type_enum as user_type,
|
|
COALESCE(token_version, 1) as token_version`,
|
|
[encryptedPhone]
|
|
);
|
|
user = inserted.rows[0]; // ← user.id is created HERE!
|
|
} else {
|
|
user = found.rows[0]; // ← Existing user found
|
|
}
|
|
|
|
// Now user.id exists!
|
|
```
|
|
|
|
**What happens:**
|
|
- ✅ **AFTER** OTP verification succeeds
|
|
- ✅ User is **found or created** based on phone_number
|
|
- ✅ `user.id` (UUID) is **assigned at this point**
|
|
- ✅ This is when user_id first exists!
|
|
|
|
---
|
|
|
|
## Complete Flow Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ STEP 1: Request OTP │
|
|
│ POST /auth/request-otp │
|
|
│ Body: { phone_number: "+919876543210" } │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ INSERT INTO otp_codes │
|
|
│ (phone_number, otp_hash, expires_at, attempt_count) │
|
|
│ VALUES ('+919876543210', 'hash...', '2024-...', 0) │
|
|
│ │
|
|
│ ❌ NO user_id - User doesn't exist yet! │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ STEP 2: Verify OTP │
|
|
│ POST /auth/verify-otp │
|
|
│ Body: { phone_number: "+919876543210", code: "123456" } │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ SELECT FROM otp_codes │
|
|
│ WHERE phone_number = '+919876543210' │
|
|
│ │
|
|
│ ❌ NO user_id in query - phone_number only! │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼ (if OTP valid)
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ DELETE FROM otp_codes WHERE id = ... │
|
|
│ (OTP consumed) │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ STEP 3: Create/Find User │
|
|
│ (AFTER OTP verification succeeds) │
|
|
│ │
|
|
│ SELECT FROM users WHERE phone_number = '+919876543210' │
|
|
│ │
|
|
│ If NOT found: │
|
|
│ INSERT INTO users (phone_number) │
|
|
│ VALUES ('+919876543210') │
|
|
│ RETURNING id ← user.id CREATED HERE! │
|
|
│ │
|
|
│ ✅ user.id EXISTS NOW! │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ STEP 4: Create Session │
|
|
│ │
|
|
│ INSERT INTO refresh_tokens │
|
|
│ (user_id, token_hash, device_id, ...) │
|
|
│ VALUES (user.id, ...) ← user_id used here! │
|
|
│ │
|
|
│ INSERT INTO user_devices │
|
|
│ (user_id, device_identifier, ...) │
|
|
│ VALUES (user.id, ...) ← user_id used here! │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Why NO `user_id` in OTP Tables?
|
|
|
|
### ✅ Current Design (CORRECT):
|
|
|
|
1. **User doesn't exist when OTP is requested**
|
|
- OTP can be requested for phone numbers that don't have accounts yet
|
|
- User is created **after** successful OTP verification
|
|
|
|
2. **Phone number is the identifier**
|
|
- Phone number is UNIQUE in `users` table
|
|
- Phone number links OTP → User
|
|
- No need for user_id in OTP table
|
|
|
|
3. **Supports both registration and login**
|
|
- New users: OTP → Create user
|
|
- Existing users: OTP → Find user
|
|
- Same flow works for both!
|
|
|
|
### ❌ Alternative (Would Be Wrong):
|
|
|
|
If you added `user_id` to `otp_codes`:
|
|
- ❌ Can't request OTP for new users (user_id doesn't exist)
|
|
- ❌ Would need to create user before OTP (defeats purpose of verification)
|
|
- ❌ Complicates the flow unnecessarily
|
|
|
|
---
|
|
|
|
## Summary Table
|
|
|
|
| Table | Has `user_id`? | When Used | Purpose |
|
|
|-------|---------------|-----------|---------|
|
|
| `otp_codes` | ❌ **NO** | Request/Verify OTP | Store OTP codes before user exists |
|
|
| `otp_requests` | ❌ **NO** | ❌ **NOT USED** | Legacy table (ignore it) |
|
|
| `users` | ✅ **YES** (primary key) | After OTP verification | Store user accounts |
|
|
| `refresh_tokens` | ✅ **YES** | After OTP verification | Store sessions (needs user_id) |
|
|
| `user_devices` | ✅ **YES** | After OTP verification | Track devices (needs user_id) |
|
|
|
|
---
|
|
|
|
## Answer to Your Question
|
|
|
|
**Q: Should `otp_code` and `otp_req` include user_id?**
|
|
|
|
**A: ❌ NO** - Your current code is **correct as-is**:
|
|
|
|
1. **`otp_codes`** - Does NOT have `user_id` ✅ (correct)
|
|
2. **`otp_requests`** - Does NOT have `user_id`, but also **NOT USED** by your code
|
|
|
|
**Q: Are we going to create a user ID before and assign it, or only use phone number?**
|
|
|
|
**A:** Your code uses **phone number only** for OTP, then creates user_id **AFTER** OTP verification:
|
|
- OTP request/verify: Uses **phone_number only**
|
|
- User creation: Happens **AFTER** OTP verification succeeds
|
|
- User_id assignment: Happens when user is created/found in `users` table
|
|
|
|
This is the **standard and secure** pattern for phone-based authentication! ✅
|
|
|
|
|