# Bongo Captcha Package - Cursor Rules

## Project Overview

This Laravel package provides CAPTCHA verification services for the Bongo framework. It supports multiple CAPTCHA providers (Google reCAPTCHA v3, Cloudflare Turnstile) through a driver-based architecture.

**Key Features:**
- Multi-provider support via Laravel Manager pattern
- Validation rule for Laravel form requests
- Helper functions and facade for easy access
- JavaScript integration for Google reCAPTCHA v3
- Score-based validation with configurable thresholds
- Action-based CAPTCHA tokens for granular control

## Package Structure

```
src/
├── Config/
│   └── captcha.php                 # Driver configuration
├── Contracts/
│   └── Captcha.php                 # CAPTCHA service interface
├── Exceptions/                      # Specific exception types
│   ├── CaptchaException.php        # Base exception
│   ├── InvalidActionException.php
│   ├── InvalidRequestException.php
│   ├── InvalidResponseException.php
│   ├── InvalidSecretException.php
│   ├── InvalidTokenException.php
│   ├── MissingSecretException.php
│   ├── MissingTokenException.php
│   └── TimeOutOrDuplicateException.php
├── Facades/
│   └── Captcha.php                 # Facade for CaptchaManager
├── Managers/
│   └── Captcha.php                 # Driver manager (extends Manager)
├── Rules/
│   └── Captcha.php                 # ValidationRule implementation
├── Services/
│   ├── Captcha.php                 # Abstract base service
│   ├── CloudflareCaptcha.php       # Cloudflare implementation
│   └── GoogleCaptcha.php           # Google reCAPTCHA v3 implementation
├── Translations/
│   └── en/
│       ├── messages.php            # Exception messages
│       └── validation.php          # Validation messages
├── CaptchaServiceProvider.php      # Service provider
└── helpers.php                     # Helper function: captcha()

public/
└── js/
    ├── cloudflare-captcha.js       # Cloudflare frontend (placeholder)
    └── google-captcha.js           # Google reCAPTCHA frontend
```

## Architecture Patterns

### 1. Service Provider Pattern

Extends `Bongo\Framework\Providers\AbstractServiceProvider`:

```php
class CaptchaServiceProvider extends AbstractServiceProvider
{
    protected string $module = 'captcha';

    public function register(): void
    {
        // Singleton binding for CaptchaManager
        $this->app->singleton(CaptchaManager::class, ...);
    }

    public function boot(): void
    {
        parent::boot(); // Auto-loads config, translations
        $this->bootAssets(); // Publishes JS assets
    }
}
```

**Auto-loaded by AbstractServiceProvider:**
- Config: `src/Config/captcha.php` → `config('captcha')`
- Translations: `src/Translations/en/*` → `trans('captcha::*')`

### 2. Driver Manager Pattern

Uses Laravel's `Illuminate\Support\Manager` for driver resolution:

```php
// Resolves driver based on config('captcha.driver')
$captcha = app(CaptchaManager::class)->driver('google');

// Or use default driver
$captcha = app(CaptchaManager::class);
```

Drivers are created via `create{Driver}Driver()` methods in `Managers\Captcha`.

### 3. Contract-Based Services

All CAPTCHA drivers implement `Contracts\Captcha`:

```php
interface Captcha
{
    public function verify(string $token, ?string $action = null): array;
    public function key(): string;
    public function init(): string;          // Returns script tag
    public function field(?string $action = null): string;  // Returns hidden field
    public function script(?string $action = null): string; // Returns init script

    // Fluent setters
    public function setRequest(Request $request): AbstractCaptcha;
    public function setReference(?string $reference = null): AbstractCaptcha;
    public function setAction(?string $action = null): AbstractCaptcha;
    // ... more setters
}
```

### 4. Abstract Service Pattern

`Services\Captcha` provides common functionality:

```php
abstract class Captcha
{
    protected ?string $name = null;        // Driver name (set by child)
    protected ?string $action = null;      // Action identifier
    protected ?string $reference = null;   // Field name
    protected ?string $uniqueId = null;    // Unique field ID

    public function boot(Request $request): void
    {
        // Chains fluent setters to initialize from config
        $this->setRequest($request)
            ->setReference()
            ->setDomain()
            ->setEndpoint()
            ->setKey()
            ->setSecret()
            ->setLocale();
    }

    protected function validateRequest(): void
    {
        // Validates required properties are set
    }
}
```

### 5. Validation Rule Pattern

Implements Laravel's `ValidationRule`:

