3x-ui/web/service/turnstile.go
Sora39831 90665c92f4 fix: harden registration with rate limiting, input validation, and security fixes
- Add per-IP rate limiter middleware (5 req/min) on /register endpoint
- Validate username (3-64 chars) and password (8-128 chars) with trim
- Use sentinel error ErrUsernameAlreadyExists instead of string matching
- Prevent TurnstileSecretKey exposure via admin settings API (json:"-")
- Skip json:"-" fields in UpdateAllSetting to avoid overwriting secrets
- Add SetTurnstileSecretKey setter for programmatic configuration
- Reuse package-level http.Client in Turnstile verification for connection pooling
- Add io.LimitReader to cap Turnstile response body size
- Log all Turnstile verification error paths for debugging
- Add invalidUsername/invalidPassword i18n keys to all 13 locales
2026-04-03 02:02:25 +08:00

50 lines
1.2 KiB
Go

package service
import (
"encoding/json"
"io"
"net/http"
"net/url"
"time"
"github.com/mhsanaei/3x-ui/v2/logger"
)
const turnstileVerifyURL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
var turnstileClient = &http.Client{Timeout: 10 * time.Second}
type turnstileResponse struct {
Success bool `json:"success"`
}
// VerifyTurnstile verifies a Cloudflare Turnstile token with the given secret key.
func VerifyTurnstile(secretKey, token, remoteIP string) bool {
form := url.Values{
"secret": {secretKey},
"response": {token},
}
if remoteIP != "" {
form.Set("remoteip", remoteIP)
}
resp, err := turnstileClient.PostForm(turnstileVerifyURL, form)
if err != nil {
logger.Warning("Turnstile verification request failed (network error):", err)
return false
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 4096))
if err != nil {
logger.Warning("Turnstile verification failed to read response:", err)
return false
}
var result turnstileResponse
if err := json.Unmarshal(body, &result); err != nil {
logger.Warning("Turnstile verification failed to parse response:", err)
return false
}
return result.Success
}