From acce3604a4ccaa13be2ecb148c919f33d4383d5e Mon Sep 17 00:00:00 2001
From: eerielili
Date: Mon, 7 Apr 2025 21:44:00 +0200
Subject: [PATCH] Add variable WEBMASTER_EMAIL and if present, display it on
error page (#235)
* Add variable WEBMASTER_EMAIL and if present, display it on error page
- Adresses issue https://github.com/TecharoHQ/anubis/issues/115
* web: regenerate templates
Signed-off-by: Xe Iaso
* update docs
Signed-off-by: Xe Iaso
---------
Signed-off-by: Xe Iaso
Co-authored-by: Xe Iaso
---
cmd/anubis/main.go | 2 +
docs/docs/CHANGELOG.md | 1 +
docs/docs/admin/installation.mdx | 1 +
lib/anubis.go | 32 ++++++++-------
web/index.go | 4 +-
web/index.templ | 10 ++++-
web/index_templ.go | 69 ++++++++++++++++++++++++--------
7 files changed, 84 insertions(+), 35 deletions(-)
diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go
index 9fca3e2..724f88a 100644
--- a/cmd/anubis/main.go
+++ b/cmd/anubis/main.go
@@ -58,6 +58,7 @@ var (
ogPassthrough = flag.Bool("og-passthrough", false, "enable Open Graph tag passthrough")
ogTimeToLive = flag.Duration("og-expiry-time", 24*time.Hour, "Open Graph tag cache expiration time")
extractResources = flag.String("extract-resources", "", "if set, extract the static resources to the specified folder")
+ webmasterEmail = flag.String("webmaster-email", "", "if set, displays webmaster's email on the reject page for appeals")
)
func keyFromHex(value string) (ed25519.PrivateKey, error) {
@@ -260,6 +261,7 @@ func main() {
OGPassthrough: *ogPassthrough,
OGTimeToLive: *ogTimeToLive,
Target: *target,
+ WebmasterEmail: *webmasterEmail,
})
if err != nil {
log.Fatalf("can't construct libanubis.Server: %v", err)
diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md
index d7c6acb..3d4aa31 100644
--- a/docs/docs/CHANGELOG.md
+++ b/docs/docs/CHANGELOG.md
@@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for [OpenGraph tags](https://ogp.me/) when rendering the challenge page. This allows for social previews to be generated when sharing the challenge page on social media platforms ([#195](https://github.com/TecharoHQ/anubis/pull/195))
- Added an `--extract-resources` flag to extract static resources to a local folder.
- Add noindex flag to all Anubis pages ([#227](https://github.com/TecharoHQ/anubis/issues/227)).
+- Added `WEBMASTER_EMAIL` variable, if it is present then display that email address on error pages ([#235](https://github.com/TecharoHQ/anubis/pull/235), [#115](https://github.com/TecharoHQ/anubis/issues/115))
## v1.15.1
diff --git a/docs/docs/admin/installation.mdx b/docs/docs/admin/installation.mdx
index c819e09..92fe737 100644
--- a/docs/docs/admin/installation.mdx
+++ b/docs/docs/admin/installation.mdx
@@ -60,6 +60,7 @@ Anubis uses these environment variables for configuration:
| `SOCKET_MODE` | `0770` | _Only used when at least one of the `*_BIND_NETWORK` variables are set to `unix`._ The socket mode (permissions) for Unix domain sockets. |
| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. |
| `USE_REMOTE_ADDRESS` | unset | If set to `true`, Anubis will take the client's IP from the network socket. For production deployments, it is expected that a reverse proxy is used in front of Anubis, which pass the IP using headers, instead. |
+| `WEBMASTER_EMAIL` | unset | If set, shows a contact email address when rendering error pages. This email address will be how users can get in contact with administrators. |
For more detailed information on configuring Open Graph tags, please refer to the [Open Graph Configuration](./configuration/open-graph.mdx) page.
diff --git a/lib/anubis.go b/lib/anubis.go
index 18a7d3f..6fd18a5 100644
--- a/lib/anubis.go
+++ b/lib/anubis.go
@@ -76,6 +76,8 @@ type Options struct {
OGPassthrough bool
OGTimeToLive time.Duration
Target string
+
+ WebmasterEmail string
}
func LoadPoliciesOrDefault(fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
@@ -193,7 +195,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
cr, rule, err := s.check(r)
if err != nil {
lg.Error("check failed", "err", err)
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"maybeReverseProxy\"")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"maybeReverseProxy\"", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
return
}
@@ -218,7 +220,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
if resp != dnsbl.AllGood {
lg.Info("DNSBL hit", "status", resp.String())
- templ.Handler(web.Base("Oh noes!", web.ErrorPage(fmt.Sprintf("DroneBL reported an entry: %s, see https://dronebl.org/lookup?ip=%s", resp.String(), ip))), templ.WithStatus(http.StatusOK)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage(fmt.Sprintf("DroneBL reported an entry: %s, see https://dronebl.org/lookup?ip=%s", resp.String(), ip), s.opts.WebmasterEmail)), templ.WithStatus(http.StatusOK)).ServeHTTP(w, r)
return
}
}
@@ -233,17 +235,17 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
lg.Info("explicit deny")
if rule == nil {
lg.Error("rule is nil, cannot calculate checksum")
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("Other internal server error (contact the admin)")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Other internal server error (contact the admin)", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
return
}
hash, err := rule.Hash()
if err != nil {
lg.Error("can't calculate checksum of rule", "err", err)
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("Other internal server error (contact the admin)")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Other internal server error (contact the admin)", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
return
}
lg.Debug("rule hash", "hash", hash)
- templ.Handler(web.Base("Oh noes!", web.ErrorPage(fmt.Sprintf("Access Denied: error code %s", hash))), templ.WithStatus(http.StatusOK)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage(fmt.Sprintf("Access Denied: error code %s", hash), s.opts.WebmasterEmail)), templ.WithStatus(http.StatusOK)).ServeHTTP(w, r)
return
case config.RuleChallenge:
lg.Debug("challenge requested")
@@ -253,7 +255,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
return
default:
s.ClearCookie(w)
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("Other internal server error (contact the admin)")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Other internal server error (contact the admin)", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
return
}
@@ -399,7 +401,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
cr, rule, err := s.check(r)
if err != nil {
lg.Error("check failed", "err", err)
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"passChallenge\".")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"passChallenge\".", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
return
}
lg = lg.With("check_result", cr)
@@ -408,7 +410,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
if nonceStr == "" {
s.ClearCookie(w)
lg.Debug("no nonce")
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("missing nonce")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("missing nonce", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
return
}
@@ -416,7 +418,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
if elapsedTimeStr == "" {
s.ClearCookie(w)
lg.Debug("no elapsedTime")
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("missing elapsedTime")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("missing elapsedTime", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
return
}
@@ -424,7 +426,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
if err != nil {
s.ClearCookie(w)
lg.Debug("elapsedTime doesn't parse", "err", err)
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid elapsedTime")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid elapsedTime", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
return
}
@@ -440,7 +442,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
if err != nil {
s.ClearCookie(w)
lg.Debug("nonce doesn't parse", "err", err)
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid nonce")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid nonce", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
return
}
@@ -450,7 +452,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
if subtle.ConstantTimeCompare([]byte(response), []byte(calculated)) != 1 {
s.ClearCookie(w)
lg.Debug("hash does not match", "got", response, "want", calculated)
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid response")), templ.WithStatus(http.StatusForbidden)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid response", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusForbidden)).ServeHTTP(w, r)
failedValidations.Inc()
return
}
@@ -459,7 +461,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(response, strings.Repeat("0", rule.Challenge.Difficulty)) {
s.ClearCookie(w)
lg.Debug("difficulty check failed", "response", response, "difficulty", rule.Challenge.Difficulty)
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid response")), templ.WithStatus(http.StatusForbidden)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid response", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusForbidden)).ServeHTTP(w, r)
failedValidations.Inc()
return
}
@@ -477,7 +479,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
if err != nil {
lg.Error("failed to sign JWT", "err", err)
s.ClearCookie(w)
- templ.Handler(web.Base("Oh noes!", web.ErrorPage("failed to sign JWT")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("failed to sign JWT", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
return
}
@@ -498,7 +500,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
func (s *Server) TestError(w http.ResponseWriter, r *http.Request) {
err := r.FormValue("err")
- templ.Handler(web.Base("Oh noes!", web.ErrorPage(err)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage(err, s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
}
// Check evaluates the list of rules, and returns the result
diff --git a/web/index.go b/web/index.go
index 0dc8b7f..5d2957b 100644
--- a/web/index.go
+++ b/web/index.go
@@ -16,8 +16,8 @@ func Index() templ.Component {
return index()
}
-func ErrorPage(msg string) templ.Component {
- return errorPage(msg)
+func ErrorPage(msg string, mail string) templ.Component {
+ return errorPage(msg, mail)
}
func Bench() templ.Component {
diff --git a/web/index.templ b/web/index.templ
index 63048e2..01d12b6 100644
--- a/web/index.templ
+++ b/web/index.templ
@@ -129,7 +129,7 @@ templ index() {
}
-templ errorPage(message string) {
+templ errorPage(message string, mail string) {
{ message }.
-
Go home
+ if mail != "" {
+
Go home or if you believe you should not be blocked, please contact the webmaster at
+ { mail }
+
+ } else {
+
Go home
+ }
}
diff --git a/web/index_templ.go b/web/index_templ.go
index 27ab6fe..a3e10fe 100644
--- a/web/index_templ.go
+++ b/web/index_templ.go
@@ -204,7 +204,7 @@ func index() templ.Component {
})
}
-func errorPage(message string) templ.Component {
+func errorPage(message string, mail string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -251,7 +251,44 @@ func errorPage(message string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, ".
Go home
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, ". ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if mail != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "Go home or if you believe you should not be blocked, please contact the webmaster at ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var15 string
+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(mail)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 144, Col: 9}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "Go home
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -275,40 +312,40 @@ func bench() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var14 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var14 == nil {
- templ_7745c5c3_Var14 = templ.NopComponent
+ templ_7745c5c3_Var16 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var16 == nil {
+ templ_7745c5c3_Var16 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, ")
)
Loading...
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\">
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}