```php
class Captcha implements ValidationRule
{
    public function __construct(
        string $action,      // Action to verify
        float $minScore = 0.5, // Minimum score threshold
        bool $enabled = true   // Enable/disable flag
    ) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        // Prevents duplicate validation calls
        if ($this->hasValidated) return;

        // Skips in testing environment
        if (app()->environment('testing')) return;

        // Verifies token and checks score
        $result = app(CaptchaManager::class)->verify($value, $this->action);
    }
}
```

## Coding Conventions

### 1. Strict Types

All PHP files declare strict types:

```php
<?php

declare(strict_types=1);

namespace Bongo\Captcha\Services;
```

### 2. Return Type Declarations

Always declare return types:

```php
public function verify(string $token, ?string $action = null): array
public function key(): string
protected function validateRequest(): void
```

### 3. Fluent Interfaces

Setters return `self` for method chaining:

```php
public function setAction(?string $action = null): self
{
    $this->action = $action;
    return $this;
}
```

### 4. Exception Handling

Use specific exception types from `Exceptions\` namespace:

```php
// Static factory methods for common exceptions
throw CaptchaException::invalidProperty(static::class, 'key');

// Specific exceptions for different failure scenarios
throw new MissingTokenException(trans('captcha::messages.missing_token'));
throw new InvalidActionException(trans('captcha::messages.invalid_action'));
```

### 5. Translation Keys

Use namespaced translation keys:

```php
trans('captcha::messages.invalid_token')
trans('captcha::validation.min_score')
```

### 6. Naming Conventions

- Service classes: `{Provider}Captcha` (e.g., `GoogleCaptcha`)
- Exceptions: Descriptive names ending in `Exception`
- Config keys: Snake case with underscores
- Methods: Camel case
- Properties: Camel case with visibility keywords

## Common Tasks

### Adding a New CAPTCHA Provider

1. **Create Service Class:**

```php
// src/Services/HcaptchaCaptcha.php
namespace Bongo\Captcha\Services;

class HcaptchaCaptcha extends Captcha implements CaptchaContract
{
    protected ?string $name = 'hcaptcha';

    public function verify(string $token, ?string $action = null): array
    {
        // Implement verification logic
    }

    public function init(): string
    {
        // Return script tag
    }

    public function field(?string $action = null): string
    {
        // Return hidden field HTML
    }

    public function script(?string $action = null): string
    {
        // Return initialization script
    }
}
```

2. **Add Driver Method:**

```php
// src/Managers/Captcha.php
public function createHcaptchaDriver(): CaptchaContract
{
    return $this->container->make(HcaptchaCaptcha::class);
}
```

3. **Add Configuration:**

```php
// src/Config/captcha.php
'services' => [
    'hcaptcha' => [
        'domain' => env('CAPTCHA_HCAPTCHA_DOMAIN', 'https://hcaptcha.com'),
        'endpoint' => env('CAPTCHA_HCAPTCHA_ENDPOINT', '/api'),
        'key' => env('CAPTCHA_HCAPTCHA_KEY'),
        'secret' => env('CAPTCHA_HCAPTCHA_SECRET'),
    ],
],
```

4. **Create Frontend JavaScript:**

```javascript
// public/js/hcaptcha-captcha.js
function HcaptchaCaptcha() {
    this.init = function(uniqueId) {
        // Initialize CAPTCHA
    }
}
```

### Using CAPTCHA in Forms

**1. Controller/View:**

```php
// Initialize CAPTCHA in blade template
{!! captcha()->init() !!}

// Add hidden field and script
{!! captcha()->field('login') !!}
{!! captcha()->script('login') !!}
```

**2. Validation Rule:**

```php
use Bongo\Captcha\Rules\Captcha;

public function rules(): array
{
    return [
        'captcha-response' => [
            'required',
            new Captcha(
                action: 'login',
                minScore: 0.5,
                enabled: config('captcha.enabled', true)
            ),
        ],
    ];
}
```

**3. Manual Verification:**

```php
use Bongo\Captcha\Facades\Captcha;

$result = Captcha::verify($request->input('captcha-response'), 'login');

if ($result['success'] && $result['score'] >= 0.5) {
    // CAPTCHA passed
}
```

### Testing CAPTCHA Protected Forms

The validation rule automatically skips verification in testing environment:

```php
// tests/Feature/LoginTest.php
public function test_login_form_submission()
{
    $response = $this->post('/login', [
        'email' => 'user@example.com',
        'password' => 'password',
        'captcha-response' => 'test-token', // Any value works in testing
    ]);

    $response->assertRedirect('/dashboard');
}
```

### Switching CAPTCHA Providers

**1. Environment Variable:**

```env
CAPTCHA_DRIVER=cloudflare
```

**2. Runtime:**

```php
$captcha = captcha()->driver('cloudflare');
$result = $captcha->verify($token, 'contact');
```

### Customizing Field Names

```php
// Default field name is 'captcha-response'
$captcha = captcha()->setReference('g-recaptcha-response');

