diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 48a74b9..fbddd58 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Embedded challenge data in initial HTML response to improve performance - Whitelisted [DuckDuckBot](https://duckduckgo.com/duckduckgo-help-pages/results/duckduckbot/) in botPolicies - Improvements to build scripts to make them less independent of the build host - Improved the OpenGraph error logging diff --git a/go.mod b/go.mod index 3063368..d936bfb 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/TecharoHQ/anubis -go 1.24.2 +go 1.24 require ( github.com/a-h/templ v0.3.857 diff --git a/lib/anubis.go b/lib/anubis.go index 6fd18a5..353fe63 100644 --- a/lib/anubis.go +++ b/lib/anubis.go @@ -263,21 +263,21 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) { if err != nil { lg.Debug("cookie not found", "path", r.URL.Path) s.ClearCookie(w) - s.RenderIndex(w, r) + s.RenderIndex(w, r, cr, rule) return } if err := ckie.Valid(); err != nil { lg.Debug("cookie is invalid", "err", err) s.ClearCookie(w) - s.RenderIndex(w, r) + s.RenderIndex(w, r, cr, rule) return } if time.Now().After(ckie.Expires) && !ckie.Expires.IsZero() { lg.Debug("cookie expired", "path", r.URL.Path) s.ClearCookie(w) - s.RenderIndex(w, r) + s.RenderIndex(w, r, cr, rule) return } @@ -288,7 +288,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) { if err != nil || !token.Valid { lg.Debug("invalid token", "path", r.URL.Path, "err", err) s.ClearCookie(w) - s.RenderIndex(w, r) + s.RenderIndex(w, r, cr, rule) return } @@ -303,7 +303,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) { if !ok { lg.Debug("invalid token claims type", "path", r.URL.Path) s.ClearCookie(w) - s.RenderIndex(w, r) + s.RenderIndex(w, r, cr, rule) return } challenge := s.challengeFor(r, rule.Challenge.Difficulty) @@ -311,7 +311,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) { if claims["challenge"] != challenge { lg.Debug("invalid challenge", "path", r.URL.Path) s.ClearCookie(w) - s.RenderIndex(w, r) + s.RenderIndex(w, r, cr, rule) return } @@ -328,7 +328,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) { lg.Debug("invalid response", "path", r.URL.Path) failedValidations.Inc() s.ClearCookie(w) - s.RenderIndex(w, r) + s.RenderIndex(w, r, cr, rule) return } @@ -337,21 +337,36 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) { s.next.ServeHTTP(w, r) } -func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request) { +func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr CheckResult, rule *policy.Bot) { + lg := slog.With( + "user_agent", r.UserAgent(), + "accept_language", r.Header.Get("Accept-Language"), + "priority", r.Header.Get("Priority"), + "x-forwarded-for", + r.Header.Get("X-Forwarded-For"), + "x-real-ip", r.Header.Get("X-Real-Ip"), + ) + + challenge := s.challengeFor(r, rule.Challenge.Difficulty) + var ogTags map[string]string = nil if s.opts.OGPassthrough { var err error ogTags, err = s.OGTags.GetOGTags(r.URL) if err != nil { - slog.Error("failed to get OG tags", "err", err) + lg.Error("failed to get OG tags", "err", err) ogTags = nil } } - handler := internal.NoStoreCache( - templ.Handler( - web.BaseWithOGTags("Making sure you're not a bot!", web.Index(), ogTags), - ), - ) + + component, err := web.BaseWithChallengeAndOGTags("Making sure you're not a bot!", web.Index(), challenge, rule.Challenge, ogTags) + if err != nil { + lg.Error("render failed", "err", err) + 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 + } + + handler := internal.NoStoreCache(templ.Handler(component)) handler.ServeHTTP(w, r) } diff --git a/web/index.go b/web/index.go index 5d2957b..ac4cbda 100644 --- a/web/index.go +++ b/web/index.go @@ -2,14 +2,22 @@ package web import ( "github.com/a-h/templ" + + "github.com/TecharoHQ/anubis/lib/policy/config" ) func Base(title string, body templ.Component) templ.Component { - return base(title, body, nil) + return base(title, body, nil, nil) } -func BaseWithOGTags(title string, body templ.Component, ogTags map[string]string) templ.Component { - return base(title, body, ogTags) +func BaseWithChallengeAndOGTags(title string, body templ.Component, challenge string, rules *config.ChallengeRules, ogTags map[string]string) (templ.Component, error) { + return base(title, body, struct { + Challenge string `json:"challenge"` + Rules *config.ChallengeRules `json:"rules"` + }{ + Challenge: challenge, + Rules: rules, + }, ogTags), nil } func Index() templ.Component { diff --git a/web/index.templ b/web/index.templ index 01d12b6..4eb1d45 100644 --- a/web/index.templ +++ b/web/index.templ @@ -5,7 +5,7 @@ import ( "github.com/TecharoHQ/anubis/xess" ) -templ base(title string, body templ.Component, ogTags map[string]string) { +templ base(title string, body templ.Component, challenge any, ogTags map[string]string) { @@ -56,6 +56,9 @@ templ base(title string, body templ.Component, ogTags map[string]string) { } @templ.JSONScript("anubis_version", anubis.Version) + if challenge != nil { + @templ.JSONScript("anubis_challenge", challenge) + } diff --git a/web/index_templ.go b/web/index_templ.go index a3e10fe..7f501e3 100644 --- a/web/index_templ.go +++ b/web/index_templ.go @@ -13,7 +13,7 @@ import ( "github.com/TecharoHQ/anubis/xess" ) -func base(title string, body templ.Component, ogTags map[string]string) templ.Component { +func base(title string, body templ.Component, challenge any, ogTags map[string]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 { @@ -104,6 +104,12 @@ func base(title string, body templ.Component, ogTags map[string]string) templ.Co if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + if challenge != nil { + templ_7745c5c3_Err = templ.JSONScript("anubis_challenge", challenge).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -111,7 +117,7 @@ func base(title string, body templ.Component, ogTags map[string]string) templ.Co var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 64, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 67, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -162,7 +168,7 @@ func index() templ.Component { templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs("/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 88, Col: 18} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 91, Col: 18} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -176,7 +182,7 @@ func index() templ.Component { templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs("/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 94, Col: 18} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 97, Col: 18} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -190,7 +196,7 @@ func index() templ.Component { templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs( "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 98, Col: 84} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 101, Col: 84} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -232,7 +238,7 @@ func errorPage(message string, mail string) templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs("/.within.website/x/cmd/anubis/static/img/reject.webp?cacheBuster=" + anubis.Version) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 138, Col: 102} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 141, Col: 102} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -245,7 +251,7 @@ func errorPage(message string, mail string) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(message) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 140, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 143, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -272,7 +278,7 @@ func errorPage(message string, mail string) templ.Component { 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} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 147, Col: 9} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -325,7 +331,7 @@ func bench() templ.Component { templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs("/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 175, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 178, Col: 22} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -339,7 +345,7 @@ func bench() templ.Component { templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs( "/.within.website/x/cmd/anubis/static/js/bench.mjs?cacheBuster=" + anubis.Version) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 179, Col: 89} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 182, Col: 89} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { diff --git a/web/js/main.mjs b/web/js/main.mjs index a093c74..9bb6031 100644 --- a/web/js/main.mjs +++ b/web/js/main.mjs @@ -133,19 +133,7 @@ function showContinueBar(hash, nonce, t0, t1) { } } - const { challenge, rules } = await fetch("/.within.website/x/cmd/anubis/api/make-challenge", { method: "POST" }) - .then(r => { - if (!r.ok) throw new Error("Failed to fetch config"); - return r.json(); - }) - .catch(err => { - ohNoes({ - titleMsg: "Internal error!", - statusMsg: `Failed to fetch challenge config: ${err.message}`, - imageSrc: imageURL("reject", anubisVersion), - }); - throw err; - }); + const { challenge, rules } = JSON.parse(document.getElementById('anubis_challenge').textContent); const process = algorithms[rules.algorithm]; if (!process) {