sockudo 2.9.0

A simple, fast, and secure WebSocket server for real-time applications.
Documentation
# Origin Validation Feature

## Overview

The origin validation feature provides an additional security layer for WebSocket connections by allowing per-app configuration of allowed origins. This feature works alongside CORS to restrict which domains can establish WebSocket connections to specific Pusher apps.

## Configuration Methods

The `allowed_origins` field can be configured through multiple methods depending on your app management driver.

### 1. Memory/Array Configuration (JSON)

Add the `allowed_origins` field to your app configuration in `config.json`:

```json
{
  "app_manager": {
    "driver": "memory",
    "array": {
      "apps": [
        {
          "id": "my-app",
          "key": "my-app-key",
          "secret": "my-app-secret",
          "allowed_origins": [
            "https://app.example.com",
            "https://*.staging.example.com",
            "http://localhost:3000"
          ]
        }
      ]
    }
  }
}
```

### 2. Environment Variables (Default App)

For the default app, configure allowed origins via environment variable:

```bash
SOCKUDO_DEFAULT_APP_ALLOWED_ORIGINS=https://app.example.com,*.staging.example.com,http://localhost:3000
```

### 3. MySQL Database Configuration

Database pooling can be tuned globally or per‑database.

- Global (applies to all SQL managers unless overridden):

```
DATABASE_POOLING_ENABLED=true
DATABASE_POOL_MIN=2
DATABASE_POOL_MAX=10
```

- MySQL‑specific overrides (take precedence when set):

```
DATABASE_MYSQL_POOL_MIN=4
DATABASE_MYSQL_POOL_MAX=32
```

Store apps in MySQL with the `allowed_origins` JSON column:

```sql
-- Create/update an app with allowed origins
INSERT INTO applications (
  id, `key`, secret, max_connections, 
  enable_client_messages, enabled, 
  max_client_events_per_second, allowed_origins
) VALUES (
  'my-app', 'my-app-key', 'my-app-secret', 1000,
  true, true, 100,
  JSON_ARRAY('https://app.example.com', '*.staging.example.com', 'http://localhost:3000')
) ON DUPLICATE KEY UPDATE allowed_origins = VALUES(allowed_origins);
```

Run the migration to add the column to existing tables:
```bash
mysql -u username -p database_name < migrations/mysql/001_add_allowed_origins.sql
```

### 4. PostgreSQL Database Configuration

Use global pooling envs above or Postgres‑specific overrides:

```
DATABASE_POSTGRES_POOL_MIN=2
DATABASE_POSTGRES_POOL_MAX=16
```

If pooling is disabled (`DATABASE_POOLING_ENABLED=false`), the legacy
`DATABASE_CONNECTION_POOL_SIZE` is used as the maximum connections.

Note: DynamoDB uses the AWS SDK client which manages its own
connection behavior; `pool_min`/`pool_max` do not apply to DynamoDB.

Store apps in PostgreSQL with the `allowed_origins` JSONB column:

```sql
-- Create/update an app with allowed origins
INSERT INTO applications (
  id, key, secret, max_connections, 
  enable_client_messages, enabled, 
  max_client_events_per_second, allowed_origins
) VALUES (
  'my-app', 'my-app-key', 'my-app-secret', 1000,
  true, true, 100,
  '["https://app.example.com", "*.staging.example.com", "http://localhost:3000"]'::jsonb
) ON CONFLICT (id) DO UPDATE SET allowed_origins = EXCLUDED.allowed_origins;
```

Run the migration to add the column to existing tables:
```bash
psql -U username -d database_name -f migrations/postgresql/001_add_allowed_origins.sql
```

### 5. DynamoDB Configuration

Store the allowed origins as a List attribute in DynamoDB:

```json
{
  "id": { "S": "my-app" },
  "key": { "S": "my-app-key" },
  "secret": { "S": "my-app-secret" },
  "allowed_origins": {
    "L": [
      { "S": "https://app.example.com" },
      { "S": "*.staging.example.com" },
      { "S": "http://localhost:3000" }
    ]
  }
}
```

Using AWS CLI:
```bash
aws dynamodb put-item \
    --table-name sockudo-applications \
    --item '{
        "id": {"S": "my-app"},
        "key": {"S": "my-app-key"},
        "secret": {"S": "my-app-secret"},
        "allowed_origins": {
            "L": [
                {"S": "https://app.example.com"},
                {"S": "*.staging.example.com"},
                {"S": "http://localhost:3000"}
            ]
        }
    }'
```

## Pattern Matching (CORS-like Behavior)

