Forms are YAML in forms/, rendered by {{ form }} with a small script and stylesheet the build publishes. cl-cms endpoint serves /_cl/ for any number of sites by host: it checks Origin, a per-IP rate limit, text-only bodies, a trap field, a signed 3 s to 24 h token and optional Turnstile, then the declared fields (types, lengths, options, no line breaks in single-line fields), and delivers by SMTP over TLS, optionally appending to a 0600 JSONL file. It also answers search from a collection's search.json, so a site can search in the browser or from the endpoint. cl-cms serve can pass /_cl/ to an endpoint, and -reload=false leaves out the live-reload script for headless browsers.
40 lines
994 B
Go
40 lines
994 B
Go
package endpoint
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var turnstileURL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
|
|
|
// turnstileOK asks Cloudflare whether a Turnstile response is genuine.
|
|
func turnstileOK(ctx context.Context, secret, response, ip string) bool {
|
|
if secret == "" || response == "" {
|
|
return false
|
|
}
|
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
form := url.Values{"secret": {secret}, "response": {response}}
|
|
if ip != "" {
|
|
form.Set("remoteip", ip)
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, turnstileURL, strings.NewReader(form.Encode()))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
res, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer res.Body.Close()
|
|
var d struct {
|
|
Success bool `json:"success"`
|
|
}
|
|
return json.NewDecoder(res.Body).Decode(&d) == nil && d.Success
|
|
}
|