···
···
func (s *Signup) signup(w http.ResponseWriter, r *http.Request) {
+
s.pages.Signup(w, pages.SignupParams{
+
CloudflareSiteKey: s.config.Cloudflare.TurnstileSiteKey,
http.Error(w, "signup is disabled", http.StatusFailedDependency)
emailId := r.FormValue("email")
+
cfToken := r.FormValue("cf-turnstile-response")
+
if err := s.validateCaptcha(cfToken, r); err != nil {
+
s.l.Warn("turnstile validation failed", "error", err)
+
s.pages.Notice(w, noticeId, "Captcha validation failed.")
if !email.IsValidEmail(emailId) {
s.pages.Notice(w, noticeId, "Invalid email address.")
···
+
type turnstileResponse struct {
+
Success bool `json:"success"`
+
ErrorCodes []string `json:"error-codes,omitempty"`
+
ChallengeTs string `json:"challenge_ts,omitempty"`
+
Hostname string `json:"hostname,omitempty"`
+
func (s *Signup) validateCaptcha(cfToken string, r *http.Request) error {
+
return errors.New("captcha token is empty")
+
if s.config.Cloudflare.TurnstileSecretKey == "" {
+
return errors.New("turnstile secret key not configured")
+
data.Set("secret", s.config.Cloudflare.TurnstileSecretKey)
+
data.Set("response", cfToken)
+
// include the client IP if we have it
+
if remoteIP := r.Header.Get("CF-Connecting-IP"); remoteIP != "" {
+
data.Set("remoteip", remoteIP)
+
} else if remoteIP := r.Header.Get("X-Forwarded-For"); remoteIP != "" {
+
if ips := strings.Split(remoteIP, ","); len(ips) > 0 {
+
data.Set("remoteip", strings.TrimSpace(ips[0]))
+
data.Set("remoteip", r.RemoteAddr)
+
resp, err := http.PostForm("https://challenges.cloudflare.com/turnstile/v0/siteverify", data)
+
return fmt.Errorf("failed to verify turnstile token: %w", err)
+
defer resp.Body.Close()
+
var turnstileResp turnstileResponse
+
if err := json.NewDecoder(resp.Body).Decode(&turnstileResp); err != nil {
+
return fmt.Errorf("failed to decode turnstile response: %w", err)
+
if !turnstileResp.Success {
+
s.l.Warn("turnstile validation failed", "error_codes", turnstileResp.ErrorCodes)
+
return errors.New("turnstile validation failed")