# LiveComponents Security E2E Tests

Comprehensive security testing suite for LiveComponents framework covering CSRF protection, rate limiting, idempotency, input sanitization, authorization, and session security.

## Overview

This test suite validates critical security features:
- **CSRF Protection**: Token validation for state-changing actions
- **Rate Limiting**: Protection against abuse and DoS attacks
- **Idempotency**: Preventing duplicate action execution
- **Input Sanitization**: XSS and injection prevention
- **Authorization**: Access control and permissions
- **Session Security**: Session management and hijacking prevention
- **Content Security Policy**: CSP header enforcement

## Quick Start

### Prerequisites

```bash
# Ensure Playwright is installed
npm install

# Install browser binaries
npx playwright install chromium

# Ensure development server is running
make up
```

### Running Security Tests

```bash
# Run all security tests
npm run test:security

# Run specific security test category
npx playwright test security.spec.js --grep "CSRF Protection"
npx playwright test security.spec.js --grep "Rate Limiting"
npx playwright test security.spec.js --grep "Idempotency"

# Run with visible browser (for debugging)
npm run test:security:headed

# Run with debug mode
npm run test:security:debug
```

## Test Categories

### 1. CSRF Protection (4 tests)

**Purpose:** Validate Cross-Site Request Forgery protection for all state-changing actions.

#### Test: CSRF token included in requests

**Validates:**
- CSRF token present in action requests
- Token included in headers (`X-CSRF-Token`) or POST data (`_csrf_token`)
- Framework automatically handles token management

**Expected Behavior:**
```javascript
// Request includes CSRF token
headers: {
    'X-CSRF-Token': 'generated-token-value'
}

// OR in POST data
{
    _csrf_token: 'generated-token-value',
    action: 'increment',
    params: { ... }
}
```

#### Test: Reject action without CSRF token

**Validates:**
- Requests without CSRF token are rejected
- Appropriate error message returned
- No state changes occur

**Expected Response:**
```
Status: 403 Forbidden
Error: "CSRF token validation failed"
```

#### Test: Reject action with invalid CSRF token

**Validates:**
- Invalid/expired tokens are rejected
- Token tampering detected
- Security event logged

**Test Approach:**
```javascript
// Replace token with invalid value
headers: { 'X-CSRF-Token': 'invalid-token-12345' }

// Expected: 403 Forbidden
```

#### Test: CSRF token rotation

**Validates:**
- Tokens rotate after usage (optional, framework-dependent)
- New token provided in response
- Old token invalidated

**Rotation Strategy:**
- **Per-request rotation**: New token after each action
- **Time-based rotation**: New token after TTL
- **Session-based**: Token tied to session lifetime

### 2. Rate Limiting (5 tests)

**Purpose:** Prevent abuse through excessive action calls and DoS attacks.

#### Test: Enforce rate limit on rapid calls

**Configuration:**
- **Default Limit**: 10 requests per minute
- **Window**: 60 seconds sliding window
- **Action**: Block excess requests

**Test Approach:**
```javascript
// Trigger 20 rapid action calls
for (let i = 0; i < 20; i++) {
    await triggerAction();
}

// Expected: First 10 succeed, remaining 10 blocked
// Success rate: 50% (10/20)
```

**Expected Error:**
```
Status: 429 Too Many Requests
Error: "Rate limit exceeded"
Retry-After: 45  // seconds
```

#### Test: Retry-After header

**Validates:**
- `Retry-After` header present in 429 responses
- Header contains remaining cooldown time
- Client can use header for backoff

**Response Format:**
```http
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{
    "error": "Rate limit exceeded",
    "retryAfter": 60,
    "limit": 10,
    "window": 60
}
```

#### Test: Rate limit reset after cooldown

**Validates:**
- Rate limit resets after window expires
- Actions allowed again after cooldown
- No lingering restrictions

**Timeline:**
```
00:00 - First 10 requests succeed
00:01 - Next 10 requests blocked (rate limited)
00:01 - Retry-After: 59 seconds
01:00 - Window expires, limit resets
01:01 - New requests succeed
```

#### Test: Separate limits per action type

**Validates:**
- Different actions have independent rate limits
- Hitting limit on action A doesn't affect action B
- Granular control per action

**Configuration:**
```javascript
const RATE_LIMITS = {
    'increment': { limit: 10, window: 60 },
    'delete': { limit: 5, window: 300 },
    'search': { limit: 30, window: 60 }
};
```

#### Test: IP-based rate limiting

**Validates:**
- Rate limits applied per IP address
- Multiple users from same IP share limit
- Different IPs have independent limits

**Scenario:**
```
User A (IP: 192.168.1.1) - 10 requests
User B (IP: 192.168.1.1) - 10 requests
Result: Both users share 10-request limit for that IP
```

