> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# CAPTCHA

> CAPTCHA verification middleware supporting reCAPTCHA, hCaptcha, and Turnstile.

## Overview

The `captcha` middleware verifies CAPTCHA tokens to protect forms and APIs from bots. It supports multiple providers including Google reCAPTCHA (v2/v3), hCaptcha, and Cloudflare Turnstile.

Use it when you need:

* Bot protection for forms
* Spam prevention
* Account creation protection
* API abuse prevention

## Installation

```go theme={null}
import "github.com/go-mizu/mizu/middlewares/captcha"
```

## Quick Start

```go theme={null}
app := mizu.New()

// Google reCAPTCHA v2
app.Use(captcha.ReCaptchaV2("your-secret-key"))
```

## Configuration

### Options

| Option         | Type                                            | Default                       | Description                    |
| -------------- | ----------------------------------------------- | ----------------------------- | ------------------------------ |
| `Provider`     | `Provider`                                      | `ProviderRecaptchaV2`         | CAPTCHA provider               |
| `Secret`       | `string`                                        | -                             | Secret key from provider       |
| `TokenLookup`  | `string`                                        | `"form:g-recaptcha-response"` | Where to find token            |
| `MinScore`     | `float64`                                       | `0.5`                         | Minimum score for v3 providers |
| `Verifier`     | `func(token string, c *mizu.Ctx) (bool, error)` | -                             | Custom verifier                |
| `ErrorHandler` | `func(*mizu.Ctx, error) error`                  | -                             | Custom error handler           |
| `SkipPaths`    | `[]string`                                      | -                             | Paths to skip verification     |
| `Timeout`      | `time.Duration`                                 | `10s`                         | Verification timeout           |

### Providers

```go theme={null}
const (
    ProviderRecaptchaV2 // Google reCAPTCHA v2 (checkbox)
    ProviderRecaptchaV3 // Google reCAPTCHA v3 (invisible)
    ProviderHCaptcha    // hCaptcha
    ProviderTurnstile   // Cloudflare Turnstile
    ProviderCustom      // Custom verification
)
```

## Examples

### Google reCAPTCHA v2

```go theme={null}
app.Use(captcha.ReCaptchaV2(os.Getenv("RECAPTCHA_SECRET")))
```

Frontend:

```html theme={null}
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<form method="POST" action="/submit">
    <div class="g-recaptcha" data-sitekey="your-site-key"></div>
    <button type="submit">Submit</button>
</form>
```

### Google reCAPTCHA v3

```go theme={null}
// Require minimum score of 0.7
app.Use(captcha.ReCaptchaV3(os.Getenv("RECAPTCHA_SECRET"), 0.7))
```

Frontend:

```html theme={null}
<script src="https://www.google.com/recaptcha/api.js?render=your-site-key"></script>
<script>
grecaptcha.ready(function() {
    grecaptcha.execute('your-site-key', {action: 'submit'})
    .then(function(token) {
        document.getElementById('captcha-token').value = token;
    });
});
</script>
<form method="POST" action="/submit">
    <input type="hidden" id="captcha-token" name="g-recaptcha-response">
    <button type="submit">Submit</button>
</form>
```

### hCaptcha

```go theme={null}
app.Use(captcha.HCaptcha(os.Getenv("HCAPTCHA_SECRET")))
```

Frontend:

```html theme={null}
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<form method="POST" action="/submit">
    <div class="h-captcha" data-sitekey="your-site-key"></div>
    <button type="submit">Submit</button>
</form>
```

### Cloudflare Turnstile

```go theme={null}
app.Use(captcha.Turnstile(os.Getenv("TURNSTILE_SECRET")))
```

Frontend:

```html theme={null}
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form method="POST" action="/submit">
    <div class="cf-turnstile" data-sitekey="your-site-key"></div>
    <button type="submit">Submit</button>
</form>
```

### Skip Certain Paths

```go theme={null}
app.Use(captcha.WithOptions(captcha.Options{
    Provider:  captcha.ProviderRecaptchaV2,
    Secret:    os.Getenv("RECAPTCHA_SECRET"),
    SkipPaths: []string{
        "/api/webhook",    // External webhooks
        "/api/health",     // Health checks
    },
}))
```

### Custom Token Location

```go theme={null}
// Token from header instead of form
app.Use(captcha.WithOptions(captcha.Options{
    Provider:    captcha.ProviderRecaptchaV3,
    Secret:      secret,
    TokenLookup: "header:X-Captcha-Token",
}))
```

