Files

Captcha Middleware

CAPTCHA verification middleware package providing session-based captcha challenge and verification.

Overview

This package implements CAPTCHA verification middleware that protects routes by requiring users to complete a CAPTCHA challenge before accessing the protected resource. It supports pluggable providers (currently hCAPTCHA) and uses encrypted sessions for verification state.

Architecture

graph TD
    A[Client Request] --> B{Captcha Session?}
    B -->|Valid| C[Proceed to Backend]
    B -->|Invalid| D[Show CAPTCHA Page]

    D --> E{POST with Token?}
    E -->|Valid| F[Create Session<br/>Set Cookie]
    E -->|Invalid| G[Show Error]
    F --> C

    subgraph Captcha Provider
        H[hCAPTCHA API]
        D -->|Script/Form HTML| H
        F -->|Verify Token| H
    end

    subgraph Session Store
        I[CaptchaSessions<br/>jsonstore]
    end

    F --> I
    I -.->|Session Check| B

Captcha Flow

sequenceDiagram
    participant C as Client
    participant M as Middleware
    participant P as Provider
    participant S as Session Store
    participant B as Backend

    C->>M: Request (no session)
    M->>M: Check cookie
    M->>M: Session not found/expired
    M->>C: Send CAPTCHA Page

    C->>M: POST with captcha response
    M->>P: Verify token
    P-->>M: Verification result

    alt Verification successful
        M->>S: Store session
        M->>C: Set session cookie<br/>Redirect to protected path
        C->>M: Request (with session cookie)
        M->>S: Validate session
        M->>B: Forward request
    else Verification failed
        M->>C: Error: verification failed
    end

Core Components

Provider Interface

type Provider interface {
    // CSP directives for the captcha provider
    CSPDirectives() []string
    // CSP sources for the captcha provider
    CSPSources() []string
    // Verify the captcha response from the request
    Verify(r *http.Request) error
    // Session expiry duration after successful verification
    SessionExpiry() time.Duration
    // Script HTML to include in the page
    ScriptHTML() string
    // Form HTML to render the captcha widget
    FormHTML() string
}

ProviderBase

type ProviderBase struct {
    Expiry time.Duration `json:"session_expiry"` // Default: 24 hours
}

func (p *ProviderBase) SessionExpiry() time.Duration

hCAPTCHA Provider

type HcaptchaProvider struct {
    ProviderBase
    SiteKey string `json:"site_key" validate:"required"`
    Secret  string `json:"secret" validate:"required"`
}

// CSP Directives: script-src, frame-src, style-src, connect-src
// CSP Sources: https://hcaptcha.com, https://*.hcaptcha.com

Captcha Session

type CaptchaSession struct {
    ID    string    `json:"id"`
    Expiry time.Time `json:"expiry"`
}

var CaptchaSessions = jsonstore.Store[*CaptchaSession]("captcha_sessions")

func newCaptchaSession(p Provider) *CaptchaSession
func (s *CaptchaSession) expired() bool

Middleware Integration

type hCaptcha struct {
    captcha.HcaptchaProvider
}

func (h *hCaptcha) before(w http.ResponseWriter, r *http.Request) bool {
    return captcha.PreRequest(h, w, r)
}

var HCaptcha = NewMiddleware[hCaptcha]()

PreRequest Handler

func PreRequest(p Provider, w http.ResponseWriter, r *http.Request) (proceed bool)

This function:

  1. Checks for valid session cookie
  2. Validates session expiry
  3. Returns true if session is valid
  4. For non-HTML requests, returns 403 Forbidden
  5. For POST requests, verifies the captcha token
  6. For GET requests, renders the CAPTCHA challenge page

Configuration

hCAPTCHA Configuration

middleware:
  my-captcha:
    use: hcaptcha
    site_key: "YOUR_SITE_KEY"
    secret: "YOUR_SECRET"
    session_expiry: 24h # optional, default 24h

Route Configuration

routes:
  - host: example.com
    path: /admin
    middlewares:
      - my-captcha

Usage Examples

Basic Setup

import "github.com/yusing/godoxy/internal/net/gphttp/middleware"

hcaptchaMiddleware := middleware.HCaptcha.New(middleware.OptionsRaw{
    "site_key": "your-site-key",
    "secret":   "your-secret",
})

Using in Middleware Chain

# config/middlewares/admin-protection.yml
- use: captcha
  site_key: "${HCAPTCHA_SITE_KEY}"
  secret: "${HCAPTCHA_SECRET}"
  bypass:
    - type: CIDR
      value: 10.0.0.0/8

Session Management

Sessions are stored in a JSON-based store with the following properties:

  • Session ID: 32-byte CRNG (crypto/rand.Read) random hex string
  • Expiry: Configurable duration (default 24 hours)
  • Cookie: godoxy_captcha_session with HttpOnly flag
flowchart TD
    A[Session Created] --> B[Cookie Set]
    B --> C[Client Sends Cookie]
    C --> D{Session Valid?}
    D -->|Yes| E[Proceed]
    D -->|No| F{HTML Request?}
    F -->|Yes| G[Show CAPTCHA]
    F -->|No| H[403 Forbidden]

CSP Integration

The CAPTCHA provider supplies CSP directives that should be added to the response:

// hCAPTCHA CSP Directives
CSPDirectives() []string
// Returns: ["script-src", "frame-src", "style-src", "connect-src"]

CSPSources() []string
// Returns: ["https://hcaptcha.com", "https://*.hcaptcha.com"]

HTML Template

The package includes an embedded HTML template (captcha.html) that renders the CAPTCHA challenge page with:

  • Provider script (<script src="https://js.hcaptcha.com/1/api.js">)
  • Provider form (<div class="h-captcha" data-sitekey="...">)
  • Auto-submit callback on successful verification

Security Considerations

  1. Session Cookie: Uses HttpOnly flag to prevent JavaScript access
  2. Token Verification: Tokens are verified server-side with the CAPTCHA provider
  3. Remote IP: Client IP is included in verification request to prevent token reuse
  4. Session Expiry: Sessions expire after configurable duration
  5. Non-HTML Fallback: Non-HTML requests receive 403 without challenge page

Error Handling

var ErrCaptchaVerificationFailed = gperr.New("captcha verification failed")

// Verification errors are logged with request details
log.Warn().Err(err).Str("url", r.URL.String()).Str("remote_addr", r.RemoteAddr).Msg("failed to verify captcha")

Integration with GoDoxy

The captcha middleware integrates with GoDoxy's:

  • Authentication: Sessions are managed via auth.SetTokenCookie
  • Session Store: Uses jsonstore for persistent session storage
  • Middleware Framework: Implements RequestModifier interface