Files
2025-10-02 02:40:11 -07:00

6.1 KiB

Turnstile Configuration Guide

Cloudflare Turnstile is optional and can be completely disabled for development or self-hosted deployments.

Quick Config

PUBLIC_ENABLE_TURNSTILE=false

What happens:

  • No Turnstile widget loads
  • No captcha required to log in
  • Login buttons work immediately
  • OAuth URLs don't include token parameter
  • No external script loaded from Cloudflare
  • Completely works offline

Perfect for:

  • Local development
  • Self-hosted deployments
  • Testing without internet

Enable Turnstile (Production)

PUBLIC_ENABLE_TURNSTILE=true
PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAABpqJe8FO0N84q0F

What happens:

  • 🔒 Turnstile widget loads on login page
  • 🔒 User must complete captcha before login
  • 🔒 OAuth URLs include ?token=... parameter
  • 🔒 Script loaded from challenges.cloudflare.com

Perfect for:

  • Production deployments
  • Bot protection
  • Public-facing instances

How It Works

Frontend Behavior

When ENABLE_TURNSTILE=false:

// Turnstile.svelte
if (!ENABLE_TURNSTILE) {
  captcha.set('turnstile-disabled');  // Dummy token
}

// LoginForm.svelte
function getOAuthUrl(provider) {
  let url = `${API_URL}/auth/${provider}`;
  // No token parameter added
  return url;
}

When ENABLE_TURNSTILE=true:

// Turnstile.svelte loads script
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" />

// LoginForm.svelte
function getOAuthUrl(provider) {
  let url = `${API_URL}/auth/${provider}?token=${captchaToken}`;
  return url;
}

Backend Requirements

Your backend should handle both cases:

// Example: src/routes/auth.ts
app.post("/auth/google", async (req, res) => {
  const turnstileToken = req.query.token;

  // If Turnstile is enabled in your backend, validate token
  if (process.env.ENABLE_TURNSTILE === 'true') {
    if (!turnstileToken) {
      return res.status(400).json({ error: "Turnstile token required" });
    }

    // Validate with Cloudflare API
    const isValid = await validateTurnstileToken(turnstileToken);
    if (!isValid) {
      return res.status(403).json({ error: "Invalid captcha" });
    }
  }

  // Continue with OAuth flow...
});

Getting a Turnstile Site Key

If you want to enable Turnstile:

  1. Go to https://dash.cloudflare.com
  2. Select "Turnstile" from the sidebar
  3. Create a new site
  4. Copy the Site Key (starts with 0x4)
  5. Add to .env:
    PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAAXXXXXXXXXXXXXXXX
    

Note: The example key 0x4AAAAAABpqJe8FO0N84q0F is from the original compiled frontend and may not work for you. Get your own key.


Build-Time vs Runtime

Important: Turnstile config is build-time only. It's embedded into the JavaScript bundle.

To change Turnstile settings:

  1. Update .env
  2. Rebuild: pnpm build
  3. Restart backend

You cannot change it at runtime without rebuilding.


Security Considerations

With Turnstile Disabled

Pros:

  • Simpler development
  • Works offline
  • No external dependencies
  • No privacy concerns

Cons:

  • No bot protection on login
  • Vulnerable to automated account creation
  • Anyone can spam OAuth endpoints

Recommendation: Fine for development and private deployments. Not recommended for public production.

With Turnstile Enabled

Pros:

  • Bot protection
  • Rate limiting
  • Industry standard (Cloudflare)

Cons:

  • Requires internet connection
  • External dependency
  • Cloudflare can track users (privacy concern)
  • Adds friction to login flow

Recommendation: Good for public production deployments with high traffic.


Environment Variable Reference

Variable Type Default Description
PUBLIC_ENABLE_TURNSTILE 'true' | 'false' 'false' Enable/disable Turnstile
PUBLIC_TURNSTILE_SITE_KEY string '0x4AAAAAABpqJe8FO0N84q0F' Cloudflare site key

Note: Must be strings 'true' or 'false', not booleans.


Testing

Test with Turnstile Disabled

# .env
PUBLIC_ENABLE_TURNSTILE=false

# Build and test
pnpm build
cd .. && pnpm start
# Open http://localhost:3000/join
# Should see login buttons immediately clickable

Test with Turnstile Enabled

# .env
PUBLIC_ENABLE_TURNSTILE=true
PUBLIC_TURNSTILE_SITE_KEY=your_key_here

# Build and test
pnpm build
cd .. && pnpm start
# Open http://localhost:3000/join
# Should see Turnstile widget before buttons work

Troubleshooting

"Turnstile failed to load"

  • Check PUBLIC_TURNSTILE_SITE_KEY is valid
  • Ensure internet connection
  • Check browser console for errors
  • Verify CSP allows challenges.cloudflare.com

"Login buttons don't work"

  • If Turnstile is enabled, complete captcha first
  • If disabled, check that captcha store has value
  • Check browser console for errors

"Token parameter missing"

  • Set PUBLIC_ENABLE_TURNSTILE=false if you don't want Turnstile
  • Rebuild after changing .env

CSP Configuration

If you enable Turnstile, your CSP must allow:

<meta http-equiv="Content-Security-Policy"
      content="script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://challenges.cloudflare.com blob:" />

This is already configured in src/app.html.


Alternative: reCAPTCHA

Want to use Google reCAPTCHA instead? You'll need to:

  1. Create a new Recaptcha.svelte component
  2. Update LoginForm.svelte to use it
  3. Load reCAPTCHA script instead of Turnstile
  4. Update backend to validate reCAPTCHA tokens

The architecture supports swapping captcha providers easily.


Summary

For Development:

PUBLIC_ENABLE_TURNSTILE=false

→ No captcha, login works immediately

For Production (with bot protection):

PUBLIC_ENABLE_TURNSTILE=true
PUBLIC_TURNSTILE_SITE_KEY=your_cloudflare_key

→ Captcha required before login

For Production (without bot protection):

PUBLIC_ENABLE_TURNSTILE=false

→ Same as development, but publicly accessible

Choose based on your needs!