npm package: @power-rent/phone-validation-adapter
Description: REST API for phone number validation using Twilio Lookup API
Latest version: 1.0.0

# Phone Validation Adapter

REST API adapter for phone number validation using Twilio Lookup API. This project was created to solve a critical issue: legacy applications running on Node.js < 14 cannot directly install the Twilio SDK due to version incompatibility. This adapter provides a simple REST interface that allows legacy systems to validate phone numbers by calling this service, eliminating the need to upgrade their entire Node.js infrastructure.

## Setup

### Prerequisites

- Node.js >= 18.0.0
- Twilio Account with API credentials

### Installation

```bash
npm install
```

### Environment Variables

Create a `.env` file in the root directory:

```
TWILIO_ACCOUNT_SID=your_account_sid
TWILIO_AUTH_TOKEN=your_auth_token
PORT=3000
```

**Important:** The `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` are required. Without them, the validation service will return HTTP 503.

### API Key Configuration

All requests to the validation endpoint require an API key and project name for authentication. API keys are stored in `src/validApiKeys.ts`:

```typescript
const validApiKeys: Record<string, string> = {
  RLC: process.env.API_KEY_RLC || '',
  TJS: process.env.API_KEY_TJS || ''
};
```

**Project Names:**
- `RLC` - Power Rent Rental Company
- `TJS` - TJ Services

**To add a new project:**

1. Add environment variable to `.env`:
```
API_KEY_NEW_PROJECT="your-secret-api-key"
```

2. Update `src/validApiKeys.ts`:
```typescript
const validApiKeys: Record<string, string> = {
  RLC: process.env.API_KEY_RLC || '',
  TJS: process.env.API_KEY_TJS || '',
  NEW_PROJECT: process.env.API_KEY_NEW_PROJECT || ''
};
```

3. Rebuild and restart:
```bash
npm run build
npm run start
```

**Important Security Notes:**
- Never commit actual API keys to version control
- Store keys in environment variables or a secrets manager
- Each client application should have its own unique key
- Rotate keys regularly (every 90 days recommended)

## API Endpoints

### POST /validate

Validates a phone number using Twilio Lookup API.

**Authentication Required:** `x-api-key` and `x-project-name` headers with valid credentials.

#### Request

```bash
curl -X POST http://localhost:3000/validate \
  -H "Content-Type: application/json" \
  -H "x-api-key: apple-blossom-elephant-rocket" \
  -H "x-project-name: RLC" \
  -d '{"phoneNumber": "+1234567890"}'
```

Request body:

```json
{
  "phoneNumber": "+1234567890"
}
```

Request headers:

| Header | Value | Required | Description |
|--------|-------|----------|-------------|
| `x-api-key` | API Key | Yes | Project-specific API key for authentication |
| `x-project-name` | Project Name | Yes | Project identifier (RLC or TJS) |
| `Content-Type` | application/json | Yes | Request content type |

#### Response (Success - HTTP 200)

```json
{
  "success": true,
  "isValid": true
}
```

#### Response (Invalid Number - HTTP 200)

```json
{
  "success": true,
  "isValid": false
}
```

#### Response (Unauthorized - HTTP 401)

Returned when API key or project name is missing or invalid:

```json
{
  "success": false,
  "error": "Invalid API key"
}
```

Or:

```json
{
  "success": false,
  "error": "Invalid project name"
}
```

#### Response (Client Error - HTTP 400)

```json
{
  "success": false,
  "error": "phoneNumber is required and must be a non-empty string"
}
```

#### Response (Service Unavailable - HTTP 503)

Returned when Twilio credentials are not configured:

```json
{
  "success": false,
  "error": "Validation service is temporarily unavailable"
}
```

#### Response (Server Error - HTTP 500)

Returned when Twilio API fails or times out:

```json
{
  "success": false,
  "error": "Phone validation failed. Please try again later."
}
```

## Error Handling Flow

This diagram shows how errors are handled at different stages:

