homepage/server/antispam/hcaptcha.go
2026-01-13 21:31:43 +01:00

83 lines
2.2 KiB
Go

package antispam
import (
"encoding/json"
"fmt"
"html/template"
"log"
"log/slog"
"net/http"
"net/url"
"strings"
)
type HCaptcha struct {
formField template.HTML
secretKey string
siteverifyEndpoint string
}
func (hcaptcha *HCaptcha) Script() template.HTML {
return `<script src="https://js.hcaptcha.com/1/api.js" async defer></script>`
}
func (hcaptcha *HCaptcha) FormField() template.HTML {
return hcaptcha.formField
}
func (hcaptcha *HCaptcha) CheckRequest(r *http.Request) (bool, error) {
err := r.ParseForm()
if err != nil {
return false, fmt.Errorf("error parsing form: %w", err)
}
token := r.PostForm.Get("x-captcha-response")
if token == "" {
slog.Debug("No hCaptcha response token in request")
return false, nil
}
form := url.Values{}
form.Set("secret", hcaptcha.secretKey)
form.Set("response", token)
res, err := http.PostForm(hcaptcha.siteverifyEndpoint, form)
if err != nil {
return false, fmt.Errorf("hCaptcha site verify error: %w", err)
}
var data struct {
Success bool `json:"success"`
Hostname string `json:"hostname"`
ErrorCodes []string `json:"error-codes"`
}
err = json.NewDecoder(res.Body).Decode(&data)
if err != nil {
return false, fmt.Errorf("hCaptcha site verify: JSON decode error: %w", err)
}
if res.StatusCode != http.StatusOK {
return false, fmt.Errorf("hCaptcha site verify: error codes %s, status %d", strings.Join(data.ErrorCodes, ", "), res.StatusCode)
}
result := data.Success && data.Hostname == r.URL.Hostname()
return result, nil
}
// NewHcaptcha returns an antispam implementation using the hCaptcha service.
func NewHcaptcha(siteKey string, secretKey string) *HCaptcha {
return newHcaptcha(siteKey, secretKey, "https://hcaptcha.com")
}
func newHcaptcha(siteKey string, secretKey string, endpoint string) *HCaptcha {
formField := `<div class="h-captcha" data-sitekey="` + template.HTMLEscapeString(siteKey) + `"></div>`
siteverifyEndpoint, err := url.JoinPath(endpoint, "siteverify")
if err != nil {
log.Panicf("error constructing siteverify endpoint: %v", err)
}
return &HCaptcha{
formField: template.HTML(formField),
secretKey: secretKey,
siteverifyEndpoint: siteverifyEndpoint,
}
}