### Custom Error Handler

```go theme={null}
app.Use(captcha.WithOptions(captcha.Options{
    Provider: captcha.ProviderRecaptchaV2,
    Secret:   secret,
    ErrorHandler: func(c *mizu.Ctx, err error) error {
        if err == captcha.ErrMissingToken {
            return c.JSON(400, map[string]string{
                "error": "Please complete the CAPTCHA",
            })
        }
        return c.JSON(403, map[string]string{
            "error": "CAPTCHA verification failed. Please try again.",
        })
    },
}))
```

### Custom Verifier

```go theme={null}
app.Use(captcha.Custom(
    func(token string, c *mizu.Ctx) (bool, error) {
        // Custom verification logic
        return myVerifier.Verify(token, c.ClientIP())
    },
    "form:my-captcha-token",
))
```

### Route-Specific Protection

```go theme={null}
// Only protect specific routes
registerCaptcha := captcha.ReCaptchaV2(secret)
loginCaptcha := captcha.ReCaptchaV3(secret, 0.5)

app.Post("/register", registerHandler, registerCaptcha)
app.Post("/login", loginHandler, loginCaptcha)
```

## API Reference

### Functions

```go theme={null}
// New creates middleware with default provider
func New(secret string) mizu.Middleware

// WithOptions creates middleware with custom options
func WithOptions(opts Options) mizu.Middleware

// Provider-specific constructors
func ReCaptchaV2(secret string) mizu.Middleware
func ReCaptchaV3(secret string, minScore float64) mizu.Middleware
func HCaptcha(secret string) mizu.Middleware
func Turnstile(secret string) mizu.Middleware

// Custom creates middleware with custom verifier
func Custom(verifier func(string, *mizu.Ctx) (bool, error), tokenLookup string) mizu.Middleware
```

### Error Types

```go theme={null}
var (
    ErrMissingToken // Token not found in request
    ErrInvalidToken // Token verification failed
    ErrVerifyFailed // Verification request failed
)
```

## Provider Comparison

| Provider     | Type      | Privacy         | Free Tier |
| ------------ | --------- | --------------- | --------- |
| reCAPTCHA v2 | Checkbox  | Google          | Yes       |
| reCAPTCHA v3 | Invisible | Google          | Yes       |
| hCaptcha     | Checkbox  | Privacy-focused | Yes       |
| Turnstile    | Invisible | Cloudflare      | Yes       |

## Best Practices

* Use v3/invisible CAPTCHAs for better UX
* Set appropriate score thresholds for v3
* Monitor verification failures for tuning
* Have fallback for CAPTCHA failures
* Test with automation tools

## Technical Details

### Token Extraction

The middleware supports extracting tokens from three locations:

* **Form**: `form:field-name` - Extracts from POST form data
* **Header**: `header:Header-Name` - Extracts from HTTP headers
* **Query**: `query:param-name` - Extracts from URL query parameters

The `TokenLookup` option uses the format `source:key` where source is one of `form`, `header`, or `query`.

### Verification Flow

1. **Request Filtering**: Skips verification for safe methods (GET, HEAD, OPTIONS) and configured skip paths
2. **Token Extraction**: Retrieves token based on `TokenLookup` configuration
3. **Verification**:
   * For custom verifiers: Calls the provided `Verifier` function
   * For provider verifiers: Makes HTTP POST to provider's API with secret, token, and client IP
4. **Score Validation**: For v3 providers (reCAPTCHA v3, Turnstile), validates score against `MinScore` threshold
5. **Error Handling**: Uses custom `ErrorHandler` if provided, otherwise returns default 400 response

### Client IP Detection

The middleware detects client IP in the following order:

1. `X-Forwarded-For` header (first IP in comma-separated list)
2. `X-Real-IP` header
3. `RemoteAddr` from request (strips port if present)

The detected IP is sent to the provider API for enhanced verification.

### Provider API Integration

Each provider has a specific verification endpoint:

* **reCAPTCHA v2/v3**: `https://www.google.com/recaptcha/api/siteverify`
* **hCaptcha**: `https://hcaptcha.com/siteverify`
* **Turnstile**: `https://challenges.cloudflare.com/turnstile/v0/siteverify`

The middleware sends:

* `secret`: Your secret key
* `response`: The CAPTCHA token
* `remoteip`: Client IP address (optional)

### Default Values