```
POST /validate
│
├─ API Key Validation
│  ├─ x-project-name header missing?
│  │  └─ HTTP 401 (Unauthorized)
│  │     └─ Client error: invalid project name
│  │
│  ├─ x-project-name exists in whitelist?
│  │  └─ Continue to x-api-key validation
│  │
│  ├─ x-api-key header missing?
│  │  └─ HTTP 401 (Unauthorized)
│  │     └─ Client error: invalid API key
│  │
│  └─ x-api-key matches project?
│     └─ Continue to Input validation
│
├─ Input Validation
│  ├─ Missing or empty phoneNumber?
│  │  └─ HTTP 400 (Bad Request)
│  │     └─ Client error: invalid input
│  │
│  └─ phoneNumber is valid string?
│     └─ Continue to Twilio validation
│
├─ Configuration Check (twilioLookupValidation.ts)
│  ├─ TWILIO_ACCOUNT_SID missing?
│  │  ├─ Throw ConfigurationError
│  │  ├─ Log to Sentry
│  │  └─ server.ts catches it
│  │     └─ HTTP 503 (Service Unavailable)
│  │        └─ User sees: "Service temporarily unavailable"
│  │
│  ├─ TWILIO_AUTH_TOKEN missing?
│  │  ├─ Throw ConfigurationError
│  │  ├─ Log to Sentry
│  │  └─ server.ts catches it
│  │     └─ HTTP 503 (Service Unavailable)
│  │
│  └─ Credentials OK?
│     └─ Call Twilio API with 5s timeout
│
├─ Twilio API Call
│  ├─ Request times out (> 5s)?
│  │  ├─ Throw ValidationError
│  │  ├─ Log to Sentry
│  │  └─ server.ts catches it
│  │     └─ HTTP 500 (Internal Server Error)
│  │        └─ User sees: "Validation failed, try again"
│  │
│  ├─ Twilio API returns error?
│  │  ├─ Throw ValidationError
│  │  ├─ Log to Sentry
│  │  └─ server.ts catches it
│  │     └─ HTTP 500 (Internal Server Error)
│  │
│  ├─ Phone is valid?
│  │  └─ Return true
│  │     └─ HTTP 200 + isValid: true
│  │        └─ User can submit form
│  │
│  └─ Phone is invalid?
│     └─ Return false
│        └─ HTTP 200 + isValid: false
│           └─ User sees validation error message
│
└─ Error Logging (All paths)
   └─ Sentry with error type tag
      ├─ ConfigurationError → errorType: 'configuration'
      └─ ValidationError → errorType: 'validation'
```

## Key Design Decisions

### 1. Explicit Error Types

Two custom error classes distinguish between different failure modes:

- **ConfigurationError**: Missing Twilio credentials (HTTP 503)
- **ValidationError**: Twilio API failures or timeouts (HTTP 500)

This prevents silent failures where configuration problems would be masked as valid numbers.

### 2. Timeout Protection

Twilio API calls have a 5-second timeout. If exceeded:
- ValidationError is thrown
- HTTP 500 is returned
- User is informed of the failure

### 3. No Silent Failures

Previous implementation returned `true` for missing credentials. This caused:
- Users to believe their phone was validated
- Form submissions with invalid phone numbers
- Data quality issues

Current implementation:
- Fails explicitly with HTTP 503
- Informs users the service is unavailable
- Prevents invalid data submission

## Architecture & Security

This adapter is designed as a **backend microservice** that bridges the gap between legacy applications and Twilio. Understanding the architecture is critical for proper deployment and security.

### Client-Server Architecture

```
┌─────────────────────────────────┐
│  Client Application             │
│  (Frontend/Backend/Legacy app)  │
└────────────┬────────────────────┘
             │ HTTP POST /validate
             │ with x-api-key header
             ↓
┌─────────────────────────────────────────────┐
│  Phone Validation Adapter                   │
│  (this service)                             │
│  ✓ Validates API key                        │
│  ✓ Validates phone number format            │
│  ✓ Calls Twilio API                         │
│  ✓ Returns validation result                │
└────────────┬────────────────────────────────┘
             │
             ↓
     ┌───────────────────┐
     │   Twilio API      │
     └───────────────────┘
```

