diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 324bfdc..909633e 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -129,6 +129,7 @@ iat ifm Imagesift imgproxy +impressum inp IPTo iptoasn @@ -238,6 +239,7 @@ Seo setsebool shellcheck Sidetrade +simprint sitemap sls sni diff --git a/data/botPolicies.yaml b/data/botPolicies.yaml index 4286d7f..fb47ff4 100644 --- a/data/botPolicies.yaml +++ b/data/botPolicies.yaml @@ -84,6 +84,35 @@ bots: dnsbl: false +# # +# impressum: +# # Displayed at the bottom of every page rendered by Anubis. +# footer: >- +# This website is hosted by Techaro. If you have any complaints or notes +# about the service, please contact +# contact@techaro.lol and we +# will assist you as soon as possible. + +# # The imprint page that will be linked to at the footer of every Anubis page. +# page: +# # The HTML
Last updated: June 2025
+ +#In common with other websites, log files are stored on the web server saving details such as the visitor's IP address, browser type, referring page and time of visit.
+ +#Cookies may be used to remember visitor preferences when interacting with the website.
+ +#Where registration is required, the visitor's email and a username will be stored on the server.
+ +# + # Open Graph passthrough configuration, see here for more information: # https://anubis.techaro.lol/docs/admin/configuration/open-graph/ openGraph: diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 25b444b..6bcde05 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -24,6 +24,7 @@ The big ticket items are as follows: - Add `robots2policy` CLI utility to convert robots.txt files to Anubis challenge policies using CEL expressions ([#409](https://github.com/TecharoHQ/anubis/issues/409)) - Refactor challenge presentation logic to use a challenge registry - Allow challenge implementations to register HTTP routes +- [Imprint/Impressum support](./admin/configuration/impressum.mdx) ([#362](https://github.com/TecharoHQ/anubis/issues/362)) A lot of performance improvements have been made: diff --git a/docs/docs/admin/configuration/impressum.mdx b/docs/docs/admin/configuration/impressum.mdx new file mode 100644 index 0000000..c86ee89 --- /dev/null +++ b/docs/docs/admin/configuration/impressum.mdx @@ -0,0 +1,70 @@ +# Imprint / Impressum configuration + +Some jurisdictions (such as the European Union and specifically Germany) [must have contact information freely available](https://www.privacycompany.eu/blog/the-imprint-requirement-a-must-have-for-companies-from-outside-germany) on an imprint/impressum page. Anubis supports creating an Anubis-specific imprint page for your organization with the `impressum` block in your bot policy file. For example: + +```yaml +impressum: + # Displayed at the bottom of every page rendered by Anubis. + footer: >- + This website is hosted by Techaro. If you have any complaints or notes + about the service, please contact + contact@techaro.lol and we + will assist you as soon as possible. + + # The imprint page that will be linked to at the footer of every Anubis page. + page: + # The HTMLLast updated: June 2025
+ +In common with other websites, log files are stored on the web server saving details such as the visitor's IP address, browser type, referring page and time of visit.
+ +Cookies may be used to remember visitor preferences when interacting with the website.
+ +Where registration is required, the visitor's email and a username will be stored on the server.
+ + +``` + +If you are subscribed to and using [advanced classification features](../thoth.mdx), be sure to disclose the following: + +```html ++ This website uses a service called + Anubis by + Techaro to filter malicious traffic. Anubis + requires the use of browser cookies to ensure that web clients are running + conformant software. Anubis also may report the following data to Techaro to + improve service quality: +
+ ++ This data is processed and stored for the legitimate interest of combatting + abusive web clients. This data is encrypted at rest as much as possible and is + only decrypted in memory for the purposes of fulfilling requests. +
+``` diff --git a/docs/docs/admin/policies.mdx b/docs/docs/admin/policies.mdx index 85e7b95..4633cde 100644 --- a/docs/docs/admin/policies.mdx +++ b/docs/docs/admin/policies.mdx @@ -233,6 +233,10 @@ remote_addresses: +## Imprint / Impressum support + +Anubis has support for showing imprint / impressum information. This is defined in the `impressum` block of your configuration. See [Imprint / Impressum configuration](./configuration/impressum.mdx) for more information. + ## Risk calculation for downstream services In case your service needs it for risk calculation reasons, Anubis exposes information about the rules that any requests match using a few headers: diff --git a/docs/manifest/cfg/anubis/botPolicies.yaml b/docs/manifest/cfg/anubis/botPolicies.yaml index 6659916..0abf0dc 100644 --- a/docs/manifest/cfg/anubis/botPolicies.yaml +++ b/docs/manifest/cfg/anubis/botPolicies.yaml @@ -70,6 +70,55 @@ bots: dnsbl: false +impressum: + footer: | + This website is hosted by Techaro. If you have any complaints or notes about the service, please contact contact@techaro.lol and we will assist you as soon as possible. + + page: + title: Privacy Policy + body: | +Last updated: June 2025
+ +In common with other websites, log files are stored on the web server saving details such as the visitor's IP address, browser type, referring page and time of visit.
+ +Cookies may be used to remember visitor preferences when interacting with the website.
+ +Where registration is required, the visitor's email and a username will be stored on the server.
+ +The information is used to enhance the vistor's experience when using the website to display personalised content and possibly advertising.
+ +E-mail addresses will not be sold, rented or leased to 3rd parties.
+ +E-mail may be sent to inform you of news of our services or offers by us or our affiliates.
+ +If you have subscribed to one of our services, you may unsubscribe by following the instructions which are included in e-mail that you receive.
+ +You may be able to block cookies via your browser settings but this may prevent you from access to certain features of the website.
+ +Cookies are small digital signature files that are stored by your web browser that allow your preferences to be recorded when visiting the website. Also they may be used to track your return visits to the website.
+ +3rd party advertising companies may also use cookies for tracking purposes.
+ +This website uses a service called Anubis to filter malicious traffic. Anubis requires the use of browser cookies to ensure that web clients are running conformant software. Anubis also may report the following data to Techaro to improve service quality:
+ +This data is processed and stored for the legitimate interest of combatting abusive web clients. This data is encrypted at rest as much as possible and is only decrypted in memory for the purposes of fulfilling requests.
+ # By default, send HTTP 200 back to clients that either get issued a challenge # or a denial. This seems weird, but this is load-bearing due to the fact that # the most aggressive scraper bots seem to really, really, want an HTTP 200 and diff --git a/lib/challenge/challenge.go b/lib/challenge/challenge.go index a26f90b..cfe69e2 100644 --- a/lib/challenge/challenge.go +++ b/lib/challenge/challenge.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/TecharoHQ/anubis/lib/policy" + "github.com/TecharoHQ/anubis/lib/policy/config" "github.com/a-h/templ" ) @@ -40,12 +41,19 @@ func Methods() []string { return result } +type IssueInput struct { + Impressum *config.Impressum + Rule *policy.Bot + Challenge string + OGTags map[string]string +} + type Impl interface { // Setup registers any additional routes with the Impl for assets or API routes. Setup(mux *http.ServeMux) // Issue a new challenge to the user, called by the Anubis. - Issue(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string, ogTags map[string]string) (templ.Component, error) + Issue(r *http.Request, lg *slog.Logger, in *IssueInput) (templ.Component, error) // Validate a challenge, making sure that it passes muster. Validate(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string) error diff --git a/lib/challenge/metarefresh/metarefresh.go b/lib/challenge/metarefresh/metarefresh.go index 54f7168..99afc28 100644 --- a/lib/challenge/metarefresh/metarefresh.go +++ b/lib/challenge/metarefresh/metarefresh.go @@ -23,7 +23,7 @@ type Impl struct{} func (i *Impl) Setup(mux *http.ServeMux) {} -func (i *Impl) Issue(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string, ogTags map[string]string) (templ.Component, error) { +func (i *Impl) Issue(r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) { u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge") if err != nil { return nil, fmt.Errorf("can't render page: %w", err) @@ -31,10 +31,10 @@ func (i *Impl) Issue(r *http.Request, lg *slog.Logger, rule *policy.Bot, challen q := u.Query() q.Set("redir", r.URL.String()) - q.Set("challenge", challenge) + q.Set("challenge", in.Challenge) u.RawQuery = q.Encode() - component, err := web.BaseWithChallengeAndOGTags("Making sure you're not a bot!", page(challenge, u.String(), rule.Challenge.Difficulty), challenge, rule.Challenge, ogTags) + component, err := web.BaseWithChallengeAndOGTags("Making sure you're not a bot!", page(in.Challenge, u.String(), in.Rule.Challenge.Difficulty), in.Impressum, in.Challenge, in.Rule.Challenge, in.OGTags) if err != nil { return nil, fmt.Errorf("can't render page: %w", err) } diff --git a/lib/challenge/proofofwork/proofofwork.go b/lib/challenge/proofofwork/proofofwork.go index eb8e312..9f02ce7 100644 --- a/lib/challenge/proofofwork/proofofwork.go +++ b/lib/challenge/proofofwork/proofofwork.go @@ -28,8 +28,8 @@ func (i *Impl) Setup(mux *http.ServeMux) { /* no implementation required */ } -func (i *Impl) Issue(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string, ogTags map[string]string) (templ.Component, error) { - component, err := web.BaseWithChallengeAndOGTags("Making sure you're not a bot!", web.Index(), challenge, rule.Challenge, ogTags) +func (i *Impl) Issue(r *http.Request, lg *slog.Logger, in *chall.IssueInput) (templ.Component, error) { + component, err := web.BaseWithChallengeAndOGTags("Making sure you're not a bot!", web.Index(), in.Impressum, in.Challenge, in.Rule.Challenge, in.OGTags) if err != nil { return nil, fmt.Errorf("can't render page: %w", err) } diff --git a/lib/challenge/proofofwork/proofofwork_test.go b/lib/challenge/proofofwork/proofofwork_test.go index f1ecf0f..34dbcdf 100644 --- a/lib/challenge/proofofwork/proofofwork_test.go +++ b/lib/challenge/proofofwork/proofofwork_test.go @@ -124,7 +124,12 @@ func TestBasic(t *testing.T) { t.Run(cs.name, func(t *testing.T) { lg := slog.With() - if _, err := i.Issue(cs.req, lg, bot, cs.challengeStr, nil); err != nil { + inp := &challenge.IssueInput{ + Rule: bot, + Challenge: cs.challengeStr, + } + + if _, err := i.Issue(cs.req, lg, inp); err != nil { t.Errorf("can't issue challenge: %v", err) } diff --git a/lib/config.go b/lib/config.go index 27021b3..cecefbc 100644 --- a/lib/config.go +++ b/lib/config.go @@ -24,6 +24,7 @@ import ( "github.com/TecharoHQ/anubis/lib/policy/config" "github.com/TecharoHQ/anubis/web" "github.com/TecharoHQ/anubis/xess" + "github.com/a-h/templ" ) type Options struct { @@ -149,6 +150,14 @@ func New(opts Options) (*Server, error) { }), "GET") } + if opts.Policy.Impressum != nil { + registerWithPrefix(anubis.APIPrefix+"imprint", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + templ.Handler( + web.Base(opts.Policy.Impressum.Page.Title, opts.Policy.Impressum.Page, opts.Policy.Impressum), + ).ServeHTTP(w, r) + }), "GET") + } + registerWithPrefix(anubis.APIPrefix+"pass-challenge", http.HandlerFunc(result.PassChallenge), "GET") registerWithPrefix(anubis.APIPrefix+"check", http.HandlerFunc(result.maybeReverseProxyHttpStatusOnly), "") registerWithPrefix("/", http.HandlerFunc(result.maybeReverseProxyOrPage), "") diff --git a/lib/http.go b/lib/http.go index eca6af5..46af196 100644 --- a/lib/http.go +++ b/lib/http.go @@ -102,7 +102,14 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic return } - component, err := impl.Issue(r, lg, rule, challengeStr, ogTags) + in := &challenge.IssueInput{ + Impressum: s.policy.Impressum, + Rule: rule, + Challenge: challengeStr, + OGTags: ogTags, + } + + component, err := impl.Issue(r, lg, in) if err != nil { lg.Error("[unexpected] render failed, please open an issue", "err", err) // This is likely a bug in the template. Should never be triggered as CI tests for this. s.respondWithError(w, r, "Internal Server Error: please contact the administrator and ask them to look for the logs around \"RenderIndex\"") @@ -118,7 +125,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic func (s *Server) RenderBench(w http.ResponseWriter, r *http.Request) { templ.Handler( - web.Base("Benchmarking Anubis!", web.Bench()), + web.Base("Benchmarking Anubis!", web.Bench(), s.policy.Impressum), ).ServeHTTP(w, r) } @@ -127,7 +134,7 @@ func (s *Server) respondWithError(w http.ResponseWriter, r *http.Request, messag } func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg string, status int) { - templ.Handler(web.Base("Oh noes!", web.ErrorPage(msg, s.opts.WebmasterEmail)), templ.WithStatus(status)).ServeHTTP(w, r) + templ.Handler(web.Base("Oh noes!", web.ErrorPage(msg, s.opts.WebmasterEmail), s.policy.Impressum), templ.WithStatus(status)).ServeHTTP(w, r) } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -180,7 +187,7 @@ func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) { } templ.Handler( - web.Base("You are not a bot!", web.StaticHappy()), + web.Base("You are not a bot!", web.StaticHappy(), s.policy.Impressum), ).ServeHTTP(w, r) } else { requestsProxied.WithLabelValues(r.Host).Inc() diff --git a/lib/policy/config/config.go b/lib/policy/config/config.go index 3de27df..18aceb0 100644 --- a/lib/policy/config/config.go +++ b/lib/policy/config/config.go @@ -327,6 +327,7 @@ type fileConfig struct { Bots []BotOrImport `json:"bots"` DNSBL bool `json:"dnsbl"` OpenGraph openGraphFileConfig `json:"openGraph,omitempty"` + Impressum *Impressum `json:"impressum,omitempty"` StatusCodes StatusCodes `json:"status_codes"` Thresholds []Threshold `json:"thresholds"` } @@ -421,6 +422,14 @@ func Load(fin io.Reader, fname string) (*Config, error) { } } + if c.Impressum != nil { + if err := c.Impressum.Valid(); err != nil { + validationErrs = append(validationErrs, err) + } + + result.Impressum = c.Impressum + } + if len(c.Thresholds) == 0 { c.Thresholds = DefaultThresholds } @@ -445,6 +454,7 @@ type Config struct { Bots []BotConfig Thresholds []Threshold DNSBL bool + Impressum *Impressum OpenGraph OpenGraph StatusCodes StatusCodes } diff --git a/lib/policy/config/impressum.go b/lib/policy/config/impressum.go new file mode 100644 index 0000000..a794fa7 --- /dev/null +++ b/lib/policy/config/impressum.go @@ -0,0 +1,71 @@ +package config + +import ( + "context" + "errors" + "fmt" + "io" +) + +var ErrMissingValue = errors.New("config: missing value") + +type Impressum struct { + Footer string `json:"footer" yaml:"footer"` + Page ImpressumPage `json:"page" yaml:"page"` +} + +func (i Impressum) Render(_ context.Context, w io.Writer) error { + if _, err := fmt.Fprint(w, i.Footer); err != nil { + return err + } + return nil +} + +func (i Impressum) Valid() error { + var errs []error + + if len(i.Footer) == 0 { + errs = append(errs, fmt.Errorf("%w: impressum footer must be defined", ErrMissingValue)) + } + + if err := i.Page.Valid(); err != nil { + errs = append(errs, err) + } + + if len(errs) != 0 { + return errors.Join(errs...) + } + + return nil +} + +type ImpressumPage struct { + Title string `json:"title" yaml:"title"` + Body string `json:"body" yaml:"body"` +} + +func (ip ImpressumPage) Render(_ context.Context, w io.Writer) error { + if _, err := fmt.Fprint(w, ip.Body); err != nil { + return err + } + + return nil +} + +func (ip ImpressumPage) Valid() error { + var errs []error + + if len(ip.Title) == 0 { + errs = append(errs, fmt.Errorf("%w: impressum page title must be defined", ErrMissingValue)) + } + + if len(ip.Body) == 0 { + errs = append(errs, fmt.Errorf("%w: impressum body title must be defined", ErrMissingValue)) + } + + if len(errs) != 0 { + return errors.Join(errs...) + } + + return nil +} diff --git a/lib/policy/config/impressum_test.go b/lib/policy/config/impressum_test.go new file mode 100644 index 0000000..91424b5 --- /dev/null +++ b/lib/policy/config/impressum_test.go @@ -0,0 +1,62 @@ +package config + +import ( + "bytes" + "errors" + "testing" +) + +func TestImpressumValid(t *testing.T) { + for _, cs := range []struct { + name string + inp Impressum + err error + }{ + { + name: "basic happy path", + inp: Impressum{ + Footer: "Website hosted by Techaro.
", + Page: ImpressumPage{ + Title: "Techaro Imprint", + Body: "
This is an imprint page.
", + }, + }, + err: nil, + }, + { + name: "no footer", + inp: Impressum{ + Footer: "", + Page: ImpressumPage{ + Title: "Techaro Imprint", + Body: "This is an imprint page.
", + }, + }, + err: ErrMissingValue, + }, + { + name: "page not valid", + inp: Impressum{ + Footer: "test page please ignore", + }, + err: ErrMissingValue, + }, + } { + t.Run(cs.name, func(t *testing.T) { + if err := cs.inp.Valid(); !errors.Is(err, cs.err) { + t.Logf("want: %v", cs.err) + t.Logf("got: %v", err) + t.Error("validation failed") + } + + var buf bytes.Buffer + if err := cs.inp.Render(t.Context(), &buf); err != nil { + t.Errorf("can't render footer: %v", err) + } + + if err := cs.inp.Page.Render(t.Context(), &buf); err != nil { + t.Errorf("can't render page: %v", err) + } + }) + } +} diff --git a/lib/policy/config/testdata/bad/impressum-no-footer.yaml b/lib/policy/config/testdata/bad/impressum-no-footer.yaml new file mode 100644 index 0000000..e0d8be0 --- /dev/null +++ b/lib/policy/config/testdata/bad/impressum-no-footer.yaml @@ -0,0 +1,11 @@ +bots: + - name: simple-weight-adjust + action: WEIGH + user_agent_regex: Mozilla + weight: + adjust: 5 + +impressum: + page: + title: Test + body:This is a test
diff --git a/lib/policy/config/testdata/bad/impressum-no-page-contents.yaml b/lib/policy/config/testdata/bad/impressum-no-page-contents.yaml new file mode 100644 index 0000000..4eeb4d0 --- /dev/null +++ b/lib/policy/config/testdata/bad/impressum-no-page-contents.yaml @@ -0,0 +1,10 @@ +bots: + - name: simple-weight-adjust + action: WEIGH + user_agent_regex: Mozilla + weight: + adjust: 5 + +impressum: + footer: "Hi there these are WORDS on the INTERNET." + page: {} diff --git a/lib/policy/config/testdata/good/impressum.yaml b/lib/policy/config/testdata/good/impressum.yaml new file mode 100644 index 0000000..9a1a03f --- /dev/null +++ b/lib/policy/config/testdata/good/impressum.yaml @@ -0,0 +1,10 @@ +bots: + - name: simple + action: CHALLENGE + user_agent_regex: Mozilla + +impressum: + footer: "Hi these are WORDS on the INTERNET." + page: + title: Test + body:This is a test
diff --git a/lib/policy/policy.go b/lib/policy/policy.go index 9ead154..9ee6efc 100644 --- a/lib/policy/policy.go +++ b/lib/policy/policy.go @@ -31,6 +31,7 @@ type ParsedConfig struct { Bots []Bot Thresholds []*Threshold DNSBL bool + Impressum *config.Impressum OpenGraph config.OpenGraph DefaultDifficulty int StatusCodes config.StatusCodes @@ -150,6 +151,8 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic parsedBot.Weight = b.Weight } + result.Impressum = c.Impressum + parsedBot.Rules = cl result.Bots = append(result.Bots, parsedBot) diff --git a/web/index.go b/web/index.go index 409dc59..0e4ade1 100644 --- a/web/index.go +++ b/web/index.go @@ -6,12 +6,12 @@ import ( "github.com/TecharoHQ/anubis/lib/policy/config" ) -func Base(title string, body templ.Component) templ.Component { - return base(title, body, nil, nil) +func Base(title string, body templ.Component, impressum *config.Impressum) templ.Component { + return base(title, body, impressum, nil, nil) } -func BaseWithChallengeAndOGTags(title string, body templ.Component, challenge string, rules *config.ChallengeRules, ogTags map[string]string) (templ.Component, error) { - return base(title, body, struct { +func BaseWithChallengeAndOGTags(title string, body templ.Component, impressum *config.Impressum, challenge string, rules *config.ChallengeRules, ogTags map[string]string) (templ.Component, error) { + return base(title, body, impressum, struct { Rules *config.ChallengeRules `json:"rules"` Challenge string `json:"challenge"` }{ diff --git a/web/index.templ b/web/index.templ index e43cf07..59e0559 100644 --- a/web/index.templ +++ b/web/index.templ @@ -1,11 +1,13 @@ package web import ( + "fmt" "github.com/TecharoHQ/anubis" + "github.com/TecharoHQ/anubis/lib/policy/config" "github.com/TecharoHQ/anubis/xess" ) -templ base(title string, body templ.Component, challenge any, ogTags map[string]string) { +templ base(title string, body templ.Component, impressum *config.Impressum, challenge any, ogTags map[string]string) { @@ -36,16 +38,16 @@ templ base(title string, body templ.Component, challenge any, ogTags map[string] } #progress { - display: none; - width: 90%; - width: min(20rem, 90%); - height: 2rem; - border-radius: 1rem; - overflow: hidden; - margin: 1rem 0 2rem; - outline-offset: 2px; - outline: #b16286 solid 4px; - } + display: none; + width: 90%; + width: min(20rem, 90%); + height: 2rem; + border-radius: 1rem; + overflow: hidden; + margin: 1rem 0 2rem; + outline-offset: 2px; + outline: #b16286 solid 4px; + } .bar-inner { background-color: #b16286; @@ -53,7 +55,7 @@ templ base(title string, body templ.Component, challenge any, ogTags map[string] width: 0; transition: width 0.25s ease-in; } - + @templ.JSONScript("anubis_version", anubis.Version) if challenge != nil { @templ.JSONScript("anubis_challenge", challenge) @@ -74,6 +76,10 @@ templ base(title string, body templ.Component, challenge any, ogTags map[string] >Techaro. Made with ❤️ in 🇨🇦.Mascot design by CELPHASE.
+ if impressum != nil { +@templ.Raw(impressum.Footer) +-- Imprint
+ } diff --git a/web/index_templ.go b/web/index_templ.go index ac02f28..8502c01 100644 --- a/web/index_templ.go +++ b/web/index_templ.go @@ -9,11 +9,13 @@ import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" import ( + "fmt" "github.com/TecharoHQ/anubis" + "github.com/TecharoHQ/anubis/lib/policy/config" "github.com/TecharoHQ/anubis/xess" ) -func base(title string, body templ.Component, challenge any, ogTags map[string]string) templ.Component { +func base(title string, body templ.Component, impressum *config.Impressum, 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 { @@ -41,7 +43,7 @@ func base(title string, body templ.Component, challenge any, ogTags map[string]s var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 12, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 14, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -54,7 +56,7 @@ func base(title string, body templ.Component, challenge any, ogTags map[string]s var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + xess.URL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 13, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 15, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -72,7 +74,7 @@ func base(title string, body templ.Component, challenge any, ogTags map[string]s var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(key) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 17, Col: 24} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 19, Col: 24} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -85,7 +87,7 @@ func base(title string, body templ.Component, challenge any, ogTags map[string]s var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(value) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 17, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 19, Col: 42} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -96,7 +98,7 @@ func base(title string, body templ.Component, challenge any, ogTags map[string]s return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -121,7 +123,7 @@ func base(title string, body templ.Component, challenge any, ogTags map[string]s 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: 66, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 68, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -135,7 +137,38 @@ func base(title string, body templ.Component, challenge any, ogTags map[string]s if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "