Files
hotdog-cms/internal/build/consent.go
T

135 lines
4.4 KiB
Go

package build
import (
_ "embed"
"html/template"
"net/url"
"sort"
"strings"
"git.coffeylabs.org/coffey-labs/hotdog-cms/internal/forms"
"git.coffeylabs.org/coffey-labs/hotdog-cms/internal/site"
)
// Consent and the privacy notice. With analytics: in site.yaml, the build
// publishes a small consent script and stylesheet (no inline code, so a
// strict CSP holds), and templates get:
//
// {{ consent }} the two tags, in <head>
// {{ consentActive }} whether to offer "Cookie settings"
// {{ privacyFacts }} what the site uses that touches visitors' data, for
// the privacy notice: built from the configuration, so
// the notice can't fall behind it
//
// The consent script's tag carries the counter's hosts (data-hosts), so the
// checks and the Content-Security-Policy see them in the page.
var (
//go:embed static/hotdog-consent.js
consentScript []byte
//go:embed static/hotdog-consent.css
consentStyle []byte
)
var providerNames = map[string]string{"plausible": "Plausible", "matomo": "Matomo", "ga4": "Google Analytics"}
func origin(u string) string {
p, err := url.Parse(u)
if err != nil || p.Host == "" {
return ""
}
return p.Scheme + "://" + p.Host
}
// analyticsHosts are the origins a counter loads from and reports to.
func analyticsHosts(a *site.AnalyticsConfig) []string {
switch a.Provider {
case "plausible":
if a.Src != "" {
return []string{origin(a.Src)}
}
return []string{"https://plausible.io"}
case "matomo":
return []string{origin(a.URL)}
case "ga4":
return []string{"https://www.googletagmanager.com", "https://www.google-analytics.com", "https://*.google-analytics.com", "https://*.analytics.google.com"}
}
return nil
}
func privacyPage(cfg *site.Config) string {
if cfg.Check.PrivacyPage != "" {
return cfg.Check.PrivacyPage
}
return "/privacy/"
}
// consentTags is what {{ consent }} writes.
func consentTags(cfg *site.Config, a *Assets) template.HTML {
an := cfg.Analytics
if an == nil {
return ""
}
js, _ := a.URL("hotdog-consent.js")
css, _ := a.URL("hotdog-consent.css")
attrs := [][2]string{
{"data-provider", an.Provider}, {"data-domain", an.Domain}, {"data-src", an.Src}, {"data-url", an.URL},
{"data-site", an.SiteID}, {"data-id", an.ID}, {"data-privacy", privacyPage(cfg)},
{"data-hosts", strings.Join(analyticsHosts(an), " ")},
}
if !an.IsGated() {
attrs = append(attrs, [2]string{"data-gated", "false"})
}
var b strings.Builder
b.WriteString(`<link rel="stylesheet" href="` + template.HTMLEscapeString(css) + `">`)
b.WriteString(`<script src="` + template.HTMLEscapeString(js) + `" defer data-hotdog-consent`)
for _, kv := range attrs {
if kv[1] != "" {
b.WriteString(" " + kv[0] + `="` + template.HTMLEscapeString(kv[1]) + `"`)
}
}
b.WriteString(`></script>`)
return template.HTML(b.String())
}
// privacyFacts lists what the site uses that touches visitors' data.
func privacyFacts(cfg *site.Config, siteForms map[string]*forms.Form) []site.PrivacyFact {
var out []site.PrivacyFact
if an := cfg.Analytics; an != nil {
basis := "Your consent, asked for on your first visit; change it any time with Cookie settings."
if !an.IsGated() {
basis = "Legitimate interest in knowing which pages are read."
}
hosts := analyticsHosts(an)
who := providerNames[an.Provider]
if len(hosts) > 0 {
who += " (" + strings.TrimPrefix(hosts[0], "https://") + ")"
}
out = append(out, site.PrivacyFact{What: "Visit statistics", Who: who, Why: "To count visits and see which pages are read.", Basis: basis})
}
names := make([]string, 0, len(siteForms))
for n := range siteForms {
names = append(names, n)
}
sort.Strings(names)
for _, n := range names {
f := siteForms[n]
title := f.Title
if title == "" {
title = n
}
who := "Sent by email to the site's owner."
if f.Store {
who = "Sent by email to the site's owner, and kept on the site's server."
}
if f.Turnstile != "" {
who += " Cloudflare Turnstile checks that a person sent it."
}
out = append(out, site.PrivacyFact{What: "The " + title + " form: what you write in it", Who: who, Why: "To answer you.", Basis: "Your request: nothing is sent unless you send it."})
}
for _, h := range cfg.Check.AllowThirdParty {
out = append(out, site.PrivacyFact{What: "Content from " + h, Who: h, Why: "Part of what some pages show.", Basis: "Legitimate interest; " + h + " sees your address when your browser fetches it."})
}
return append(out, cfg.Privacy.Extra...)
}