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
if h.turnstileSecret != "" {
if strings.TrimSpace(req.TurnstileToken) == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Security verification failed"})
return
}
turnstileService := services.NewTurnstileService(h.turnstileSecret)
remoteIP := c.ClientIP()
turnstileResp, err := turnstileService.VerifyToken(req.TurnstileToken, remoteIP)
turnstileResp, err := turnstileService.VerifyToken(req.TurnstileToken, "")
if err != nil {
log.Error().Err(err).Msg("Admin login: Turnstile verification failed")
c.JSON(http.StatusBadRequest, gin.H{"error": "Security verification failed"})
return
}
if !turnstileResp.Success {
log.Warn().Strs("errors", turnstileResp.ErrorCodes).Msg("Admin login: Turnstile validation failed")
c.JSON(http.StatusForbidden, gin.H{"error": "Security verification failed. Please try again."})
log.Warn().
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
}
}

View file

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"time"
)
@ -39,22 +40,18 @@ func (s *TurnstileService) VerifyToken(token, remoteIP string) (*TurnstileRespon
return &TurnstileResponse{Success: true}, nil
}
// Prepare the request data
data := fmt.Sprintf(
"secret=%s&response=%s",
s.secretKey,
token,
)
if remoteIP != "" {
data += fmt.Sprintf("&remoteip=%s", remoteIP)
}
// Prepare the request data (properly form-encoded)
// Note: We intentionally do NOT send remoteip. In practice this often causes false negatives
// behind proxies/CDNs (Cloudflare), and Turnstile does not require it.
form := url.Values{}
form.Set("secret", s.secretKey)
form.Set("response", token)
// Make the request to Cloudflare
resp, err := s.client.Post(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
"application/x-www-form-urlencoded",
bytes.NewBufferString(data),
bytes.NewBufferString(form.Encode()),
)
if err != nil {
return nil, fmt.Errorf("failed to verify turnstile token: %w", err)
@ -79,13 +76,13 @@ func (s *TurnstileService) VerifyToken(token, remoteIP string) (*TurnstileRespon
// GetErrorMessage returns a user-friendly error message for error codes
func (s *TurnstileService) GetErrorMessage(errorCodes []string) string {
errorMessages := map[string]string{
"missing-input-secret": "Server configuration error",
"invalid-input-secret": "Server configuration error",
"missing-input-response": "Please complete the security check",
"invalid-input-response": "Security check failed, please try again",
"bad-request": "Invalid request format",
"timeout-or-duplicate": "Security check expired, please try again",
"internal-error": "Verification service unavailable",
"missing-input-secret": "Server configuration error",
"invalid-input-secret": "Server configuration error",
"missing-input-response": "Please complete the security check",
"invalid-input-response": "Security check failed, please try again",
"bad-request": "Invalid request format",
"timeout-or-duplicate": "Security check expired, please try again",
"internal-error": "Verification service unavailable",
}
for _, code := range errorCodes {