### 3. Idempotency (4 tests)

**Purpose:** Ensure critical operations execute exactly once, even with duplicate requests.

#### Test: Handle duplicate calls idempotently

**Validates:**
- Duplicate action calls with same idempotency key are ignored
- Operation executes exactly once
- State changes applied only once

**Test Approach:**
```javascript
// First call with idempotency key
await callAction('increment', { idempotencyKey: 'key-123' });
// Counter: 0 → 1

// Duplicate call (same key)
await callAction('increment', { idempotencyKey: 'key-123' });
// Counter: 1 (unchanged, duplicate ignored)
```

#### Test: Allow different idempotency keys

**Validates:**
- Different idempotency keys allow separate executions
- Each unique key represents distinct operation
- No interference between different keys

**Test Approach:**
```javascript
await callAction('increment', { idempotencyKey: 'key-1' });  // Executes
await callAction('increment', { idempotencyKey: 'key-2' });  // Executes
await callAction('increment', { idempotencyKey: 'key-1' });  // Ignored (duplicate)
```

#### Test: Idempotency key expiration

**Validates:**
- Keys expire after TTL (default: 24 hours)
- Expired keys allow re-execution
- Storage cleanup for old keys

**Timeline:**
```
00:00 - Action with key-123 executes
00:00 - Duplicate with key-123 ignored
24:00 - Key-123 expires
24:01 - Action with key-123 executes again (new operation)
```

#### Test: Cached result for duplicate action

**Validates:**
- Duplicate calls return cached result
- No server-side re-execution
- Consistent response for same key

**Expected Behavior:**
```javascript
const result1 = await callAction('createOrder', {
    idempotencyKey: 'order-123'
});
// Returns: { orderId: 'abc', total: 100 }

const result2 = await callAction('createOrder', {
    idempotencyKey: 'order-123'
});
// Returns: { orderId: 'abc', total: 100 } (cached, same result)
```

### 4. Input Sanitization & XSS Prevention (5 tests)

**Purpose:** Prevent XSS, injection attacks, and malicious input processing.

#### Test: Sanitize HTML in action parameters

**Validates:**
- HTML tags stripped or escaped
- Script tags prevented from execution
- Safe rendering of user input

**Attack Vectors Tested:**
```javascript
// XSS attempts
'<script>alert("XSS")</script>'
'<img src=x onerror=alert("XSS")>'
'<svg onload=alert("XSS")>'
'<iframe src="javascript:alert(\'XSS\')"></iframe>'
```

**Expected Output:**
```html
<!-- Input: <script>alert("XSS")</script> -->
<!-- Output: &lt;script&gt;alert("XSS")&lt;/script&gt; -->
<!-- Displayed as text, not executed -->
```

#### Test: Escape HTML entities

**Validates:**
- Special characters properly escaped
- No raw HTML injection
- Content-Type headers correct

**Entity Escaping:**
```
< → &lt;
> → &gt;
& → &amp;
" → &quot;
' → &#x27;
```

#### Test: Prevent JavaScript injection

**Validates:**
- `javascript:` protocol blocked in URLs
- Event handlers stripped
- No inline script execution

**Blocked Patterns:**
```javascript
javascript:alert('XSS')
data:text/html,<script>alert('XSS')</script>
vbscript:msgbox("XSS")
```

#### Test: Validate file paths

**Validates:**
- Path traversal attacks prevented
- Only allowed directories accessible
- Absolute paths rejected

**Attack Attempts:**
```
../../../etc/passwd
..\..\windows\system32\config\sam
/etc/passwd
C:\Windows\System32\config\SAM
```

**Expected:** All rejected with "Invalid path" error

#### Test: Prevent SQL injection

**Validates:**
- Parameterized queries used
- SQL keywords escaped
- No raw query concatenation

**Attack Vectors:**
```sql
'; DROP TABLE users; --
' OR '1'='1
admin'--
' UNION SELECT * FROM users--
```

**Expected:** Treated as literal search strings, no SQL execution

### 5. Authorization (3 tests)

**Purpose:** Enforce access control and permission checks.

#### Test: Reject unauthorized action calls

**Validates:**
- Actions requiring authentication are protected
- Anonymous users blocked
- Clear error message

**Protected Action:**
```php
#[Action]
#[RequiresAuth]
public function deleteAllData(): array
{
    // Only authenticated users
}
```

**Expected Response:**
```
Status: 401 Unauthorized
Error: "Authentication required"
```

#### Test: Allow authorized action calls

**Validates:**
- Authenticated users can access protected actions
- Valid session/token accepted
- Actions execute successfully

**Flow:**
```
1. User logs in → Session created
2. Call protected action → Success
3. Action executes → Result returned
```

