Fix Turnstile verification encoding and admin login diagnostics

This commit is contained in:
Patrick Britton 2026-02-16 12:22:02 -06:00
parent 416fbc6fc1
commit e0fd5cea8c
2 changed files with 26 additions and 22 deletions

View file

@ -83,17 +83,24 @@ func (h *AdminHandler) AdminLogin(c *gin.Context) {
// Verify Turnstile token // Verify Turnstile token
if h.turnstileSecret != "" { if h.turnstileSecret != "" {
if strings.TrimSpace(req.TurnstileToken) == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Security verification failed"})
return
}
turnstileService := services.NewTurnstileService(h.turnstileSecret) turnstileService := services.NewTurnstileService(h.turnstileSecret)
remoteIP := c.ClientIP() turnstileResp, err := turnstileService.VerifyToken(req.TurnstileToken, "")
turnstileResp, err := turnstileService.VerifyToken(req.TurnstileToken, remoteIP)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Admin login: Turnstile verification failed") log.Error().Err(err).Msg("Admin login: Turnstile verification failed")
c.JSON(http.StatusBadRequest, gin.H{"error": "Security verification failed"}) c.JSON(http.StatusBadRequest, gin.H{"error": "Security verification failed"})
return return
} }
if !turnstileResp.Success { if !turnstileResp.Success {
log.Warn().Strs("errors", turnstileResp.ErrorCodes).Msg("Admin login: Turnstile validation failed") log.Warn().
c.JSON(http.StatusForbidden, gin.H{"error": "Security verification failed. Please try again."}) Strs("errors", turnstileResp.ErrorCodes).
Str("hostname", turnstileResp.Hostname).
Str("action", turnstileResp.Action).
Msg("Admin login: Turnstile validation failed")
c.JSON(http.StatusBadRequest, gin.H{"error": "Security verification failed"})
return return
} }
} }

View file

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"time" "time"
) )
@ -39,22 +40,18 @@ func (s *TurnstileService) VerifyToken(token, remoteIP string) (*TurnstileRespon
return &TurnstileResponse{Success: true}, nil return &TurnstileResponse{Success: true}, nil
} }
// Prepare the request data // Prepare the request data (properly form-encoded)
data := fmt.Sprintf( // Note: We intentionally do NOT send remoteip. In practice this often causes false negatives
"secret=%s&response=%s", // behind proxies/CDNs (Cloudflare), and Turnstile does not require it.
s.secretKey, form := url.Values{}
token, form.Set("secret", s.secretKey)
) form.Set("response", token)
if remoteIP != "" {
data += fmt.Sprintf("&remoteip=%s", remoteIP)
}
// Make the request to Cloudflare // Make the request to Cloudflare
resp, err := s.client.Post( resp, err := s.client.Post(
"https://challenges.cloudflare.com/turnstile/v0/siteverify", "https://challenges.cloudflare.com/turnstile/v0/siteverify",
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
bytes.NewBufferString(data), bytes.NewBufferString(form.Encode()),
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to verify turnstile token: %w", err) return nil, fmt.Errorf("failed to verify turnstile token: %w", err)