| Setting     | Default Value                 |
| ----------- | ----------------------------- |
| Provider    | `ProviderRecaptchaV2`         |
| TokenLookup | `"form:g-recaptcha-response"` |
| MinScore    | `0.5`                         |
| Timeout     | `10s`                         |

### Error Types

The middleware defines three specific error types:

* `ErrMissingToken`: Token not found in the specified lookup location
* `ErrInvalidToken`: Token verification failed (invalid or low score)
* `ErrVerifyFailed`: HTTP request to provider API failed

## Testing

### Test Coverage

| Test Case                                  | Description                             | Expected Behavior                                  |
| ------------------------------------------ | --------------------------------------- | -------------------------------------------------- |
| `TestWithOptions_MissingToken`             | POST request without CAPTCHA token      | Returns 400 Bad Request                            |
| `TestWithOptions_CustomVerifier` (valid)   | Custom verifier with valid token        | Request proceeds (200 OK)                          |
| `TestWithOptions_CustomVerifier` (invalid) | Custom verifier with invalid token      | Returns 400 Bad Request                            |
| `TestWithOptions_SkipSafeMethods`          | GET request without token               | Request proceeds (200 OK), CAPTCHA skipped         |
| `TestWithOptions_SkipPaths`                | POST to path in SkipPaths               | Request proceeds (200 OK), CAPTCHA skipped         |
| `TestWithOptions_HeaderToken`              | Token provided via HTTP header          | Token extracted from header, verification succeeds |
| `TestWithOptions_QueryToken`               | Token provided via query parameter      | Token extracted from query, verification succeeds  |
| `TestWithOptions_CustomErrorHandler`       | Invalid token with custom error handler | Custom handler returns 403 Forbidden with JSON     |
| `TestCustom`                               | Custom verifier function                | Custom verification logic applied successfully     |
| `TestNew`                                  | Creating middleware with New()          | Middleware instance created with defaults          |
| `TestErrors`                               | Error message validation                | All error types return correct messages            |
| `TestExtractToken_InvalidLookup`           | TokenLookup without colon separator     | Returns 400 Bad Request (token not found)          |
| `TestVerifyToken_WithMockServer` (valid)   | Valid token verified by mock server     | Returns 200 OK                                     |
| `TestVerifyToken_WithMockServer` (invalid) | Invalid token verified by mock server   | Returns 400 Bad Request                            |
| `TestVerifyToken_RecaptchaV3Score` (high)  | reCAPTCHA v3 with score above threshold | Returns 200 OK                                     |
| `TestVerifyToken_RecaptchaV3Score` (low)   | reCAPTCHA v3 with score below threshold | Returns 400 Bad Request                            |
| `TestVerifyToken_ServerError`              | Provider API returns 500 error          | Returns 400 Bad Request                            |
| `TestVerifyToken_InvalidJSON`              | Provider returns malformed JSON         | Returns 400 Bad Request                            |
| `TestVerifyToken_UnknownProvider`          | Unknown provider type                   | Returns 400 Bad Request                            |
| `TestGetClientIP_XForwardedFor`            | X-Forwarded-For header present          | Extracts first IP from header, sends to provider   |
| `TestGetClientIP_XRealIP`                  | X-Real-IP header present                | Extracts IP from header, sends to provider         |
| `TestReCaptchaV2`                          | ReCaptchaV2 constructor                 | Creates middleware with reCAPTCHA v2 config        |
| `TestReCaptchaV3`                          | ReCaptchaV3 constructor                 | Creates middleware with reCAPTCHA v3 config        |
| `TestHCaptcha`                             | HCaptcha constructor                    | Creates middleware with hCaptcha config            |
| `TestTurnstile`                            | Turnstile constructor                   | Creates middleware with Turnstile config           |
| `TestWithOptions_VerifierError`            | Custom verifier returns error           | Returns 400 Bad Request                            |
| `TestWithOptions_HeadMethod`               | HEAD request without token              | Request proceeds (200 OK), CAPTCHA skipped         |
| `TestWithOptions_OptionsMethod`            | OPTIONS request without token           | Request proceeds (200 OK), CAPTCHA skipped         |

## Security Considerations

1. **Secret Key** - Never expose secret key in frontend code
2. **Score Tuning** - Adjust v3 scores based on traffic patterns
3. **Timeout** - Set reasonable timeout for verification
4. **Fallback** - Have manual review process for edge cases

## Related Middlewares

* [honeypot](/middlewares/honeypot) - Hidden field bot detection
* [ratelimit](/middlewares/ratelimit) - Rate limiting