#### Test: Role-based authorization

**Validates:**
- Different roles have different permissions
- Role checks enforced
- Insufficient permissions rejected

**Role Matrix:**
```
Action: deleteAllData
- admin: ✅ Allowed
- moderator: ❌ Forbidden
- user: ❌ Forbidden

Action: editOwnProfile
- admin: ✅ Allowed
- moderator: ✅ Allowed
- user: ✅ Allowed
```

### 6. Session Security (3 tests)

**Purpose:** Secure session management and hijacking prevention.

#### Test: Invalidate session after logout

**Validates:**
- Session properly destroyed on logout
- Session cookie removed
- Subsequent requests rejected

**Flow:**
```
1. Login → Session active
2. Call protected action → Success
3. Logout → Session destroyed
4. Call protected action → 401 Unauthorized
```

#### Test: Detect session hijacking

**Validates:**
- Session binding to IP/User-Agent
- Suspicious activity detected
- Stolen sessions rejected

**Detection Criteria:**
- IP address change
- User-Agent change
- Geo-location anomaly
- Concurrent sessions from different locations

**Response:**
```
Status: 403 Forbidden
Error: "Session invalid - security violation detected"
Action: Session terminated, user notified
```

#### Test: Enforce session timeout

**Validates:**
- Sessions expire after inactivity
- Timeout configurable (default: 30 minutes)
- Expired sessions rejected

**Timeline:**
```
00:00 - Login
00:15 - Action call (session refreshed)
00:45 - No activity for 30 minutes
00:46 - Action call → 401 Session Expired
```

### 7. Content Security Policy (2 tests)

**Purpose:** Enforce CSP headers to prevent injection attacks.

#### Test: CSP headers present

**Validates:**
- `Content-Security-Policy` header set
- Proper directives configured
- No unsafe configurations

**Expected Header:**
```http
Content-Security-Policy:
    default-src 'self';
    script-src 'self' 'nonce-xyz123';
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https:;
    connect-src 'self' wss://localhost;
    font-src 'self' https://fonts.gstatic.com;
```

#### Test: Block inline scripts via CSP

**Validates:**
- Inline scripts blocked by CSP
- Console errors for violations
- CSP reports generated

**Violation Detection:**
```javascript
// Attempt to inject inline script
const script = document.createElement('script');
script.textContent = 'alert("XSS")';
document.body.appendChild(script);

// Expected Console Error:
// "Refused to execute inline script because it violates
//  Content Security Policy directive: 'script-src self'"
```

## Test Page Requirements

Tests assume the following test page at `https://localhost/livecomponents/test/security`:

### Required HTML Elements

```html
<div data-component-id="counter:test">
    <div id="counter-value">0</div>
    <button id="trigger-action">Trigger Action</button>
    <div class="action-success" style="display:none">Success</div>
    <div class="error-message" style="display:none"></div>
    <div class="rate-limit-error" style="display:none" data-retry-after=""></div>
</div>

<div data-component-id="text:test">
    <div id="text-display"></div>
</div>

<div data-component-id="admin:test">
    <div class="authorization-error" style="display:none"></div>
</div>

<!-- Login Form -->
<form id="login-form">
    <input type="text" id="username" name="username" />
    <input type="password" id="password" name="password" />
    <button type="submit" id="login-btn">Login</button>
</form>

<div class="logged-in-indicator" style="display:none"></div>
<button id="logout-btn" style="display:none">Logout</button>

<!-- CSRF Token -->
<meta name="csrf-token" content="generated-token-value">
```

### Required Component Actions

```php
final readonly class SecurityTestComponent extends LiveComponent
{
    #[Action]
    #[RateLimit(requests: 10, window: 60)]
    public function triggerAction(): array
    {
        return ['success' => true];
    }

    #[Action]
    public function increment(string $idempotencyKey = null): array
    {
        // Idempotent increment
        if ($idempotencyKey && $this->hasExecuted($idempotencyKey)) {
            return $this->getCachedResult($idempotencyKey);
        }

        $newValue = $this->state->get('counter') + 1;
        $this->state->set('counter', $newValue);

        $result = ['value' => $newValue];

        if ($idempotencyKey) {
            $this->cacheResult($idempotencyKey, $result);
        }

        return $result;
    }

    #[Action]
    public function setText(string $text): array
    {
        // Sanitize HTML
        $sanitizedText = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
        $this->state->set('text', $sanitizedText);

        return ['text' => $sanitizedText];
    }

    #[Action]
    #[RequiresAuth]
    #[RequiresRole('admin')]
    public function performAdminAction(): array
    {
        return ['success' => true];
    }
}
```

## Configuration

### Environment Variables

