Documentation
¶
Overview ¶
Package spam holds the site-wide spam protection settings and the checks the public form handlers run on every submission.
Index ¶
- Constants
- Variables
- func CheckToken(key []byte, slug, tok string, now time.Time) (issued time.Time, ok bool)
- func EvaluateRules(s Settings, data map[string]interface{}) (reason string, tripped bool)
- func IssueToken(key []byte, slug string, now time.Time) string
- func NewClient() *http.Client
- func SealSecret(key []byte, secret string) ([]byte, error)
- func SecretKey(serverSecret string) []byte
- func TimingKey(serverSecret string) []byte
- type HTTPVerifier
- type Input
- type Pipeline
- type Querier
- type Result
- type Settings
- type SettingsReader
- type Store
- type Verdict
- type Verifier
Constants ¶
const ( ReasonLinks = "too many links" ReasonWord = "blocked word" ReasonScript = "blocked script" )
Reasons EvaluateRules reports. They name the rule only, never user content.
const ( ProviderNone = "none" ProviderTurnstile = "turnstile" ProviderRecaptcha = "recaptcha" ProviderHCaptcha = "hcaptcha" )
Captcha providers.
const ( ActionSpam = "spam" ActionRefuse = "refuse" )
What happens when a content rule trips.
const ( MaxMinSeconds = 60 MaxMaxLinks = 50 MaxBlockedWords = 200 MaxWordLen = 100 MaxSiteKeyLen = 200 )
Limits on the numeric and list settings.
const (
RecaptchaAction = "hm_form"
)
const ( // TokenMaxAge is how long an issued timing token stays usable. TokenMaxAge = 24 * time.Hour )
Variables ¶
var DefaultEndpoints = map[string]string{
ProviderTurnstile: "https://challenges.cloudflare.com/turnstile/v0/siteverify",
ProviderRecaptcha: "https://www.google.com/recaptcha/api/siteverify",
ProviderHCaptcha: "https://api.hcaptcha.com/siteverify",
}
DefaultEndpoints are the provider siteverify URLs.
var Providers = []string{ProviderNone, ProviderTurnstile, ProviderRecaptcha, ProviderHCaptcha}
Providers lists the valid provider values.
var Scripts = []string{"cyrillic", "cjk", "arabic", "hebrew", "thai", "devanagari"}
Scripts lists the writing systems a site can block.
Functions ¶
func CheckToken ¶
CheckToken returns when tok was issued. ok is false for a malformed, forged, future-dated or expired token; callers then treat the timing as missing.
func EvaluateRules ¶
EvaluateRules checks the submitted values against the content rules and reports the first rule that trips. Control fields must already be stripped.
func IssueToken ¶
IssueToken returns a stateless token proving when a visitor loaded the form with this slug: v1.<unix seconds>.<base64url HMAC>.
func NewClient ¶
NewClient returns the HTTP client verification uses: SSRF-safe, 5s timeout, no redirects.
func SealSecret ¶
SealSecret returns the sealed form of a captcha secret for Settings.SecretSealed.
Types ¶
type HTTPVerifier ¶
type HTTPVerifier struct {
// contains filtered or unexported fields
}
HTTPVerifier verifies tokens over HTTPS.
func NewHTTPVerifier ¶
func NewHTTPVerifier(c *http.Client, endpoints map[string]string) *HTTPVerifier
NewHTTPVerifier returns a verifier using c. endpoints overrides the provider URLs (tests); nil means DefaultEndpoints.
func (*HTTPVerifier) TestSecret ¶
func (v *HTTPVerifier) TestSecret(ctx context.Context, provider, secret, siteKey string) (valid bool, detail string, err error)
TestSecret asks the provider about a dummy token, which tells a wrong secret (rejected as such) from a right one (only the token is rejected). For hCaptcha the site key is sent too, so a key and secret from different accounts show up. valid is false with a plain-sentence detail when the secret is wrong or the provider's reply cannot tell.
func (*HTTPVerifier) Verify ¶
func (v *HTTPVerifier) Verify(ctx context.Context, provider, secret, siteKey, token, remoteIP string) (Result, error)
Verify posts the token to the provider. A transport error, a non-200 reply or an unreadable body is an error; a reply that rejects the token is a Result with OK false. Neither the secret nor the token is ever included in an error.
type Input ¶
type Input struct {
Slug string
CaptchaRequired bool // the form's "Require the spam check" switch
Data map[string]interface{}
Timing string // the hm_ts token the page fetched at load
Captcha string // the provider's response token
IP string
}
Input is one submission as the pipeline sees it. Data holds the validated field values only: control fields must already be stripped.
type Pipeline ¶
type Pipeline struct {
Settings SettingsReader
Verifier Verifier
SecretKey []byte // opens the sealed captcha secret
TimingKey []byte // signs and checks timing tokens
Now func() time.Time
}
Pipeline runs the checks every public form submission passes through.
func (*Pipeline) CaptchaUsable ¶
CaptchaUsable reports whether s names a provider whose secret can be read.
func (*Pipeline) Check ¶
Check runs, in order: the captcha (only when the form requires it and a provider with a readable secret is set up), the minimum time to submit, and the content rules. reason names the rule that tripped, never user content.
func (*Pipeline) CheckToken ¶
CheckToken reports whether tok is a valid, unexpired timing token for slug.
func (*Pipeline) IssueToken ¶
IssueToken returns a timing token for the form with this slug.
type Querier ¶
type Querier interface {
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
}
Querier is the part of a pgx pool the Store needs.
type Result ¶
type Result struct {
OK bool
Codes []string // provider error codes, safe to show an admin
Score float64 // reCAPTCHA v3 score, 0 for other providers
}
Result is a provider's verdict on one token.
type Settings ¶
type Settings struct {
Provider string
SiteKey string
SecretSealed []byte // sealed captcha secret; nil when none is saved
MinSeconds int
MaxLinks int
BlockedWords []string
BlockedScripts []string
Action string
}
Settings is the spam protection configuration.
type SettingsReader ¶
SettingsReader supplies the current settings; *Store satisfies it.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads and writes the single hatamoto_spam_settings row, with a short cache because every public page render and form submit reads it.
func (*Store) Get ¶
Get returns the stored settings, or Defaults when no row exists. On a read error it serves the last good copy if it has one.
func (*Store) Invalidate ¶
func (s *Store) Invalidate()
Invalidate drops the cached copy so the next Get reads the database.