config: Add string replacement for templates, add example config.yml (close #10)
This commit is contained in:
@@ -33,6 +33,7 @@ func ServeChallengeScript(w http.ResponseWriter, r *http.Request, reg *Registrat
|
||||
"Random": utils.CacheBust(),
|
||||
"Challenge": reg.Name,
|
||||
"ChallengeScript": script,
|
||||
"Strings": data.State.Options().Strings,
|
||||
})
|
||||
if err != nil {
|
||||
//TODO: log
|
||||
|
@@ -14,9 +14,8 @@ const u = (url = "", params = {}) => {
|
||||
(async () => {
|
||||
const status = document.getElementById('status');
|
||||
const title = document.getElementById('title');
|
||||
const spinner = document.getElementById('spinner');
|
||||
|
||||
status.innerText = 'Starting challenge {{ .Challenge }}...';
|
||||
status.innerText = '{{ .Strings.Get "status_starting_challenge" }} {{ .Challenge }}...';
|
||||
|
||||
try {
|
||||
const info = await setup({
|
||||
@@ -25,15 +24,13 @@ const u = (url = "", params = {}) => {
|
||||
});
|
||||
|
||||
if (info != "") {
|
||||
status.innerText = 'Calculating... ' + info
|
||||
status.innerText = '{{ .Strings.Get "status_calculating" }} ' + info
|
||||
} else {
|
||||
status.innerText = 'Calculating...';
|
||||
status.innerText = '{{ .Strings.Get "status_calculating" }}';
|
||||
}
|
||||
} catch (err) {
|
||||
title.innerHTML = "Oh no!";
|
||||
status.innerHTML = `Failed to initialize: ${err.message}`;
|
||||
spinner.innerHTML = "";
|
||||
spinner.style.display = "none";
|
||||
title.innerHTML = '{{ .Strings.Get "title_error" }}';
|
||||
status.innerHTML = `{{ .Strings.Get "status_error" }} ${err.message}`;
|
||||
return
|
||||
}
|
||||
|
||||
@@ -44,11 +41,11 @@ const u = (url = "", params = {}) => {
|
||||
const t1 = Date.now();
|
||||
console.log({ result, info });
|
||||
|
||||
title.innerHTML = "Challenge success!";
|
||||
title.innerHTML = '{{ .Strings.Get "status_challenge_success" }}';
|
||||
if (info != "") {
|
||||
status.innerHTML = `Done! Took ${t1 - t0}ms, ${info}`;
|
||||
status.innerHTML = `{{ .Strings.Get "status_challenge_done_took" }} ${t1 - t0}ms, ${info}`;
|
||||
} else {
|
||||
status.innerHTML = `Done! Took ${t1 - t0}ms`;
|
||||
status.innerHTML = `{{ .Strings.Get "status_challenge_done_took" }} ${t1 - t0}ms`;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -62,9 +59,7 @@ const u = (url = "", params = {}) => {
|
||||
});
|
||||
}, 500);
|
||||
} catch (err) {
|
||||
title.innerHTML = "Oh no!";
|
||||
status.innerHTML = `Failed to challenge: ${err.message}`;
|
||||
spinner.innerHTML = "";
|
||||
spinner.style.display = "none";
|
||||
title.innerHTML = '{{ .Strings.Get "title_error" }}';
|
||||
status.innerHTML = `{{ .Strings.Get "status_error" }} ${err.message}`;
|
||||
}
|
||||
})();
|
36
lib/http.go
36
lib/http.go
@@ -8,47 +8,11 @@ import (
|
||||
"git.gammaspectra.live/git/go-away/lib/challenge"
|
||||
"git.gammaspectra.live/git/go-away/lib/policy"
|
||||
"git.gammaspectra.live/git/go-away/utils"
|
||||
"html/template"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var templates map[string]*template.Template
|
||||
|
||||
func init() {
|
||||
|
||||
templates = make(map[string]*template.Template)
|
||||
|
||||
dir, err := embed.TemplatesFs.ReadDir(".")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, e := range dir {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
data, err := embed.TemplatesFs.ReadFile(e.Name())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = initTemplate(e.Name(), string(data))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initTemplate(name, data string) error {
|
||||
tpl := template.New(name)
|
||||
_, err := tpl.Parse(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
templates[name] = tpl
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetLoggerForRequest(r *http.Request) *slog.Logger {
|
||||
data := challenge.RequestDataFromContext(r.Context())
|
||||
args := []any{
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"git.gammaspectra.live/git/go-away/lib/challenge"
|
||||
"git.gammaspectra.live/git/go-away/lib/policy"
|
||||
@@ -9,7 +8,6 @@ import (
|
||||
"git.gammaspectra.live/git/go-away/utils"
|
||||
"github.com/google/cel-go/cel"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -84,77 +82,6 @@ func (state *State) Logger(r *http.Request) *slog.Logger {
|
||||
return GetLoggerForRequest(r)
|
||||
}
|
||||
|
||||
func (state *State) ChallengePage(w http.ResponseWriter, r *http.Request, status int, reg *challenge.Registration, params map[string]any) {
|
||||
data := challenge.RequestDataFromContext(r.Context())
|
||||
input := make(map[string]any)
|
||||
input["Id"] = data.Id.String()
|
||||
input["Random"] = utils.CacheBust()
|
||||
|
||||
input["Path"] = state.UrlPath()
|
||||
for k, v := range state.Options().ChallengeTemplateOverrides {
|
||||
input[k] = v
|
||||
}
|
||||
for k, v := range state.Options().Strings {
|
||||
input["str_"+k] = v
|
||||
}
|
||||
|
||||
if reg != nil {
|
||||
input["Challenge"] = reg.Name
|
||||
}
|
||||
|
||||
maps.Copy(input, params)
|
||||
|
||||
if _, ok := input["Title"]; !ok {
|
||||
input["Title"] = state.Options().Strings.Get("challenge_are_you_bot")
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 8192))
|
||||
|
||||
err := templates["challenge-"+state.Options().ChallengeTemplate+".gohtml"].Execute(buf, input)
|
||||
if err != nil {
|
||||
state.ErrorPage(w, r, http.StatusInternalServerError, err, "")
|
||||
} else {
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func (state *State) ErrorPage(w http.ResponseWriter, r *http.Request, status int, err error, redirect string) {
|
||||
data := challenge.RequestDataFromContext(r.Context())
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 8192))
|
||||
|
||||
input := map[string]any{
|
||||
"Id": data.Id.String(),
|
||||
"Random": utils.CacheBust(),
|
||||
"Error": err.Error(),
|
||||
"Path": state.UrlPath(),
|
||||
"Theme": "",
|
||||
"Title": state.Options().Strings.Get("error") + " " + http.StatusText(status),
|
||||
"HideSpinner": true,
|
||||
"Challenge": "",
|
||||
"Redirect": redirect,
|
||||
}
|
||||
for k, v := range state.Options().ChallengeTemplateOverrides {
|
||||
input[k] = v
|
||||
}
|
||||
for k, v := range state.Options().Strings {
|
||||
input["str_"+k] = v
|
||||
}
|
||||
|
||||
err2 := templates["challenge-"+state.Options().ChallengeTemplate+".gohtml"].Execute(buf, input)
|
||||
if err2 != nil {
|
||||
// nested errors!
|
||||
panic(err2)
|
||||
} else {
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func (state *State) GetChallenge(id challenge.Id) (*challenge.Registration, bool) {
|
||||
reg, ok := state.challenges.Get(id)
|
||||
return reg, ok
|
||||
|
@@ -3,12 +3,12 @@ package settings
|
||||
import "maps"
|
||||
|
||||
type Settings struct {
|
||||
Bind Bind `json:"bind"`
|
||||
Bind Bind `yaml:"bind"`
|
||||
|
||||
Backends map[string]Backend `json:"backends"`
|
||||
Backends map[string]Backend `yaml:"backends"`
|
||||
|
||||
BindDebug string `json:"bind-debug"`
|
||||
BindMetrics string `json:"bind-metrics"`
|
||||
BindDebug string `yaml:"bind-debug"`
|
||||
BindMetrics string `yaml:"bind-metrics"`
|
||||
|
||||
Strings Strings `yaml:"strings"`
|
||||
|
||||
|
@@ -1,12 +1,43 @@
|
||||
package settings
|
||||
|
||||
import "maps"
|
||||
import (
|
||||
"html/template"
|
||||
"maps"
|
||||
)
|
||||
|
||||
type Strings map[string]string
|
||||
|
||||
var DefaultStrings = make(Strings).set(map[string]string{
|
||||
"challenge_are_you_bot": "Checking you are not a bot",
|
||||
"error": "Oh no!",
|
||||
"title_challenge": "Checking you are not a bot",
|
||||
"title_error": "Oh no!",
|
||||
|
||||
"noscript_warning": "<p>Sadly, you may need to enable JavaScript to get past this challenge. This is required because AI companies have changed the social contract around how website hosting works.</p>",
|
||||
|
||||
"details_title": "Why am I seeing this?",
|
||||
"details_text": `
|
||||
<p>
|
||||
You are seeing this because the administrator of this website has set up <a href="https://git.gammaspectra.live/git/go-away">go-away</a>
|
||||
to protect the server against the scourge of <a href="https://thelibre.news/foss-infrastructure-is-under-attack-by-ai-companies/">AI companies aggressively scraping websites</a>.
|
||||
</p>
|
||||
<p>
|
||||
Mass scraping can and does cause downtime for the websites, which makes their resources inaccessible for everyone.
|
||||
</p>
|
||||
<p>
|
||||
Please note that some challenges requires the use of modern JavaScript features and some plugins may disable these.
|
||||
Disable such plugins for this domain (for example, JShelter) if you encounter any issues.
|
||||
</p>
|
||||
`,
|
||||
"details_contact_admin_with_request_id": "If you have any issues contact the site administrator and provide the following Request Id",
|
||||
|
||||
"button_refresh_page": "Refresh page",
|
||||
|
||||
"status_loading_challenge": "Loading challenge",
|
||||
"status_starting_challenge": "Starting challenge",
|
||||
"status_loading": "Loading...",
|
||||
"status_calculating": "Calculating...",
|
||||
"status_challenge_success": "Challenge success!",
|
||||
"status_challenge_done_took": "Done! Took",
|
||||
"status_error": "Error:",
|
||||
})
|
||||
|
||||
func (s Strings) set(v map[string]string) Strings {
|
||||
@@ -14,11 +45,11 @@ func (s Strings) set(v map[string]string) Strings {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Strings) Get(value string) string {
|
||||
func (s Strings) Get(value string) template.HTML {
|
||||
v, ok := (s)[value]
|
||||
if !ok {
|
||||
// fallback
|
||||
return "string:" + value
|
||||
return template.HTML("string:" + value)
|
||||
}
|
||||
return v
|
||||
return template.HTML(v)
|
||||
}
|
||||
|
114
lib/template.go
Normal file
114
lib/template.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"git.gammaspectra.live/git/go-away/embed"
|
||||
"git.gammaspectra.live/git/go-away/lib/challenge"
|
||||
"git.gammaspectra.live/git/go-away/utils"
|
||||
"html/template"
|
||||
"maps"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var templates map[string]*template.Template
|
||||
|
||||
func init() {
|
||||
|
||||
templates = make(map[string]*template.Template)
|
||||
|
||||
dir, err := embed.TemplatesFs.ReadDir(".")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, e := range dir {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
data, err := embed.TemplatesFs.ReadFile(e.Name())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = initTemplate(e.Name(), string(data))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initTemplate(name, data string) error {
|
||||
tpl := template.New(name)
|
||||
_, err := tpl.Parse(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
templates[name] = tpl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (state *State) ChallengePage(w http.ResponseWriter, r *http.Request, status int, reg *challenge.Registration, params map[string]any) {
|
||||
data := challenge.RequestDataFromContext(r.Context())
|
||||
input := make(map[string]any)
|
||||
input["Id"] = data.Id.String()
|
||||
input["Random"] = utils.CacheBust()
|
||||
|
||||
input["Path"] = state.UrlPath()
|
||||
input["Links"] = state.Options().Links
|
||||
input["Strings"] = state.Options().Strings
|
||||
for k, v := range state.Options().ChallengeTemplateOverrides {
|
||||
input[k] = v
|
||||
}
|
||||
|
||||
if reg != nil {
|
||||
input["Challenge"] = reg.Name
|
||||
}
|
||||
|
||||
maps.Copy(input, params)
|
||||
|
||||
if _, ok := input["Title"]; !ok {
|
||||
input["Title"] = state.Options().Strings.Get("title_challenge")
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 8192))
|
||||
|
||||
err := templates["challenge-"+state.Options().ChallengeTemplate+".gohtml"].Execute(buf, input)
|
||||
if err != nil {
|
||||
state.ErrorPage(w, r, http.StatusInternalServerError, err, "")
|
||||
} else {
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func (state *State) ErrorPage(w http.ResponseWriter, r *http.Request, status int, err error, redirect string) {
|
||||
data := challenge.RequestDataFromContext(r.Context())
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 8192))
|
||||
|
||||
input := map[string]any{
|
||||
"Id": data.Id.String(),
|
||||
"Random": utils.CacheBust(),
|
||||
"Error": err.Error(),
|
||||
"Path": state.UrlPath(),
|
||||
"Theme": "",
|
||||
"Title": template.HTML(string(state.Options().Strings.Get("title_error")) + " " + http.StatusText(status)),
|
||||
"Challenge": "",
|
||||
"Redirect": redirect,
|
||||
"Links": state.Options().Links,
|
||||
"Strings": state.Options().Strings,
|
||||
}
|
||||
for k, v := range state.Options().ChallengeTemplateOverrides {
|
||||
input[k] = v
|
||||
}
|
||||
|
||||
err2 := templates["challenge-"+state.Options().ChallengeTemplate+".gohtml"].Execute(buf, input)
|
||||
if err2 != nil {
|
||||
// nested errors!
|
||||
panic(err2)
|
||||
} else {
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(buf.Bytes())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user