auth/SECURITY_SCENARIOS.md

7.5 KiB

Phone Number & Device Management - Security Scenarios

Current System Behavior

Scenario 1: Two Different Users with Same Phone Number

Current Behavior:

  • SECURITY ISSUE: Both users will access the SAME account
  • The system uses "find or create" logic:
    • If phone number exists → Logs into existing account
    • If phone number doesn't exist → Creates new account
  • The second person can:
    • See the first person's data
    • Modify the first person's profile
    • Access their listings/transactions
    • Change their account settings

Why this happens:

// src/routes/authRoutes.js (lines 88-107)
// find or create user
const found = await db.query(`SELECT ... FROM users WHERE phone_number = $1`, [phone]);
if (found.rows.length === 0) {
  // Create new user
} else {
  user = found.rows[0];  // ← Uses existing account!
}

Database Constraint:

  • phone_number has UNIQUE constraint (one account per phone number)

Scenario 2: Same User, Different Device

Current Behavior:

  • Works correctly: Same user can log in from multiple devices
  • Each device gets:
    • Its own refresh token (tracked by device_id)
    • Separate device record in user_devices table
    • Independent session
  • All devices can be logged in simultaneously
  • Each device's tokens work independently

How it works:

// Device tracking (lines 117-142)
INSERT INTO user_devices (user_id, device_identifier, ...)
VALUES ($1, $2, ...)
ON CONFLICT (user_id, device_identifier)
DO UPDATE SET last_seen_at = NOW(), ...

Database Constraint:

  • UNIQUE (user_id, device_identifier) → Same device can't be registered twice for same user

Example:

  1. User logs in on Phone A → Gets tokens, device_id = "phone-a"
  2. User logs in on Phone B → Gets different tokens, device_id = "phone-b"
  3. Both devices active simultaneously
  4. Logout on Phone A → Only revokes tokens for device_id = "phone-a"
  5. Phone B continues working

Security Risks

Risk 1: Phone Number Hijacking

Problem:

  • If someone gets access to your phone number (SIM swap, lost phone), they can log into your account
  • They receive the OTP and gain full account access

Mitigation (Recommended):

  1. Add additional verification (email, recovery questions)
  2. Implement device fingerprinting
  3. Alert user on new device login
  4. Allow user to see all active devices and revoke them

Risk 2: Shared Phone Numbers

Problem:

  • Family members sharing a phone
  • Business phone used by multiple employees
  • Second-hand phone numbers

Current Impact:

  • Account confusion
  • Data privacy violations
  • Unauthorized access

Solution 1: Warn User on First Login from New Device

Implementation:

// In verify-otp endpoint
const existingDevices = await db.query(
  `SELECT COUNT(*) FROM user_devices WHERE user_id = $1`,
  [user.id]
);

if (existingDevices.rows[0].count > 0) {
  // This is a new device for existing account
  // Could send notification to all other devices
  // Or require additional verification
}

Solution 2: Multi-Factor Authentication (MFA)

Add options:

  • Email verification for new device
  • SMS backup codes
  • Recovery questions
  • Authenticator app

Solution 3: Account Ownership Verification

Before creating account:

// Check if phone number recently used
const recentLogins = await db.query(
  `SELECT user_id, last_login_at 
   FROM users 
   WHERE phone_number = $1 
   AND last_login_at > NOW() - INTERVAL '7 days'`,
  [phoneNumber]
);

if (recentLogins.rows.length > 0) {
  // Require additional verification or block
}

Solution 4: Device Management Endpoint

Add API endpoints:

// GET /users/me/devices - List all active devices
// DELETE /users/me/devices/:device_id - Revoke specific device
// POST /users/me/devices/:device_id/verify - Verify device ownership

Solution 5: Session Limits

Limit concurrent sessions:

// Enforce maximum devices per user
const MAX_DEVICES = 5;
const deviceCount = await db.query(
  `SELECT COUNT(*) FROM user_devices WHERE user_id = $1 AND is_active = true`,
  [user.id]
);

if (deviceCount >= MAX_DEVICES) {
  // Revoke oldest device or require user to choose which to revoke
}

Immediate Actions Needed

1. Document Current Behavior

  • Update API documentation
  • Warn developers about phone number uniqueness
  • Add to user terms of service

2. Add Logging

// Log all login attempts
await db.query(
  `INSERT INTO auth_audit (user_id, action, status, device_id, ip_address)
   VALUES ($1, 'login', 'success', $2, $3)`,
  [user.id, devId, req.ip]
);

3. Add User Notifications

  • Email/SMS alert when login from new device
  • Show active devices in user profile
  • Allow device revocation

4. Consider Account Recovery Flow

  • Allow users to dispute account ownership
  • Support team can transfer ownership
  • Require additional verification for sensitive actions

Testing Scenarios

Test Case 1: Same Phone, Different Users

1. User A requests OTP for +919876543210
2. User A verifies OTP → Account created
3. User B requests OTP for +919876543210
4. User B verifies OTP → Logs into User A's account ❌

Test Case 2: Same User, Multiple Devices

1. User logs in on Device A → Gets tokens
2. User logs in on Device B → Gets different tokens
3. Both tokens work simultaneously ✅
4. User logs out on Device A → Device B still works ✅

Test Case 3: Device Re-login

1. User logs in on Device A
2. User logs out
3. User logs in again on Device A → New tokens issued ✅

Best Practices for Mobile App

1. Inform Users

// Show warning in app
if (response.needs_profile && existingAccount) {
    showDialog("This phone number is already registered. 
                You will be logged into the existing account.")
}

2. Show Active Devices

// Display all logged-in devices
GET /users/me/devices  List devices with last_seen_at

3. Allow Device Management

// Let users revoke devices
DELETE /users/me/devices/{device_id}

4. Handle Token Revocation

// If refresh returns 401, check if device was revoked
if (error.code == 401) {
    checkActiveDevices()
    if (currentDeviceRevoked) {
        forceReLogin()
    }
}

Database Queries for Analysis

Check accounts by phone

SELECT phone_number, COUNT(*) as account_count
FROM users
GROUP BY phone_number
HAVING COUNT(*) > 1;
-- Should return 0 rows (UNIQUE constraint)

Check devices per user

SELECT u.phone_number, COUNT(ud.id) as device_count
FROM users u
LEFT JOIN user_devices ud ON u.id = ud.user_id
WHERE ud.is_active = true
GROUP BY u.id, u.phone_number
ORDER BY device_count DESC;

Check concurrent sessions

SELECT u.phone_number, ud.device_identifier, ud.last_seen_at
FROM users u
JOIN user_devices ud ON u.id = ud.user_id
WHERE ud.is_active = true
AND ud.last_seen_at > NOW() - INTERVAL '1 hour'
ORDER BY ud.last_seen_at DESC;

Summary

Scenario Current Behavior Risk Level Action Needed
Same phone, different users Share same account 🔴 HIGH Add verification/alert
Same user, different devices Multiple active sessions 🟢 LOW Add device management UI
Same device, multiple logins Works (token refresh) 🟢 LOW None
Phone number hijacking Full account access 🔴 HIGH Add MFA, alerts