// Generate field with custom name
echo $captcha->field('custom-action');
```

## Testing

### Running Tests

```bash
composer test           # Run PHPUnit
composer test:coverage  # Run with coverage
composer analyse        # Run PHPStan
composer format         # Run Pint (code style)
```

### Test Structure

```php
namespace Bongo\Captcha\Tests;

class ExampleTest extends TestCase
{
    public function test_example()
    {
        // Test implementation
    }
}
```

### Service Provider Setup

The `TestCase` automatically loads `CaptchaServiceProvider`:

```php
protected function getPackageProviders($app): array
{
    return [CaptchaServiceProvider::class];
}
```

## Key Implementation Details

### Google reCAPTCHA v3 Flow

1. **Page Load:**
   - `init()` outputs `<script>` tag loading Google API
   - `field()` outputs hidden input with `data-key` and `data-action`
   - `script()` outputs initialization JavaScript

2. **Form Submit:**
   - JavaScript intercepts submit event
   - Calls `grecaptcha.execute()` with action
   - Populates hidden field with token
   - Submits form

3. **Server Verification:**
   - Token sent to `https://www.google.com/recaptcha/api/siteverify`
   - Response includes `success`, `score`, `action`, `hostname`
   - Score compared against threshold

### Unique ID Generation

Each CAPTCHA instance gets a unique ID based on reference and action:

```php
public function makeUniqueId(): string
{
    return base64_encode("{$this->reference}-{$this->action}-");
}
```

This allows multiple CAPTCHA fields on the same page.

### Duplicate Validation Prevention

The `Captcha` rule uses a flag to prevent duplicate API calls:

```php
protected bool $hasValidated = false;

public function validate(string $attribute, mixed $value, Closure $fail): void
{
    if ($this->hasValidated) return; // Skip duplicate validation
    $this->hasValidated = true;

    // Perform verification
}
```

This is critical because Laravel calls `validate()` multiple times when retrieving validated data.

### Firefox Compatibility

The JavaScript handles Firefox's different form submission behaviour:

```javascript
// Bind submit buttons for Firefox
submitButtons.forEach((button) => {
    button.addEventListener('click', (event) => {
        event.preventDefault();
        this.generateToken();
    });
});

// Bind form submit for other browsers
form.addEventListener('submit', (event) => {
    event.preventDefault();
    this.generateToken();
});
```

## Dependencies

**Runtime:**
- `php: ^8.2`
- `bongo/framework: ^3.0` - Base AbstractServiceProvider
- `illuminate/contracts: ^10.0||^11.0` - Laravel contracts

**Development:**
- `laravel/pint` - Code style
- `larastan/larastan` - Static analysis
- `orchestra/testbench` - Package testing

## Configuration

Published via:

```bash
php artisan vendor:publish --tag=captcha:config
php artisan vendor:publish --tag=captcha:assets
```

**Environment Variables:**

```env
CAPTCHA_DRIVER=google

# Google reCAPTCHA
CAPTCHA_GOOGLE_KEY=your-site-key
CAPTCHA_GOOGLE_SECRET=your-secret-key

# Cloudflare Turnstile
CAPTCHA_CLOUDFLARE_KEY=your-site-key
CAPTCHA_CLOUDFLARE_SECRET=your-secret-key
```

## Code Style

Follows Laravel preset with customizations (see `pint.json`):

- Strict types declaration required
- No parentheses for `new` without arguments
- Nullable parameters without redundant `null` defaults

## Common Pitfalls

1. **Missing Environment Variables:**
   - Always set `CAPTCHA_GOOGLE_KEY` and `CAPTCHA_GOOGLE_SECRET`
   - Check `.env` file before testing

2. **Duplicate Validation:**
   - The `hasValidated` flag prevents this, but be aware of the pattern

3. **Testing Environment:**
   - CAPTCHA validation is automatically skipped in `testing` environment
   - Don't set `APP_ENV=testing` in production

4. **Multiple Forms:**
   - Use unique `action` values for each form
   - This generates unique field IDs

5. **JavaScript Loading:**
   - Ensure Google API script loads before form submission
   - Use `async defer` attributes

## Related Documentation

- **ARCHITECTURE.md** - Detailed architecture diagrams and class relationships
- **README.md** - Installation and usage guide
- **CLAUDE.md** - Quick reference for Claude Code