The origin validation works exactly like CORS, supporting both protocol-specific and protocol-agnostic patterns:

### 1. Protocol-Agnostic Patterns (Recommended)
```json
"allowed_origins": [
  "example.com",                // Matches both http:// and https://
  "api.example.com",            // Matches both protocols for subdomain
  "localhost:3000",             // Matches both http:// and https:// on port 3000
  "node1.ghslocal.com:444"      // Custom domain with port - matches both protocols
]
```

### 2. Protocol-Specific Patterns
```json
"allowed_origins": [
  "https://secure.example.com", // Only matches HTTPS
  "http://insecure.example.com" // Only matches HTTP
]
```

### 3. Wildcard Patterns
```json
"allowed_origins": [
  "*",                          // Matches any origin
  "*.example.com",              // Matches any subdomain (any protocol)
  "https://*.secure.com"        // Matches HTTPS subdomains only
]
```

## Validation Rules

1. **Empty or Missing Configuration**: If `allowed_origins` is not configured or is empty, all origins are allowed (backward compatible).

2. **CORS-like Matching**: 
   - **Protocol-agnostic**: `example.com` matches both `http://example.com` and `https://example.com`
   - **Protocol-specific**: `https://example.com` only matches `https://example.com`
   - **Exact port matching**: `localhost:3000` matches both protocols but requires exact port

3. **Wildcard Matching**: 
   - `*.domain.com` matches any subdomain including nested subdomains
   - Wildcards without protocol match any protocol
   - Wildcards with protocol only match that specific protocol

4. **Missing Origin Header**: If the Origin header is missing, the connection is rejected when origin validation is configured.

## Error Response

When a connection is rejected due to origin validation, the client receives a Pusher protocol error before the connection is closed:

```json
{
  "event": "pusher:error",
  "data": {
    "code": 4009,
    "message": "Origin not allowed"
  }
}
```

Error code 4009 indicates an unauthorized connection, following the Pusher protocol specification.

### Implementation Details

- **Validation Timing**: Origin validation occurs after the WebSocket upgrade completes but before the connection is fully established. This ensures clients receive the error message through the WebSocket connection.
- **Single Error Message**: The error is sent exactly once to avoid duplicate messages.
- **Metrics**: Connection errors due to origin validation are tracked as `origin_not_allowed` in metrics without duplication.

## Security Considerations

1. **Browser-Only Protection**: Origin headers can only be trusted from browser clients. Non-browser clients can spoof the Origin header.

2. **Defense in Depth**: This feature provides an additional security layer but should not be the only security measure.

3. **Works with CORS**: This feature complements CORS configuration and provides app-specific origin restrictions.

## Examples

### Example 1: Mixed Protocol Environment (Recommended)
```json
{
  "id": "production-app",
  "allowed_origins": [
    "www.myapp.com",              // Matches both HTTP and HTTPS
    "app.myapp.com",              // Matches both HTTP and HTTPS
    "https://secure.myapp.com"    // HTTPS only for sensitive areas
  ]
}
```

### Example 2: Development Environment
```json
{
  "id": "dev-app", 
  "allowed_origins": [
    "localhost:3000",             // Matches both protocols
    "localhost:3001",             // Matches both protocols
    "127.0.0.1:8080"              // Matches both protocols
  ]
}
```

### Example 3: Multi-node Environment  
```json
{
  "id": "multinode-app",
  "allowed_origins": [
    "node1.ghslocal.com:444",     // Matches both HTTP and HTTPS
    "node2.ghslocal.com:444",     // Matches both HTTP and HTTPS  
    "*.ghslocal.com"              // Wildcard for all subdomains
  ]
}
```

### Example 4: Security-Conscious Setup
```json
{
  "id": "secure-app",
  "allowed_origins": [
    "https://app.example.com",    // HTTPS only for production
    "https://admin.example.com",  // HTTPS only for admin
    "localhost:3000"              // Any protocol for local dev
  ]
}
```

## Testing

### Unit Tests
Run the origin validation unit tests:
```bash
cargo test origin_validation
```

### Integration Tests
The integration tests require setting up test apps with specific origin configurations:

```javascript
// test/automated/origin_validation.js
npm install
node origin_validation.js
```

## Migration Guide

### From No Origin Validation

1. Start with logging mode - configure allowed origins but monitor rejected connections
2. Gradually add all legitimate origins to the configuration
3. Enable strict validation once all origins are identified

### Backward Compatibility

- Apps without `allowed_origins` configured continue to work as before
- No changes required for existing deployments
- Origin validation is opt-in per app