### Backend Responsibilities

The backend service (this adapter) is responsible for:

1. **Store API Keys Securely**

   API keys should **never** be hardcoded in production. Instead, use one of these approaches:

   - **Environment Variables** (recommended for simple deployments):
     ```bash
     VALID_API_KEYS=key1,key2,key3
     ```

   - **Secrets Manager** (AWS Secrets Manager, HashiCorp Vault, etc.):
     ```typescript
     // Pseudo-code
     const keys = await secretsManager.getSecret('phone-validation-keys');
     ```

   - **Database** (for dynamic key management):
     ```typescript
     // Pseudo-code
     const keys = await database.query('SELECT api_key FROM valid_keys WHERE active = true');
     ```

2. **Issue Keys to Clients**

   Provide a secure endpoint where authenticated clients can request API keys:

   ```typescript
   // Pseudo-code example
   app.post('/api-keys/request', (req, res) => {
     // Verify client identity (JWT, OAuth, mTLS, etc.)
     // Generate or retrieve API key
     // Return key to client
     res.json({ apiKey: 'generated-key' });
   });
   ```

   **Important:** This endpoint should:
   - Require strong authentication (JWT, OAuth, mTLS)
   - Rate limit requests
   - Log all key requests
   - Expire keys automatically
   - Allow key rotation

3. **Validate Keys Before Processing**

   Always verify API keys on the server before calling Twilio:

   ```typescript
   // Current implementation does this via middleware
   const authenticateApiKey = (req, res, next) => {
     const apiKey = req.headers['x-api-key'];
     const projectName = req.headers['x-project-name'];
     
     if (!checkIfKeyExists(projectName)) {
       res.status(401).json({ error: 'Invalid project name' });
       return;
     }
     
     if (!isValidApiKey({ key: apiKey, projectName })) {
       res.status(401).json({ error: 'Invalid API key' });
       return;
     }
     next();
   };
   ```

   Benefits:
   - Keys never exposed to clients
   - Project-specific key validation
   - Prevents unauthorized Twilio API calls
   - Reduces costs (Twilio charges per request)
   - Enables per-project rate limiting
   - Audit trail for all validation attempts

### Frontend/Client Responsibilities

Client applications should:

1. **Never hardcode API keys** in frontend code
2. **Request keys from backend** via secure endpoint
3. **Pass keys in request headers** (`x-api-key`)
4. **Handle 401 responses** gracefully
5. **Implement retry logic** for transient failures

Example client usage:

```typescript
// Get API key from backend (must be authenticated)
const apiKey = await fetch('/api/phone-validation-key', {
  headers: { Authorization: 'Bearer ' + userToken }
}).then(r => r.json()).then(r => r.apiKey);

// Get project name from backend config or user settings
const projectName = 'RLC'; // or 'TJS'

// Use key to validate phone
const result = await fetch('https://validator-service.example.com/validate', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': apiKey,
    'x-project-name': projectName
  },
  body: JSON.stringify({ phoneNumber: '+1234567890' })
});
```

### Security Best Practices

1. **Rotate API Keys Regularly** - Change keys every 90 days
2. **Use HTTPS** - All communication must be encrypted
3. **Rate Limit** - Prevent abuse of the validation endpoint
4. **Monitor Usage** - Track validation requests and costs
5. **Separate Credentials** - Twilio keys ≠ Adapter keys
6. **Audit Logging** - Log all API key usage
7. **Principle of Least Privilege** - Give clients minimum permissions needed

## Development

### Start Development Server

```bash
npm run dev
```

Server runs on `http://localhost:3000` by default.

### Build

```bash
npm run build
```

Output goes to `dist/` directory.

### Testing

```bash
npm run lint
npm run format:check
```

## License

MIT