```env
# CSRF Protection
CSRF_TOKEN_TTL=7200                  # 2 hours
CSRF_ROTATE_ON_ACTION=false          # Rotate token after each action

# Rate Limiting
RATE_LIMIT_ENABLED=true
RATE_LIMIT_DEFAULT=60                # Requests per window
RATE_LIMIT_WINDOW=60                 # Window in seconds
RATE_LIMIT_STORAGE=redis             # Storage backend

# Idempotency
IDEMPOTENCY_ENABLED=true
IDEMPOTENCY_TTL=86400                # 24 hours
IDEMPOTENCY_STORAGE=redis

# Session Security
SESSION_LIFETIME=1800                # 30 minutes
SESSION_SECURE=true                  # HTTPS only
SESSION_HTTP_ONLY=true               # No JavaScript access
SESSION_SAME_SITE=strict             # SameSite cookie attribute
SESSION_BIND_IP=true                 # Bind to IP address
SESSION_BIND_USER_AGENT=true         # Bind to User-Agent

# Content Security Policy
CSP_ENABLED=true
CSP_REPORT_ONLY=false                # Enforce CSP (not just report)
CSP_REPORT_URI=/csp-report           # CSP violation reporting endpoint
```

## Troubleshooting

### CSRF Tests Failing

**Symptoms:**
- CSRF token not found in requests
- All CSRF tests failing

**Solutions:**

1. **Verify CSRF middleware enabled:**
```php
// In middleware stack
new CsrfMiddleware($csrfTokenGenerator)
```

2. **Check meta tag presence:**
```html
<meta name="csrf-token" content="...">
```

3. **Verify JavaScript includes token:**
```javascript
const token = document.querySelector('meta[name="csrf-token"]').content;
// Include in requests
```

### Rate Limiting Not Working

**Symptoms:**
- All rapid requests succeed
- No 429 responses

**Solutions:**

1. **Check rate limit configuration:**
```bash
# Verify environment variables
docker exec php php -r "echo getenv('RATE_LIMIT_ENABLED');"
```

2. **Verify rate limiter initialized:**
```php
// Check DI container
$rateLimiter = $container->get(RateLimiter::class);
```

3. **Check storage backend:**
```bash
# For Redis backend
docker exec redis redis-cli KEYS "rate_limit:*"
```

### Idempotency Issues

**Symptoms:**
- Duplicate requests execute
- Idempotency keys not working

**Solutions:**

1. **Check idempotency key format:**
```javascript
// Must be unique per operation
idempotencyKey: `${userId}-${operationId}-${timestamp}`
```

2. **Verify storage:**
```bash
# For Redis
docker exec redis redis-cli KEYS "idempotency:*"
```

3. **Check TTL:**
```bash
# Verify keys not expiring too quickly
docker exec redis redis-cli TTL "idempotency:key-123"
```

### Authorization Failures

**Symptoms:**
- Authorized users rejected
- Role checks failing

**Solutions:**

1. **Verify session active:**
```javascript
const sessionActive = await page.evaluate(() => {
    return window.__sessionActive === true;
});
```

2. **Check role assignment:**
```php
// Verify user roles
$user->hasRole('admin'); // Should return true
```

3. **Review authorization middleware:**
```php
// Ensure middleware in correct order
[AuthMiddleware, RoleMiddleware, ...]
```

## CI/CD Integration

### GitHub Actions

```yaml
name: Security Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * *'  # Daily

jobs:
  security-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright
        run: npx playwright install --with-deps chromium

      - name: Start dev server
        run: make up

      - name: Run security tests
        run: npm run test:security

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: security-test-results
          path: test-results/

      - name: Security alert on failure
        if: failure()
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: '🚨 Security Tests Failed',
              labels: ['security', 'urgent'],
              body: 'Security tests failed. Immediate review required.'
            });
```

## Best Practices

### 1. Regular Security Testing

- Run security tests before every deployment
- Include in CI/CD pipeline
- Monitor for new vulnerabilities
- Update tests for new attack vectors

### 2. Defense in Depth

- Multiple security layers (CSRF + Rate Limit + Auth)
- Fail securely (block by default, allow explicitly)
- Log all security events
- Alert on suspicious patterns

### 3. Security Monitoring

- Track failed authentication attempts
- Monitor rate limit violations
- Alert on XSS attempts
- Log authorization failures

### 4. Incident Response

- Defined escalation procedures
- Security event playbooks
- Regular security drills
- Post-incident analysis

## Resources

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP CSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
- [Content Security Policy Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
- [Rate Limiting Best Practices](https://cloud.google.com/architecture/rate-limiting-strategies-techniques)

## Support

For security issues or questions:
1. Review this documentation
2. Check framework security documentation
3. Consult OWASP guidelines
4. **Report security vulnerabilities privately** to security@example.com
5. Create GitHub issue for non-security test failures
