mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-06 00:38:18 +00:00
Compare commits
23 Commits
v1.20.0
...
Xe/docs-bo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8babcf87ca | ||
|
|
322224446a | ||
|
|
3cf9275f21 | ||
|
|
4e176b557c | ||
|
|
888b7d6e77 | ||
|
|
0e43138324 | ||
|
|
c981c23f7e | ||
|
|
9f0c5e974e | ||
|
|
292c470ada | ||
|
|
12453fdc00 | ||
|
|
f5b3bf81bc | ||
|
|
1820649987 | ||
|
|
14eeeb56d6 | ||
|
|
d9e0fbe905 | ||
|
|
6aa17532da | ||
|
|
b1edf84a7c | ||
|
|
d47a3406db | ||
|
|
ff5991b5cf | ||
|
|
19f78f37ad | ||
|
|
b0b0a5c08a | ||
|
|
261306dc63 | ||
|
|
3520421757 | ||
|
|
ad5430612f |
12
.devcontainer/Dockerfile
Normal file
12
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM ghcr.io/xe/devcontainer-base/pre/go
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod go.sum package.json package-lock.json ./
|
||||
RUN go install github.com/a-h/templ/cmd/templ \
|
||||
&& npx --yes playwright@1.52.0 install --with-deps\
|
||||
&& apt-get update \
|
||||
&& apt-get -y install zstd brotli \
|
||||
&& mkdir -p /home/vscode/.local/share/fish \
|
||||
&& chown -R vscode:vscode /home/vscode/.local/share/fish \
|
||||
&& chown -R vscode:vscode /go
|
||||
13
.devcontainer/README.md
Normal file
13
.devcontainer/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Anubis Dev Container
|
||||
|
||||
Anubis offers a [development container](https://containers.dev/) image in order to make it easier to contribute to the project. This image is based on [Xe/devcontainer-base/go](https://github.com/Xe/devcontainer-base/tree/main/src/go), which is based on Debian Bookworm with the following customizations:
|
||||
|
||||
- [Fish](https://fishshell.com/) as the shell complete with a custom theme
|
||||
- [Go](https://go.dev) at the most recent stable version
|
||||
- [Node.js](https://nodejs.org/en) at the most recent stable version
|
||||
- [Atuin](https://atuin.sh/) to sync shell history between your host OS and the development container
|
||||
- [Docker](https://docker.com) to manage and build Anubis container images from inside the development container
|
||||
- [Ko](https://ko.build/) to build production-ready Anubis container images
|
||||
- [Neovim](https://neovim.io/) for use with Git
|
||||
|
||||
This development container is tested and known to work with [Visual Studio Code](https://code.visualstudio.com/). If you run into problems with it outside of VS Code, please file an issue and let us know what editor you are using.
|
||||
34
.devcontainer/devcontainer.json
Normal file
34
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,34 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
|
||||
{
|
||||
"name": "Dev",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"build": {
|
||||
"dockerfile": "./Dockerfile",
|
||||
"context": "..",
|
||||
"cacheFrom": [
|
||||
"type=registry,ref=ghcr.io/techarohq/anubis/devcontainer"
|
||||
]
|
||||
},
|
||||
"postStartCommand": "npm ci && go mod download",
|
||||
"features": {
|
||||
"ghcr.io/xe/devcontainer-features/ko:1.1.0": {}
|
||||
},
|
||||
"initializeCommand": "mkdir -p ${localEnv:HOME}${localEnv:USERPROFILE}/.local/share/atuin",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"esbenp.prettier-vscode",
|
||||
"ms-azuretools.vscode-containers",
|
||||
"golang.go",
|
||||
"unifiedjs.vscode-mdx",
|
||||
"a-h.templ",
|
||||
"redhat.vscode-yaml"
|
||||
]
|
||||
}
|
||||
},
|
||||
"forwardPorts": [
|
||||
8923,
|
||||
3000
|
||||
]
|
||||
}
|
||||
2
.github/actions/spelling/excludes.txt
vendored
2
.github/actions/spelling/excludes.txt
vendored
@@ -89,3 +89,5 @@
|
||||
^lib/policy/config/testdata/bad/unparseable\.json$
|
||||
ignore$
|
||||
robots.txt
|
||||
^lib/localization/locales/.*\.json$
|
||||
^lib/localization/.*_test.go$
|
||||
|
||||
24
.github/actions/spelling/expect.txt
vendored
24
.github/actions/spelling/expect.txt
vendored
@@ -13,16 +13,20 @@ asnc
|
||||
asnchecker
|
||||
asns
|
||||
aspirational
|
||||
atuin
|
||||
azuretools
|
||||
badregexes
|
||||
bdba
|
||||
berr
|
||||
bingbot
|
||||
bitcoin
|
||||
bitrate
|
||||
blogging
|
||||
Bluesky
|
||||
blueskybot
|
||||
boi
|
||||
botnet
|
||||
botstopper
|
||||
BPort
|
||||
Brightbot
|
||||
broked
|
||||
@@ -33,7 +37,7 @@ Caddyfile
|
||||
caninetools
|
||||
Cardyb
|
||||
celchecker
|
||||
CELPHASE
|
||||
celphase
|
||||
cerr
|
||||
certresolver
|
||||
cespare
|
||||
@@ -48,11 +52,14 @@ chibi
|
||||
cidranger
|
||||
ckie
|
||||
cloudflare
|
||||
Codespaces
|
||||
confd
|
||||
connnection
|
||||
containerbuild
|
||||
coreutils
|
||||
Cotoyogi
|
||||
CRDs
|
||||
Cromite
|
||||
crt
|
||||
Cscript
|
||||
daemonizing
|
||||
@@ -71,9 +78,11 @@ domainhere
|
||||
dracula
|
||||
dronebl
|
||||
droneblresponse
|
||||
dropin
|
||||
duckduckbot
|
||||
eerror
|
||||
ellenjoe
|
||||
emacs
|
||||
enbyware
|
||||
etld
|
||||
everyones
|
||||
@@ -87,6 +96,7 @@ facebookgo
|
||||
Factset
|
||||
fastcgi
|
||||
fediverse
|
||||
ffprobe
|
||||
finfos
|
||||
Firecrawl
|
||||
flagenv
|
||||
@@ -165,7 +175,6 @@ Linting
|
||||
linuxbrew
|
||||
LLU
|
||||
loadbalancer
|
||||
locahost
|
||||
lol
|
||||
LOMINSA
|
||||
maintainership
|
||||
@@ -183,6 +192,7 @@ mozilla
|
||||
nbf
|
||||
netsurf
|
||||
nginx
|
||||
nicksnyder
|
||||
nobots
|
||||
NONINFRINGEMENT
|
||||
nosleep
|
||||
@@ -193,6 +203,7 @@ omgilibot
|
||||
openai
|
||||
opengraph
|
||||
openrc
|
||||
oswald
|
||||
pag
|
||||
palemoon
|
||||
Pangu
|
||||
@@ -221,6 +232,7 @@ qwantbot
|
||||
rac
|
||||
rawler
|
||||
rcvar
|
||||
redhat
|
||||
redir
|
||||
redirectscheme
|
||||
refactors
|
||||
@@ -231,6 +243,7 @@ risc
|
||||
ruleset
|
||||
runlevels
|
||||
RUnlock
|
||||
runtimedir
|
||||
sas
|
||||
sasl
|
||||
Scumm
|
||||
@@ -246,6 +259,7 @@ shellcheck
|
||||
Sidetrade
|
||||
simprint
|
||||
sitemap
|
||||
skopeo
|
||||
sls
|
||||
sni
|
||||
Sourceware
|
||||
@@ -263,6 +277,8 @@ SVCNAME
|
||||
tagline
|
||||
tarballs
|
||||
tarrif
|
||||
tbn
|
||||
tbr
|
||||
techaro
|
||||
techarohq
|
||||
templ
|
||||
@@ -275,12 +291,14 @@ Tik
|
||||
Timpibot
|
||||
traefik
|
||||
uberspace
|
||||
unifiedjs
|
||||
unixhttpd
|
||||
unmarshal
|
||||
unparseable
|
||||
uuidgen
|
||||
uvx
|
||||
UXP
|
||||
Valkey
|
||||
Varis
|
||||
Velen
|
||||
vendored
|
||||
@@ -318,5 +336,5 @@ yourdomain
|
||||
yoursite
|
||||
Zenos
|
||||
zizmor
|
||||
Zonbocom
|
||||
zombocom
|
||||
zos
|
||||
|
||||
47
.github/workflows/devcontainer.yml
vendored
Normal file
47
.github/workflows/devcontainer.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Dev container prebuild
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
tags: ["v*.*.*"]
|
||||
|
||||
jobs:
|
||||
devcontainer:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install skopeo
|
||||
|
||||
- name: Log into registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: techarohq
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Pre-build dev container image
|
||||
uses: devcontainers/ci@8bf61b26e9c3a98f69cb6ce2f88d24ff59b785c6 # v0.3.1900000417
|
||||
with:
|
||||
imageName: ghcr.io/techarohq/anubis/devcontainer
|
||||
cacheFrom: ghcr.io/techarohq/anubis/devcontainer
|
||||
push: always
|
||||
platform: linux/amd64,linux/arm64
|
||||
4
.github/workflows/zizmor.yml
vendored
4
.github/workflows/zizmor.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: Run zizmor 🌈
|
||||
run: uvx zizmor --format sarif . > results.sarif
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
uses: github/codeql-action/upload-sarif@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
category: zizmor
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -20,3 +20,5 @@ node_modules
|
||||
|
||||
# how does this get here
|
||||
doc/VERSION
|
||||
|
||||
web/static/locales/*.json
|
||||
10
.vscode/extensions.json
vendored
Normal file
10
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"ms-azuretools.vscode-containers",
|
||||
"golang.go",
|
||||
"unifiedjs.vscode-mdx",
|
||||
"a-h.templ",
|
||||
"redhat.vscode-yaml"
|
||||
]
|
||||
}
|
||||
27
.vscode/launch.json
vendored
Normal file
27
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${fileDirname}"
|
||||
},
|
||||
{
|
||||
"name": "Anubis [dev]",
|
||||
"command": "npm run dev",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"name": "Start Docs",
|
||||
"command": "cd docs && npm ci && npm run start",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||

|
||||

|
||||

|
||||
[](https://github.com/sponsors/Xe)
|
||||
|
||||
## Sponsors
|
||||
|
||||
|
||||
13
anubis.go
13
anubis.go
@@ -11,12 +11,11 @@ var Version = "devel"
|
||||
|
||||
// CookieName is the name of the cookie that Anubis uses in order to validate
|
||||
// access.
|
||||
const CookieName = "techaro.lol-anubis-auth"
|
||||
var CookieName = "techaro.lol-anubis-auth"
|
||||
|
||||
// WithDomainCookieName is the name that is prepended to the per-domain cookie used when COOKIE_DOMAIN is set.
|
||||
const WithDomainCookieName = "techaro.lol-anubis-auth-for-"
|
||||
|
||||
const TestCookieName = "techaro.lol-anubis-cookie-test-if-you-block-this-anubis-wont-work"
|
||||
// TestCookieName is the name of the cookie that Anubis uses in order to check
|
||||
// if cookies are enabled on the client's browser.
|
||||
var TestCookieName = "techaro.lol-anubis-cookie-verification"
|
||||
|
||||
// CookieDefaultExpirationTime is the amount of time before the cookie/JWT expires.
|
||||
const CookieDefaultExpirationTime = 7 * 24 * time.Hour
|
||||
@@ -33,3 +32,7 @@ const APIPrefix = "/.within.website/x/cmd/anubis/api/"
|
||||
// DefaultDifficulty is the default "difficulty" (number of leading zeroes)
|
||||
// that must be met by the client in order to pass the challenge.
|
||||
const DefaultDifficulty = 4
|
||||
|
||||
// ForcedLanguage is the language being used instead of the one of the request's Accept-Language header
|
||||
// if being set.
|
||||
var ForcedLanguage = ""
|
||||
|
||||
@@ -48,8 +48,11 @@ var (
|
||||
cookieDomain = flag.String("cookie-domain", "", "if set, the top-level domain that the Anubis cookie will be valid for")
|
||||
cookieDynamicDomain = flag.Bool("cookie-dynamic-domain", false, "if set, automatically set the cookie Domain value based on the request domain")
|
||||
cookieExpiration = flag.Duration("cookie-expiration-time", anubis.CookieDefaultExpirationTime, "The amount of time the authorization cookie is valid for")
|
||||
cookiePrefix = flag.String("cookie-prefix", "techaro.lol-anubis", "prefix for browser cookies created by Anubis")
|
||||
cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
|
||||
forcedLanguage = flag.String("forced-language", "", "if set, this language is being used instead of the one from the request's Accept-Language header")
|
||||
hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set")
|
||||
cookieSecure = flag.Bool("cookie-secure", true, "if true, sets the secure flag on Anubis cookies")
|
||||
ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned")
|
||||
ed25519PrivateKeyHexFile = flag.String("ed25519-private-key-hex-file", "", "file name containing value for ed25519-private-key-hex")
|
||||
metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to")
|
||||
@@ -107,8 +110,41 @@ func doHealthCheck() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseBindNetFromAddr determine bind network and address based on the given network and address.
|
||||
func parseBindNetFromAddr(address string) (string, string) {
|
||||
defaultScheme := "http://"
|
||||
if !strings.Contains(address, "://") {
|
||||
if strings.HasPrefix(address, ":") {
|
||||
address = defaultScheme + "localhost" + address
|
||||
} else {
|
||||
address = defaultScheme + address
|
||||
}
|
||||
}
|
||||
|
||||
bindUri, err := url.Parse(address)
|
||||
if err != nil {
|
||||
log.Fatal(fmt.Errorf("failed to parse bind URL: %w", err))
|
||||
}
|
||||
|
||||
switch bindUri.Scheme {
|
||||
case "unix":
|
||||
return "unix", bindUri.Path
|
||||
case "tcp", "http", "https":
|
||||
return "tcp", bindUri.Host
|
||||
default:
|
||||
log.Fatal(fmt.Errorf("unsupported network scheme %s in address %s", bindUri.Scheme, address))
|
||||
}
|
||||
return "", address
|
||||
}
|
||||
|
||||
func setupListener(network string, address string) (net.Listener, string) {
|
||||
formattedAddress := ""
|
||||
|
||||
if network == "" {
|
||||
// keep compatibility
|
||||
network, address = parseBindNetFromAddr(address)
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "unix":
|
||||
formattedAddress = "unix:" + address
|
||||
@@ -341,6 +377,10 @@ func main() {
|
||||
slog.Warn("REDIRECT_DOMAINS is not set, Anubis will only redirect to the same domain a request is coming from, see https://anubis.techaro.lol/docs/admin/configuration/redirect-domains")
|
||||
}
|
||||
|
||||
anubis.CookieName = *cookiePrefix + "-auth"
|
||||
anubis.TestCookieName = *cookiePrefix + "-cookie-verification"
|
||||
anubis.ForcedLanguage = *forcedLanguage
|
||||
|
||||
// If OpenGraph configuration values are not set in the config file, use the
|
||||
// values from flags / envvars.
|
||||
if !policy.OpenGraph.Enabled {
|
||||
@@ -351,20 +391,22 @@ func main() {
|
||||
}
|
||||
|
||||
s, err := libanubis.New(libanubis.Options{
|
||||
BasePrefix: *basePrefix,
|
||||
StripBasePrefix: *stripBasePrefix,
|
||||
Next: rp,
|
||||
Policy: policy,
|
||||
ServeRobotsTXT: *robotsTxt,
|
||||
ED25519PrivateKey: ed25519Priv,
|
||||
HS512Secret: []byte(*hs512Secret),
|
||||
CookieDomain: *cookieDomain,
|
||||
CookieExpiration: *cookieExpiration,
|
||||
CookiePartitioned: *cookiePartitioned,
|
||||
RedirectDomains: redirectDomainsList,
|
||||
Target: *target,
|
||||
WebmasterEmail: *webmasterEmail,
|
||||
OpenGraph: policy.OpenGraph,
|
||||
BasePrefix: *basePrefix,
|
||||
StripBasePrefix: *stripBasePrefix,
|
||||
Next: rp,
|
||||
Policy: policy,
|
||||
ServeRobotsTXT: *robotsTxt,
|
||||
ED25519PrivateKey: ed25519Priv,
|
||||
HS512Secret: []byte(*hs512Secret),
|
||||
CookieDomain: *cookieDomain,
|
||||
CookieDynamicDomain: *cookieDynamicDomain,
|
||||
CookieExpiration: *cookieExpiration,
|
||||
CookiePartitioned: *cookiePartitioned,
|
||||
RedirectDomains: redirectDomainsList,
|
||||
Target: *target,
|
||||
WebmasterEmail: *webmasterEmail,
|
||||
OpenGraph: policy.OpenGraph,
|
||||
CookieSecure: *cookieSecure,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("can't construct libanubis.Server: %v", err)
|
||||
|
||||
@@ -88,7 +88,7 @@ dnsbl: false
|
||||
# impressum:
|
||||
# # Displayed at the bottom of every page rendered by Anubis.
|
||||
# footer: >-
|
||||
# This website is hosted by Zonbocom. If you have any complaints or notes
|
||||
# This website is hosted by Zombocom. If you have any complaints or notes
|
||||
# about the service, please contact
|
||||
# <a href="mailto:contact@domainhere.example">contact@domainhere.example</a>
|
||||
# and we will assist you as soon as possible.
|
||||
|
||||
248
docs/blog/2025-06-27-release-1.20.0/index.mdx
Normal file
248
docs/blog/2025-06-27-release-1.20.0/index.mdx
Normal file
@@ -0,0 +1,248 @@
|
||||
---
|
||||
slug: release/v1.20.0
|
||||
title: Anubis v1.20.0 is now available!
|
||||
authors: [xe]
|
||||
tags: [release]
|
||||
image: sunburst.webp
|
||||
---
|
||||
|
||||

|
||||
|
||||
Hey all!
|
||||
|
||||
Today we released [Anubis v1.20.0: Thancred Waters](https://github.com/TecharoHQ/anubis/releases/tag/v1.20.0). This adds a lot of new and exciting features to Anubis, including but not limited to the `WEIGH` action, custom weight thresholds, Imprint/impressum support, and a no-JS challenge. Here's what you need to know so you can protect your websites in new and exciting ways!
|
||||
|
||||
{/* truncate */}
|
||||
|
||||
## Sponsoring the product
|
||||
|
||||
If you rely on Anubis to keep your website safe, please consider sponsoring the project on [GitHub Sponsors](https://github.com/sponsors/Xe) or [Patreon](https://patreon.com/cadey). Funding helps pay hosting bills and offset the time spent on making this project the best it can be. Every little bit helps and when enough money is raised, [I can make Anubis my full-time job](https://github.com/TecharoHQ/anubis/discussions/278).
|
||||
|
||||
I am waiting to hear back from NLNet on if Anubis was selected for funding or not. Let's hope it is!
|
||||
|
||||
## Deprecation warning: `DEFAULT_DIFFICULTY`
|
||||
|
||||
Anubis v1.20.0 is the last version to support the `DEFAULT_DIFFICULTY` flag in the exact way it currently does. In future versions, this will be ineffectual and you should use the [custom threshold system](/docs/admin/configuration/thresholds) instead.
|
||||
|
||||
If this becomes an imposition in practice, this will be reverted.
|
||||
|
||||
## Chrome won't show "invalid response" after "Success!"
|
||||
|
||||
There were a bunch of smaller fixes in Anubis this time around, but the biggest one was finally squashing the ["invalid response" after "Success!" issue](https://github.com/TecharoHQ/anubis/issues/564) that had been plaguing Chrome users. This was a really annoying issue to track down but it was discovered while we were working on better end-to-end / functional testing: [Chrome randomizes the `Accept-Language` header](https://github.com/explainers-by-googlers/reduce-accept-language) so that websites can't do fingerprinting as easily.
|
||||
|
||||
When Anubis issues a challenge, it grabs [information that the browser sends to the user](/docs/design/how-anubis-works#challenge-format) to create a challenge string. Anubis doesn't store these challenge strings anywhere, and when a solution is being checked it calculates the challenge string from the request. This means that they'd get a challenge on one end, compute the response for that challenge, and then the server would validate that against a different challenge. This server-side validation would fail, leading to the user seeing "invalid response" after the client reported success.
|
||||
|
||||
I suspect this was why Vanadium and Cromite were having sporadic issues as well.
|
||||
|
||||
## New Features
|
||||
|
||||
The biggest feature in Anubis is the "weight" subsystem. This allows administrators to make custom rules that change the suspicion level of a request without having to take immediate action. As an example, consider the self-hostable git forge [Gitea](https://about.gitea.com/). When you load a page in Gitea, it creates a session cookie that your browser sends with every request. Weight allows you to mark a request that includes a Gitea session token as _less_ suspicious:
|
||||
|
||||
```yaml
|
||||
- name: gitea-session-token
|
||||
action: WEIGH
|
||||
expression:
|
||||
all:
|
||||
# Check if the request has a Cookie header
|
||||
- '"Cookie" in headers'
|
||||
# Check if the request's Cookie header contains the Gitea session token
|
||||
- headers["Cookie"].contains("i_love_gitea=")
|
||||
# Remove 5 weight points
|
||||
weight:
|
||||
adjust: -5
|
||||
```
|
||||
|
||||
This is different from the past where you could only allow every request with a Gitea session token, meaning that the invention of lying would allow malicious clients to bypass protection.
|
||||
|
||||
Weight is added and removed whenever a `WEIGH` rule is encountered. When all rules are processed and the request doesn't match any `ALLOW`, `CHALLENGE`, or `DENY` rules, Anubis uses [weight thresholds](/docs/admin/configuration/thresholds) to figure out how to handle that request. Thresholds are defined in the [policy file](/docs/admin/policies) alongside your bot rules:
|
||||
|
||||
```yaml
|
||||
thresholds:
|
||||
- name: minimal-suspicion # This client is likely fine, its soul is lighter than a feather
|
||||
expression: weight <= 0 # a feather weighs zero units
|
||||
action: ALLOW # Allow the traffic through
|
||||
# For clients that had some weight reduced through custom rules, give them a
|
||||
# lightweight challenge.
|
||||
- name: mild-suspicion
|
||||
expression:
|
||||
all:
|
||||
- weight > 0
|
||||
- weight < 10
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
# https://anubis.techaro.lol/docs/admin/configuration/challenges/metarefresh
|
||||
algorithm: metarefresh
|
||||
difficulty: 1
|
||||
report_as: 1
|
||||
# For clients that are browser-like but have either gained points from custom rules or
|
||||
# report as a standard browser.
|
||||
- name: moderate-suspicion
|
||||
expression:
|
||||
all:
|
||||
- weight >= 10
|
||||
- weight < 20
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
# https://anubis.techaro.lol/docs/admin/configuration/challenges/proof-of-work
|
||||
algorithm: fast
|
||||
difficulty: 2 # two leading zeros, very fast for most clients
|
||||
report_as: 2
|
||||
# For clients that are browser like and have gained many points from custom rules
|
||||
- name: extreme-suspicion
|
||||
expression: weight >= 20
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
# https://anubis.techaro.lol/docs/admin/configuration/challenges/proof-of-work
|
||||
algorithm: fast
|
||||
difficulty: 4
|
||||
report_as: 4
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
If you don't have thresholds defined in your Anubis policy file, Anubis will default to the "legacy" behaviour where browser-like clients get a challenge at the default difficulty.
|
||||
|
||||
:::
|
||||
|
||||
This lets most clients through if they pass a simple [proof of work challenge](/docs/admin/configuration/challenges/proof-of-work), but any clients that are less suspicious (like ones with a Gitea session token) are given the lightweight [Meta Refresh](/docs/admin/configuration/challenges/metarefresh) challenge instead.
|
||||
|
||||
Threshold expressions are like [Bot rule expressions](/docs/admin/configuration/expressions), but there's only one input: the request's weight. If no thresholds match, the request is allowed through.
|
||||
|
||||
### Imprint/Impressum Support
|
||||
|
||||
European countries like Germany [require an imprint/impressum](https://www.ionos.com/digitalguide/websites/digital-law/a-case-for-thinking-global-germanys-impressum-laws/) to be present in the footer of their website. This allows users to contact someone on the team behind a website in case they run into issues. This also must generally have a separate page where users can view an extended imprint with other information like a privacy policy or a copyright notice.
|
||||
|
||||
Anubis v1.20.0 and later [has support for showing imprints](/docs/admin/configuration/impressum). You can configure two kinds of imprints:
|
||||
|
||||
1. An imprint that is shown in the footer of every Anubis page.
|
||||
2. An extended imprint / privacy policy that is shown when users click on the "Imprint" link. For example, [here's the imprint for the website you're looking at right now](https://anubis.techaro.lol/.within.website/x/cmd/anubis/api/imprint).
|
||||
|
||||
Imprints are configured in [the policy file](/docs/admin/policies/):
|
||||
|
||||
```yaml
|
||||
impressum:
|
||||
# Displayed at the bottom of every page rendered by Anubis.
|
||||
footer: >-
|
||||
This website is hosted by Zombocom. If you have any complaints or notes
|
||||
about the service, please contact
|
||||
<a href="mailto:contact@zombocom.example">contact@zombocom.example</a> 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 <title> of the page
|
||||
title: Imprint and Privacy Policy
|
||||
# The HTML contents of the page. The exact contents of this page can
|
||||
# and will vary by locale. Please consult with a lawyer if you are not
|
||||
# sure what to put here.
|
||||
body: >-
|
||||
<p>Last updated: June 2025</p>
|
||||
|
||||
<h2>Information that is gathered from visitors</h2>
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
<p>Cookies may be used to remember visitor preferences when interacting
|
||||
with the website.</p>
|
||||
|
||||
<p>Where registration is required, the visitor's email and a username
|
||||
will be stored on the server.</p>
|
||||
|
||||
<!-- ... -->
|
||||
```
|
||||
|
||||
If this is insufficient, please [file an issue](https://github.com/TecharoHQ/anubis/issues/new) with a link to the relevant legislation for your country so that this feature can be amended and improved.
|
||||
|
||||
### No-JS Challenge
|
||||
|
||||
One of the first issues in Anubis before it was moved to the [TecharoHQ org](https://github.com/TecharoHQ) was a request [to support challenging browsers without using JavaScript](https://github.com/Xe/x/issues/651). This is a pretty challenging thing to do without rethinking how Anubis works from a fundamentally low level, and with v1.20.0, [Anubis finally has support for running without client-side JavaScript](https://github.com/TecharoHQ/anubis/issues/95) thanks to the [Meta Refresh](/docs/admin/configuration/challenges/metarefresh) challenge.
|
||||
|
||||
When Anubis decides it needs to send a challenge to your browser, it sends a challenge page. Historically, this challenge page is [an HTML template](https://github.com/TecharoHQ/anubis/blob/main/web/index.templ) that kicks off some JavaScript, reads the challenge information out of the page body, and then solves it as fast as possible in order to let users see the website they want to visit.
|
||||
|
||||
In v1.20.0, Anubis has a challenge registry to hold [different client challenge implementations](/docs/category/challenges). This allows us to implement anything we want as long as it can render a page to show a challenge and then check if the result is correct. This is going to be used to implement a WebAssembly-based proof of work option (one that will be way more efficient than the existing browser JS version), but as a proof of concept I implemented a simple challenge using [HTML `<meta refresh>`](https://en.wikipedia.org/wiki/Meta_refresh).
|
||||
|
||||
In my testing, this has worked with every browser I have thrown it at (including CLI browsers, the browser embedded in emacs, etc.). The default configuration of Anubis does use the [meta refresh challenge](/docs/admin/configuration/challenges/metarefresh) for [clients with a very low suspicion](/docs/admin/configuration/thresholds), but by default clients will be sent an [easy proof of work challenge](/docs/admin/configuration/challenges/proof-of-work).
|
||||
|
||||
If the false positive rate of this challenge turns out to not be very high in practice, the meta refresh challenge will be enabled by default for browsers in future versions of Anubis.
|
||||
|
||||
### `robots2policy`
|
||||
|
||||
Anubis was created because crawler bots don't respect [`robots.txt` files](https://www.robotstxt.org/). Administrators have been working on refining and crafting their `robots.txt` files for years, and one common comment is that people don't know where to start crafting their own rules.
|
||||
|
||||
Anubis now ships with a [`robots2policy` tool](/docs/admin/robots2policy) that lets you convert your `robots.txt` file to an Anubis policy.
|
||||
|
||||
```text
|
||||
robots2policy -input https://github.com/robots.txt
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
If you installed Anubis from [an OS package](/docs/admin/native-install), you may need to run `anubis-robots2policy` instead of `robots2policy`.
|
||||
|
||||
:::
|
||||
|
||||
We hope that this will help you get started with Anubis faster. We are working on a version of this that will run in the documentation via WebAssembly.
|
||||
|
||||
### Open Graph configuration is being moved to the policy file
|
||||
|
||||
Anubis supports reading [Open Graph tags](/docs/admin/configuration/open-graph) from target services and returning them in challenge pages. This makes the right metadata show up when linking services protected by Anubis in chat applications or on social media.
|
||||
|
||||
In order to test the migration of all of the configuration to the policy file, Open Graph configuration has been moved to the policy file. For more information, please read [the Open Graph configuration options](/docs/admin/configuration/open-graph#configuration-options).
|
||||
|
||||
You can also set default Open Graph tags:
|
||||
|
||||
```yaml
|
||||
openGraph:
|
||||
enabled: true
|
||||
ttl: 24h
|
||||
# If set, return these opengraph values instead of looking them up with
|
||||
# the target service.
|
||||
#
|
||||
# Correlates to properties in https://ogp.me/
|
||||
override:
|
||||
# og:title is required, it is the title of the website
|
||||
"og:title": "Techaro Anubis"
|
||||
"og:description": >-
|
||||
Anubis is a Web AI Firewall Utility that helps you fight the bots
|
||||
away so that you can maintain uptime at work!
|
||||
"description": >-
|
||||
Anubis is a Web AI Firewall Utility that helps you fight the bots
|
||||
away so that you can maintain uptime at work!
|
||||
```
|
||||
|
||||
## Improvements and optimizations
|
||||
|
||||
One of the biggest improvements we've made in v1.20.0 is replacing [SHA-256 with xxhash](https://github.com/TecharoHQ/anubis/pull/676). Anubis uses hashes all over the place to help with identifying clients, matching against rules when allowing traffic through, in error messages sent to users, and more. Historically these have been done with [SHA-256](https://en.wikipedia.org/wiki/SHA-2), however this has been having a mild performance impact in real-world use. As a result, we now use [xxhash](https://xxhash.com/) when possible. This makes policy matching 3x faster in some scenarios and reduces memory usage across the board.
|
||||
|
||||
Anubis now uses [bart](https://pkg.go.dev/github.com/gaissmai/bart) for doing IP address matching when you specify addresses in a `remote_address` check configuration or when you are matching against [advanced checks](/docs/admin/thoth). This uses the same kind of IP address routing configuration that your OS kernel does, making it very fast to query information about IP addresses. This makes IP address range matches anywhere from 3-14 times faster depending on the number of addresses it needs to match against. For more information and benchmarks, check out [@JasonLovesDoggo](https://github.com/JasonLovesDoggo)'s PR: [perf: replace cidranger with bart for significant performance improvements #675](https://github.com/TecharoHQ/anubis/pull/675).
|
||||
|
||||
## What's up next?
|
||||
|
||||
v1.21.0 is already shaping up to be a massive improvement as Anubis adds [internationalization](https://en.wikipedia.org/wiki/Internationalization) support, allowing your users to see its messages in the language they're most comfortable with.
|
||||
|
||||
So far Anubis supports the following languages:
|
||||
|
||||
- English (Simplified and Traditional)
|
||||
- French
|
||||
- Portugese (Brazil)
|
||||
- Spanish
|
||||
|
||||
If you want to contribute translations, please [file an issue](https://github.com/TecharoHQ/anubis/issues/new) with your language of choice or submit a pull request to [the `lib/localization/locales` folder](https://github.com/TecharoHQ/anubis/tree/main/lib/localization/locales). We are about to introduce features to the translation stack, so you may want to hold off a hot minute, but we welcome any and all contributions to making Anubis useful to a global audience.
|
||||
|
||||
Other things we plan to do:
|
||||
|
||||
- Move configuration to the policy file
|
||||
- Support reloading the policy file at runtime without having to restart Anubis
|
||||
- Detecting if a client is "brand new"
|
||||
- A [Valkey](https://valkey.io/)-backed store for sharing information between instances of Anubis
|
||||
- Augmenting No-JS support in the paid product
|
||||
- TLS fingerprinting
|
||||
- Automated testing improvements in CI (FreeBSD CI support, better automated integration/functional testing, etc.)
|
||||
|
||||
## Conclusion
|
||||
|
||||
I hope that these features let you get the same Anubis power you've come to know and love and increases the things you can do with it! I've been really excited to ship [thresholds](/docs/admin/configuration/thresholds) and the cloud-based services for Anubis.
|
||||
|
||||
If you run into any problems, please [file an issue](https://github.com/TecharoHQ/anubis/issues/new). Otherwise, have a good day and get back to making your communities great.
|
||||
BIN
docs/blog/2025-06-27-release-1.20.0/sunburst.webp
Normal file
BIN
docs/blog/2025-06-27-release-1.20.0/sunburst.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -1 +0,0 @@
|
||||
|
||||
@@ -11,6 +11,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
<!-- This changes the project to: -->
|
||||
|
||||
- Add `COOKIE_SECURE` option to set the cookie [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies)
|
||||
- Sets cookie defaults to use [SameSite: None](https://web.dev/articles/samesite-cookies-explained)
|
||||
- Determine the `BIND_NETWORK`/`--bind-network` value from the bind address ([#677](https://github.com/TecharoHQ/anubis/issues/677)).
|
||||
- Implement localization system. Find locale files in lib/localization/locales/.
|
||||
- Implement a [development container](https://containers.dev/) manifest to make contributions easier.
|
||||
- Fix dynamic cookie domains functionality ([#731](https://github.com/TecharoHQ/anubis/pull/731))
|
||||
- Add option for custom cookie prefix ([#732](https://github.com/TecharoHQ/anubis/pull/732))
|
||||
- Remove the "Success" interstitial after a proof of work challenge is concluded.
|
||||
- Add option for forcing a specific language ([#742](https://github.com/TecharoHQ/anubis/pull/742))
|
||||
|
||||
### Potentially breaking changes
|
||||
|
||||
The following potentially breaking change applies to native installs with systemd only:
|
||||
|
||||
Each instance of systemd service template now has a unique `RuntimeDirectory`, as opposed to each instance of the service sharing a `RuntimeDirectory`. This change was made to avoid [the `RuntimeDirectory` getting nuked any time one of the Anubis instances restarts](https://github.com/TecharoHQ/anubis/issues/748).
|
||||
|
||||
If you configured Anubis' unix sockets to listen on `/run/anubis/foo.sock` for instance `anubis@foo`, you will need to configure Anubis to listen on `/run/anubis/foo/sock` and additionally configure your HTTP load balancer as appropriate.
|
||||
|
||||
If you need the legacy behaviour, install this [systemd unit dropin](https://www.flatcar.org/docs/latest/setup/systemd/drop-in-units/):
|
||||
|
||||
```systemd
|
||||
# /etc/systemd/system/anubis@.service.d/50-runtimedir.conf
|
||||
[Service]
|
||||
RuntimeDirectory=anubis
|
||||
```
|
||||
|
||||
## v1.20.0: Thancred Waters
|
||||
|
||||
The big ticket items are as follows:
|
||||
|
||||
215
docs/docs/admin/botstopper.mdx
Normal file
215
docs/docs/admin/botstopper.mdx
Normal file
@@ -0,0 +1,215 @@
|
||||
---
|
||||
title: "Commercial support and an unbranded version"
|
||||
---
|
||||
|
||||
If you want to use Anubis but organizational policies prevent you from using the branding that the open source project ships, we offer a commercial version of Anubis named BotStopper. BotStopper builds off of the open source core of Anubis and offers organizations more control over the branding, including but not limited to:
|
||||
|
||||
- Custom images for different states of the challenge process (in process, success, failure)
|
||||
- Custom CSS and fonts
|
||||
- Custom titles for the challenge and error pages
|
||||
- "Anubis" replaced with "BotStopper" across the UI
|
||||
- A private bug tracker for issues
|
||||
|
||||
In the near future this will expand to:
|
||||
|
||||
- A private challenge implementation that does advanced fingerprinting to check if the client is a genuine browser or not
|
||||
- Advanced fingerprinting via [Thoth-based advanced checks](./thoth.mdx)
|
||||
|
||||
In order to sign up for BotStopper, please do one of the following:
|
||||
|
||||
- Sign up [on GitHub Sponsors](https://github.com/sponsors/Xe) at the $50 per month tier or higher
|
||||
- Email [sales@techaro.lol](mailto:sales@techaro.lol) with your requirements for invoicing, please note that custom invoicing will cost more than using GitHub Sponsors for understandable overhead reasons
|
||||
|
||||
## Installation
|
||||
|
||||
Install BotStopper like you would Anubis, but replace the image reference. EG:
|
||||
|
||||
```diff
|
||||
-ghcr.io/techarohq/anubis:latest
|
||||
+ghcr.io/techarohq/botstopper/anubis:latest
|
||||
```
|
||||
|
||||
### Binary packages
|
||||
|
||||
Binary packages are available [in the GitHub Releases page](https://github.com/TecharoHQ/botstopper/releases), the main difference is that the package name is `techaro-botstopper`, the systemd service is `techaro-botstopper@your-instance.service`, the binary is `/usr/bin/botstopper`, and the configuration is in `/etc/techaro-botstopper`. All other instructions in the [native package install guide](./native-install.mdx) apply.
|
||||
|
||||
### Docker / Podman
|
||||
|
||||
In order to pull the BotStopper image, you need to [authenticate with GitHub's Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry).
|
||||
|
||||
```text
|
||||
docker login ghcr.io -u your-username --password-stdin
|
||||
```
|
||||
|
||||
Then you can use the image as normal.
|
||||
|
||||
### Kubernetes
|
||||
|
||||
If you are using Kubernetes, you will need to create an image pull secret:
|
||||
|
||||
```text
|
||||
kubectl create secret docker-registry \
|
||||
techarohq-botstopper \
|
||||
--docker-server ghcr.io \
|
||||
--docker-username your-username \
|
||||
--docker-password your-access-token \
|
||||
--docker-email your@email.address
|
||||
```
|
||||
|
||||
Then attach it to your Deployment:
|
||||
|
||||
```diff
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
+ imagePullSecrets:
|
||||
+ - name: techarohq-botstopper
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Docker compose
|
||||
|
||||
Follow [the upstream Docker compose directions](https://anubis.techaro.lol/docs/admin/environments/docker-compose) with the following additional options:
|
||||
|
||||
```diff
|
||||
anubis:
|
||||
image: ghcr.io/techarohq/botstopper/anubis:latest
|
||||
environment:
|
||||
BIND: ":8080"
|
||||
DIFFICULTY: "4"
|
||||
METRICS_BIND: ":9090"
|
||||
SERVE_ROBOTS_TXT: "true"
|
||||
TARGET: "http://nginx"
|
||||
OG_PASSTHROUGH: "true"
|
||||
OG_EXPIRY_TIME: "24h"
|
||||
|
||||
+ # botstopper config here
|
||||
+ CHALLENGE_TITLE: "Doing math for your connnection!"
|
||||
+ ERROR_TITLE: "Something went wrong!"
|
||||
+ OVERLAY_FOLDER: /assets
|
||||
+ volumes:
|
||||
+ - "./your_folder:/assets"
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
There is an example in [docker-compose.yaml](https://github.com/TecharoHQ/botstopper/blob/main/docker-compose.yaml). Start the example with `docker compose up`:
|
||||
|
||||
```text
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
And then open [https://botstopper.local.cetacean.club:8443](https://botstopper.local.cetacean.club:8443) in your browser.
|
||||
|
||||
> [!NOTE]
|
||||
> This uses locally signed sacrificial TLS certificates stored in `./demo/pki`. Your browser will rightly reject these. Here is what the example looks like:
|
||||
>
|
||||
> 
|
||||
|
||||
## Custom images and CSS
|
||||
|
||||
Anubis uses an internal filesystem that contains CSS, JavaScript, and images. The BotStopper variant of Anubis lets you specify an overlay folder with the environment variable `OVERLAY_FOLDER`. The contents of this folder will be overlaid on top of Anubis' internal filesystem, allowing you to easily customize the images and CSS.
|
||||
|
||||
Your directory tree should look like this, assuming your data is in `./your_folder`:
|
||||
|
||||
```text
|
||||
./your_folder
|
||||
└── static
|
||||
├── css
|
||||
│ └── custom.css
|
||||
└── img
|
||||
├── happy.webp
|
||||
├── pensive.webp
|
||||
└── reject.webp
|
||||
```
|
||||
|
||||
For an example directory tree using some off-the-shelf images the Tango icon set, see the [testdata](https://github.com/TecharoHQ/botstopper/tree/main/testdata/static/img) folder.
|
||||
|
||||
### Custom CSS
|
||||
|
||||
CSS customization is done mainly with CSS variables. View [the example custom CSS file](https://github.com/TecharoHQ/botstopper/blob/main/testdata/static/css/custom.css) for more information about what can be customized.
|
||||
|
||||
### Custom fonts
|
||||
|
||||
If you want to add custom fonts, copy the `woff2` files alongside your `custom.css` file and then include them with the [`@font-face` CSS at-rule](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face):
|
||||
|
||||
```css
|
||||
@font-face {
|
||||
font-family: "Oswald";
|
||||
font-style: normal;
|
||||
font-weight: 200 900;
|
||||
font-display: swap;
|
||||
src: url("./fonts/oswald.woff2") format("woff2");
|
||||
}
|
||||
```
|
||||
|
||||
Then adjust your CSS variables accordingly:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--body-sans-font: Oswald, sans-serif;
|
||||
--body-preformatted-font: monospace;
|
||||
--body-title-font: serif;
|
||||
}
|
||||
```
|
||||
|
||||
To convert `.ttf` fonts to [Web-optimized woff2 fonts](https://www.w3.org/TR/WOFF2/), use the `woff2_compress` command from the `woff2` or `woff2-tools` package:
|
||||
|
||||
```console
|
||||
$ woff2_compress oswald.ttf
|
||||
Processing oswald.ttf => oswald.woff2
|
||||
Compressed 159517 to 70469.
|
||||
```
|
||||
|
||||
Then you can import and use it as normal.
|
||||
|
||||
### Customizing images
|
||||
|
||||
Anubis uses three images to visually communicate the state of the program. These are:
|
||||
|
||||
| Image name | Intended message | Example |
|
||||
| :------------- | :----------------------------------------------- | :-------------------------------- |
|
||||
| `happy.webp` | You have passed validation, all is good |  |
|
||||
| `pensive.webp` | Checking is running, hold steady until it's done |  |
|
||||
| `reject.webp` | Something went wrong, this is a terminal state |  |
|
||||
|
||||
To make your own images at the optimal quality, use the following ffmpeg command:
|
||||
|
||||
```text
|
||||
ffmpeg -i /path/to/image -vf scale=-1:384 happy.webp
|
||||
```
|
||||
|
||||
`ffprobe` should report something like this on the generated images:
|
||||
|
||||
```text
|
||||
Input #0, webp_pipe, from 'happy.webp':
|
||||
Duration: N/A, bitrate: N/A
|
||||
Stream #0:0: Video: webp, none, 25 fps, 25 tbr, 25 tbn
|
||||
```
|
||||
|
||||
In testing 384 by 384 pixels gives the best balance between filesize, quality, and clarity.
|
||||
|
||||
```text
|
||||
$ du -hs *
|
||||
4.0K happy.webp
|
||||
12K pensive.webp
|
||||
8.0K reject.webp
|
||||
```
|
||||
|
||||
## Customizing messages
|
||||
|
||||
You can customize messages using the following environment variables:
|
||||
|
||||
| Message | Environment variable | Default |
|
||||
| :------------------- | :------------------- | :----------------------------------------- |
|
||||
| Challenge page title | `CHALLENGE_TITLE` | `Ensuring the security of your connection` |
|
||||
| Error page title | `ERROR_TITLE` | `Error` |
|
||||
|
||||
For example:
|
||||
|
||||
```sh
|
||||
# /etc/techaro-botstopper/gitea.env
|
||||
CHALLENGE_TITLE="Wait a moment please!"
|
||||
ERROR_TITLE="Client error"
|
||||
```
|
||||
@@ -67,6 +67,7 @@ Anubis uses these environment variables for configuration:
|
||||
| `COOKIE_DYNAMIC_DOMAIN` | false | If set to true, automatically set cookie domain fields based on the hostname of the request. EG: if you are making a request to `anubis.techaro.lol`, the Anubis cookie will be valid for any subdomain of `techaro.lol`. |
|
||||
| `COOKIE_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. |
|
||||
| `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. |
|
||||
| `COOKIE_SECURE` | `true` | If set to `true`, enables the [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies), meaning that the cookies will only be transmitted over HTTPS. If Anubis is used in an unsecure context (plain HTTP), this will be need to be set to false |
|
||||
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
||||
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. See below for details. |
|
||||
| `ED25519_PRIVATE_KEY_HEX_FILE` | unset | Path to a file containing the hex-encoded ed25519 private key. Only one of this or its sister option may be set. |
|
||||
|
||||
@@ -107,7 +107,6 @@ This ensures that the token has enough metadata to prove that the token is valid
|
||||
Challenges are formed by taking some user request metadata and using that to generate a SHA-256 checksum. The following request headers are used:
|
||||
|
||||
- `Accept-Encoding`: The content encodings that the requestor supports, such as gzip.
|
||||
- `Accept-Language`: The language that the requestor would prefer the server respond in, such as English.
|
||||
- `X-Real-Ip`: The IP address of the requestor, as set by a reverse proxy server.
|
||||
- `User-Agent`: The user agent string of the requestor.
|
||||
- The current time in UTC rounded to the nearest week.
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
title: Local development
|
||||
---
|
||||
|
||||
If you use an editor with [Development containers](https://containers.dev) support, load this repo's [devcontainer configuration](https://github.com/TecharoHQ/anubis/tree/main/.devcontainer). Skip to [Running Anubis locally](#running-anubis-locally) if you are using the devcontainer.
|
||||
|
||||
This enables you to contribute from [GitHub Codespaces](https://github.com/features/codespaces) or other web-based editors.
|
||||
|
||||
:::note
|
||||
|
||||
TL;DR: `npm ci && npm run dev`
|
||||
|
||||
@@ -14,6 +14,7 @@ title: Anubis
|
||||

|
||||

|
||||

|
||||
[](https://github.com/sponsors/Xe)
|
||||
|
||||
## Sponsors
|
||||
|
||||
|
||||
BIN
docs/static/img/botstopper/example-screenshot.webp
vendored
Normal file
BIN
docs/static/img/botstopper/example-screenshot.webp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/static/img/botstopper/happy.webp
vendored
Normal file
BIN
docs/static/img/botstopper/happy.webp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
docs/static/img/botstopper/pensive.webp
vendored
Normal file
BIN
docs/static/img/botstopper/pensive.webp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
BIN
docs/static/img/botstopper/reject.webp
vendored
Normal file
BIN
docs/static/img/botstopper/reject.webp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
12
go.mod
12
go.mod
@@ -4,7 +4,7 @@ go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/TecharoHQ/thoth-proto v0.4.0
|
||||
github.com/a-h/templ v0.3.898
|
||||
github.com/a-h/templ v0.3.906
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
|
||||
github.com/gaissmai/bart v0.20.4
|
||||
@@ -13,14 +13,16 @@ require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.0
|
||||
github.com/playwright-community/playwright-go v0.5200.0
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/text v0.26.0
|
||||
google.golang.org/grpc v1.73.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/apimachinery v0.33.2
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
sigs.k8s.io/yaml v1.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -29,7 +31,7 @@ require (
|
||||
cel.dev/expr v0.23.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
@@ -108,6 +110,8 @@ require (
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.3 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
|
||||
@@ -117,7 +121,6 @@ require (
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
golang.org/x/vuln v1.1.4 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||
@@ -133,6 +136,7 @@ require (
|
||||
tool (
|
||||
github.com/TecharoHQ/yeet/cmd/yeet
|
||||
github.com/a-h/templ/cmd/templ
|
||||
github.com/nicksnyder/go-i18n/v2/goi18n
|
||||
github.com/suzuki-shunsuke/pinact/cmd/pinact
|
||||
golang.org/x/tools/cmd/deadcode
|
||||
golang.org/x/tools/cmd/goimports
|
||||
|
||||
18
go.sum
18
go.sum
@@ -8,8 +8,8 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||
@@ -36,8 +36,8 @@ github.com/TecharoHQ/yeet v0.6.0 h1:RCBAjr7wIlllsgy0tpvWpLX7jsZgu2tiuBY3RrprcR0=
|
||||
github.com/TecharoHQ/yeet v0.6.0/go.mod h1:bj2V4Fg8qKQXoiuPZa3HuawrE8g+LsOQv/9q2WyGSsA=
|
||||
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
|
||||
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
|
||||
github.com/a-h/templ v0.3.898 h1:g9oxL/dmM6tvwRe2egJS8hBDQTncokbMoOFk1oJMX7s=
|
||||
github.com/a-h/templ v0.3.898/go.mod h1:oLBbZVQ6//Q6zpvSMPTuBK0F3qOtBdFBcGRspcT+VNQ=
|
||||
github.com/a-h/templ v0.3.906 h1:ZUThc8Q9n04UATaCwaG60pB1AqbulLmYEAMnWV63svg=
|
||||
github.com/a-h/templ v0.3.906/go.mod h1:FFAu4dI//ESmEN7PQkJ7E7QfnSEMdcnu7QrAY8Dn334=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
@@ -237,6 +237,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
|
||||
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
|
||||
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
@@ -327,6 +329,10 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
|
||||
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
@@ -448,5 +454,5 @@ pault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4=
|
||||
pault.ag/go/topsort v0.1.1/go.mod h1:r1kc/L0/FZ3HhjezBIPaNVhkqv8L0UJ9bxRuHRVZ0q4=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
|
||||
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/TecharoHQ/anubis/internal/dnsbl"
|
||||
"github.com/TecharoHQ/anubis/internal/ogtags"
|
||||
"github.com/TecharoHQ/anubis/lib/challenge"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/TecharoHQ/anubis/lib/policy"
|
||||
"github.com/TecharoHQ/anubis/lib/policy/checker"
|
||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
||||
@@ -68,7 +69,6 @@ type Server struct {
|
||||
policy *policy.ParsedConfig
|
||||
DNSBLCache *decaymap.Impl[string, dnsbl.DroneBLResponse]
|
||||
OGTags *ogtags.OGTagCache
|
||||
cookieName string
|
||||
ed25519Priv ed25519.PrivateKey
|
||||
hs512Secret []byte
|
||||
opts Options
|
||||
@@ -126,7 +126,8 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
||||
cr, rule, err := s.check(r)
|
||||
if err != nil {
|
||||
lg.Error("check failed", "err", err)
|
||||
s.respondWithError(w, r, "Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"maybeReverseProxy\"")
|
||||
localizer := localization.GetLocalizer(r)
|
||||
s.respondWithError(w, r, fmt.Sprintf("%s \"maybeReverseProxy\"", localizer.T("internal_server_error")))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -145,24 +146,24 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
||||
return
|
||||
}
|
||||
|
||||
ckie, err := r.Cookie(s.cookieName)
|
||||
ckie, err := r.Cookie(anubis.CookieName)
|
||||
if err != nil {
|
||||
lg.Debug("cookie not found", "path", r.URL.Path)
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ckie.Valid(); err != nil {
|
||||
lg.Debug("cookie is invalid", "err", err)
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||
return
|
||||
}
|
||||
|
||||
if time.Now().After(ckie.Expires) && !ckie.Expires.IsZero() {
|
||||
lg.Debug("cookie expired", "path", r.URL.Path)
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||
return
|
||||
}
|
||||
@@ -171,7 +172,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
lg.Debug("invalid token", "path", r.URL.Path, "err", err)
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||
return
|
||||
}
|
||||
@@ -179,7 +180,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
lg.Debug("invalid token claims type", "path", r.URL.Path)
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||
return
|
||||
}
|
||||
@@ -187,14 +188,14 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
||||
policyRule, ok := claims["policyRule"].(string)
|
||||
if !ok {
|
||||
lg.Debug("policyRule claim is not a string")
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||
return
|
||||
}
|
||||
|
||||
if policyRule != rule.Hash() {
|
||||
lg.Debug("user originally passed with a different rule, issuing new challenge", "old", policyRule, "new", rule.Name)
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||
return
|
||||
}
|
||||
@@ -210,23 +211,25 @@ func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.Ch
|
||||
cookiePath = strings.TrimSuffix(anubis.BasePrefix, "/") + "/"
|
||||
}
|
||||
|
||||
localizer := localization.GetLocalizer(r)
|
||||
|
||||
switch cr.Rule {
|
||||
case config.RuleAllow:
|
||||
lg.Debug("allowing traffic to origin (explicit)")
|
||||
s.ServeHTTPNext(w, r)
|
||||
return true
|
||||
case config.RuleDeny:
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
lg.Info("explicit deny")
|
||||
if rule == nil {
|
||||
lg.Error("rule is nil, cannot calculate checksum")
|
||||
s.respondWithError(w, r, "Internal Server Error: Please contact the administrator and ask them to look for the logs around \"maybeReverseProxy.RuleDeny\"")
|
||||
s.respondWithError(w, r, fmt.Sprintf("%s \"maybeReverseProxy.RuleDeny\"", localizer.T("internal_server_error")))
|
||||
return true
|
||||
}
|
||||
hash := rule.Hash()
|
||||
|
||||
lg.Debug("rule hash", "hash", hash)
|
||||
s.respondWithStatus(w, r, fmt.Sprintf("Access Denied: error code %s", hash), s.policy.StatusCodes.Deny)
|
||||
s.respondWithStatus(w, r, fmt.Sprintf("%s %s", localizer.T("access_denied"), hash), s.policy.StatusCodes.Deny)
|
||||
return true
|
||||
case config.RuleChallenge:
|
||||
lg.Debug("challenge requested")
|
||||
@@ -235,9 +238,9 @@ func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.Ch
|
||||
s.RenderBench(w, r)
|
||||
return true
|
||||
default:
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
slog.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule)
|
||||
s.respondWithError(w, r, "Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"maybeReverseProxy.Rules\"")
|
||||
s.respondWithError(w, r, fmt.Sprintf("%s \"maybeReverseProxy.Rules\"", localizer.T("internal_server_error")))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -258,7 +261,12 @@ func (s *Server) handleDNSBL(w http.ResponseWriter, r *http.Request, ip string,
|
||||
|
||||
if resp != dnsbl.AllGood {
|
||||
lg.Info("DNSBL hit", "status", resp.String())
|
||||
s.respondWithStatus(w, r, fmt.Sprintf("DroneBL reported an entry: %s, see https://dronebl.org/lookup?ip=%s", resp.String(), ip), s.policy.StatusCodes.Deny)
|
||||
localizer := localization.GetLocalizer(r)
|
||||
s.respondWithStatus(w, r, fmt.Sprintf("%s: %s, %s https://dronebl.org/lookup?ip=%s",
|
||||
localizer.T("dronebl_entry"),
|
||||
resp.String(),
|
||||
localizer.T("see_dronebl_lookup"),
|
||||
ip), s.policy.StatusCodes.Deny)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -267,6 +275,7 @@ func (s *Server) handleDNSBL(w http.ResponseWriter, r *http.Request, ip string,
|
||||
|
||||
func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
lg := internal.GetRequestLogger(r)
|
||||
localizer := localization.GetLocalizer(r)
|
||||
|
||||
redir := r.FormValue("redir")
|
||||
if redir == "" {
|
||||
@@ -276,7 +285,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
encoder.Encode(struct {
|
||||
Error string `json:"error"`
|
||||
}{
|
||||
Error: "Invalid invocation of MakeChallenge",
|
||||
Error: localizer.T("invalid_invocation"),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -291,7 +300,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
err := encoder.Encode(struct {
|
||||
Error string `json:"error"`
|
||||
}{
|
||||
Error: "Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"makeChallenge\"",
|
||||
Error: fmt.Sprintf("%s \"makeChallenge\"", localizer.T("internal_server_error")),
|
||||
})
|
||||
if err != nil {
|
||||
lg.Error("failed to encode error response", "err", err)
|
||||
@@ -302,7 +311,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
lg = lg.With("check_result", cr)
|
||||
chal := s.challengeFor(r, rule.Challenge.Difficulty)
|
||||
|
||||
s.SetCookie(w, anubis.TestCookieName, chal, "/", r.Host)
|
||||
s.SetCookie(w, CookieOpts{Host: r.Host, Name: anubis.TestCookieName, Value: chal})
|
||||
|
||||
err = encoder.Encode(struct {
|
||||
Rules *config.ChallengeRules `json:"rules"`
|
||||
@@ -322,6 +331,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
lg := internal.GetRequestLogger(r)
|
||||
localizer := localization.GetLocalizer(r)
|
||||
|
||||
// Adjust cookie path if base prefix is not empty
|
||||
cookiePath := "/"
|
||||
@@ -330,20 +340,20 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if _, err := r.Cookie(anubis.TestCookieName); errors.Is(err, http.ErrNoCookie) {
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, anubis.TestCookieName, "/", r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
||||
lg.Warn("user has cookies disabled, this is not an anubis bug")
|
||||
s.respondWithError(w, r, "Your browser is configured to disable cookies. Anubis requires cookies for the legitimate interest of making sure you are a valid client. Please enable cookies for this domain")
|
||||
s.respondWithError(w, r, localizer.T("cookies_disabled"))
|
||||
return
|
||||
}
|
||||
|
||||
s.ClearCookie(w, anubis.TestCookieName, "/", r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
||||
|
||||
redir := r.FormValue("redir")
|
||||
redirURL, err := url.ParseRequestURI(redir)
|
||||
if err != nil {
|
||||
lg.Error("invalid redirect", "err", err)
|
||||
s.respondWithError(w, r, "Invalid redirect")
|
||||
s.respondWithError(w, r, localizer.T("invalid_redirect"))
|
||||
return
|
||||
}
|
||||
// used by the path checker rule
|
||||
@@ -351,18 +361,18 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
urlParsed, err := r.URL.Parse(redir)
|
||||
if err != nil {
|
||||
s.respondWithError(w, r, "Redirect URL not parseable")
|
||||
s.respondWithError(w, r, localizer.T("redirect_not_parseable"))
|
||||
return
|
||||
}
|
||||
if (len(urlParsed.Host) > 0 && len(s.opts.RedirectDomains) != 0 && !slices.Contains(s.opts.RedirectDomains, urlParsed.Host)) || urlParsed.Host != r.URL.Host {
|
||||
s.respondWithError(w, r, "Redirect domain not allowed")
|
||||
s.respondWithError(w, r, localizer.T("redirect_domain_not_allowed"))
|
||||
return
|
||||
}
|
||||
|
||||
cr, rule, err := s.check(r)
|
||||
if err != nil {
|
||||
lg.Error("check failed", "err", err)
|
||||
s.respondWithError(w, r, "Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"passChallenge\"")
|
||||
s.respondWithError(w, r, fmt.Sprintf("%s \"passChallenge\"", localizer.T("internal_server_error")))
|
||||
return
|
||||
}
|
||||
lg = lg.With("check_result", cr)
|
||||
@@ -370,7 +380,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
impl, ok := challenge.Get(rule.Challenge.Algorithm)
|
||||
if !ok {
|
||||
lg.Error("check failed", "err", err)
|
||||
s.respondWithError(w, r, fmt.Sprintf("Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to file a bug as Anubis is trying to use challenge method %s but it does not exist in the challenge registry", rule.Challenge.Algorithm))
|
||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -379,7 +389,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
if err := impl.Validate(r, lg, rule, challengeStr); err != nil {
|
||||
failedValidations.WithLabelValues(rule.Challenge.Algorithm).Inc()
|
||||
var cerr *challenge.Error
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
lg.Debug("challenge validate call failed", "err", err)
|
||||
|
||||
switch {
|
||||
@@ -402,12 +412,12 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
if err != nil {
|
||||
lg.Error("failed to sign JWT", "err", err)
|
||||
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||
s.respondWithError(w, r, "failed to sign JWT")
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
s.respondWithError(w, r, localizer.T("failed_to_sign_jwt"))
|
||||
return
|
||||
}
|
||||
|
||||
s.SetCookie(w, s.cookieName, tokenString, cookiePath, r.Host)
|
||||
s.SetCookie(w, CookieOpts{Path: cookiePath, Host: r.Host, Value: tokenString})
|
||||
|
||||
challengesValidated.WithLabelValues(rule.Challenge.Algorithm).Inc()
|
||||
lg.Debug("challenge passed, redirecting to app")
|
||||
|
||||
@@ -189,8 +189,6 @@ func TestCVE2025_24369(t *testing.T) {
|
||||
srv := spawnAnubis(t, Options{
|
||||
Next: http.NewServeMux(),
|
||||
Policy: pol,
|
||||
|
||||
CookieName: t.Name(),
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
|
||||
@@ -235,13 +233,13 @@ func TestCookieCustomExpiration(t *testing.T) {
|
||||
var ckie *http.Cookie
|
||||
for _, cookie := range resp.Cookies() {
|
||||
t.Logf("%#v", cookie)
|
||||
if cookie.Name == srv.cookieName {
|
||||
if cookie.Name == anubis.CookieName {
|
||||
ckie = cookie
|
||||
break
|
||||
}
|
||||
}
|
||||
if ckie == nil {
|
||||
t.Errorf("Cookie %q not found", srv.cookieName)
|
||||
t.Errorf("Cookie %q not found", anubis.CookieName)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -264,7 +262,7 @@ func TestCookieSettings(t *testing.T) {
|
||||
|
||||
CookieDomain: "127.0.0.1",
|
||||
CookiePartitioned: true,
|
||||
CookieName: t.Name(),
|
||||
CookieSecure: true,
|
||||
CookieExpiration: anubis.CookieDefaultExpirationTime,
|
||||
})
|
||||
|
||||
@@ -286,13 +284,13 @@ func TestCookieSettings(t *testing.T) {
|
||||
var ckie *http.Cookie
|
||||
for _, cookie := range resp.Cookies() {
|
||||
t.Logf("%#v", cookie)
|
||||
if cookie.Name == srv.cookieName {
|
||||
if cookie.Name == anubis.CookieName {
|
||||
ckie = cookie
|
||||
break
|
||||
}
|
||||
}
|
||||
if ckie == nil {
|
||||
t.Errorf("Cookie %q not found", srv.cookieName)
|
||||
t.Errorf("Cookie %q not found", anubis.CookieName)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -312,6 +310,10 @@ func TestCookieSettings(t *testing.T) {
|
||||
if ckie.Partitioned != srv.opts.CookiePartitioned {
|
||||
t.Errorf("wanted partitioned flag %v, got: %v", srv.opts.CookiePartitioned, ckie.Partitioned)
|
||||
}
|
||||
|
||||
if ckie.Secure != srv.opts.CookieSecure {
|
||||
t.Errorf("wanted secure flag %v, got: %v", srv.opts.CookieSecure, ckie.Secure)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) {
|
||||
@@ -619,7 +621,6 @@ func TestRuleChange(t *testing.T) {
|
||||
Policy: pol,
|
||||
|
||||
CookieDomain: "127.0.0.1",
|
||||
CookieName: t.Name(),
|
||||
CookieExpiration: ckieExpiration,
|
||||
})
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/lib/challenge"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/TecharoHQ/anubis/lib/policy"
|
||||
"github.com/TecharoHQ/anubis/web"
|
||||
"github.com/a-h/templ"
|
||||
@@ -34,7 +35,9 @@ func (i *Impl) Issue(r *http.Request, lg *slog.Logger, in *challenge.IssueInput)
|
||||
q.Set("challenge", in.Challenge)
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
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)
|
||||
loc := localization.GetLocalizer(r)
|
||||
component, err := web.BaseWithChallengeAndOGTags(loc.T("making_sure_not_bot"), page(in.Challenge, u.String(), in.Rule.Challenge.Difficulty, loc), in.Impressum, in.Challenge, in.Rule.Challenge, in.OGTags, loc)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't render page: %w", err)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,15 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
)
|
||||
|
||||
templ page(challenge, redir string, difficulty int) {
|
||||
templ page(challenge, redir string, difficulty int, loc *localization.SimpleLocalizer) {
|
||||
<div class="centered-div">
|
||||
<img id="image" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
|
||||
<img style="display:none;" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version }/>
|
||||
<p id="status">Loading...</p>
|
||||
<p>Please wait a moment while we ensure the security of your connection.</p>
|
||||
<p id="status">{ loc.T("loading") }</p>
|
||||
<p>{ loc.T("connection_security") }</p>
|
||||
<meta http-equiv="refresh" content={ fmt.Sprintf("%d; url=%s", difficulty, redir) }/>
|
||||
</div>
|
||||
}
|
||||
|
||||
43
lib/challenge/metarefresh/metarefresh_templ.go
generated
43
lib/challenge/metarefresh/metarefresh_templ.go
generated
@@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.906
|
||||
package metarefresh
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
@@ -12,9 +12,10 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
)
|
||||
|
||||
func page(challenge, redir string, difficulty int) templ.Component {
|
||||
func page(challenge, redir string, difficulty int, loc *localization.SimpleLocalizer) 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 {
|
||||
@@ -42,7 +43,7 @@ func page(challenge, redir string, difficulty int) templ.Component {
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.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: `metarefresh.templ`, Line: 11, Col: 165}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 12, Col: 165}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -55,26 +56,52 @@ func page(challenge, redir string, difficulty int) templ.Component {
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.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: `metarefresh.templ`, Line: 12, Col: 174}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 13, Col: 174}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><p id=\"status\">Loading...</p><p>Please wait a moment while we ensure the security of your connection.</p><meta http-equiv=\"refresh\" content=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><p id=\"status\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d; url=%s", difficulty, redir))
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(loc.T("loading"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 15, Col: 83}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 14, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(loc.T("connection_security"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 15, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p><meta http-equiv=\"refresh\" content=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d; url=%s", difficulty, redir))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 16, Col: 83}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/TecharoHQ/anubis/internal"
|
||||
chall "github.com/TecharoHQ/anubis/lib/challenge"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/TecharoHQ/anubis/lib/policy"
|
||||
"github.com/TecharoHQ/anubis/web"
|
||||
"github.com/a-h/templ"
|
||||
@@ -29,7 +30,8 @@ func (i *Impl) Setup(mux *http.ServeMux) {
|
||||
}
|
||||
|
||||
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)
|
||||
loc := localization.GetLocalizer(r)
|
||||
component, err := web.BaseWithChallengeAndOGTags(loc.T("making_sure_not_bot"), web.Index(loc), in.Impressum, in.Challenge, in.Rule.Challenge, in.OGTags, loc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't render page: %w", err)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/TecharoHQ/anubis/internal/dnsbl"
|
||||
"github.com/TecharoHQ/anubis/internal/ogtags"
|
||||
"github.com/TecharoHQ/anubis/lib/challenge"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/TecharoHQ/anubis/lib/policy"
|
||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
||||
"github.com/TecharoHQ/anubis/web"
|
||||
@@ -34,7 +35,6 @@ type Options struct {
|
||||
CookieDynamicDomain bool
|
||||
CookieDomain string
|
||||
CookieExpiration time.Duration
|
||||
CookieName string
|
||||
CookiePartitioned bool
|
||||
BasePrefix string
|
||||
WebmasterEmail string
|
||||
@@ -44,6 +44,7 @@ type Options struct {
|
||||
StripBasePrefix bool
|
||||
OpenGraph config.OpenGraph
|
||||
ServeRobotsTXT bool
|
||||
CookieSecure bool
|
||||
}
|
||||
|
||||
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
|
||||
@@ -101,12 +102,6 @@ func New(opts Options) (*Server, error) {
|
||||
|
||||
anubis.BasePrefix = opts.BasePrefix
|
||||
|
||||
cookieName := anubis.CookieName
|
||||
|
||||
if opts.CookieDomain != "" {
|
||||
cookieName = anubis.WithDomainCookieName + opts.CookieDomain
|
||||
}
|
||||
|
||||
result := &Server{
|
||||
next: opts.Next,
|
||||
ed25519Priv: opts.ED25519PrivateKey,
|
||||
@@ -115,7 +110,6 @@ func New(opts Options) (*Server, error) {
|
||||
opts: opts,
|
||||
DNSBLCache: decaymap.New[string, dnsbl.DroneBLResponse](),
|
||||
OGTags: ogtags.NewOGTagCache(opts.Target, opts.Policy.OpenGraph),
|
||||
cookieName: cookieName,
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@@ -155,7 +149,7 @@ func New(opts Options) (*Server, error) {
|
||||
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),
|
||||
web.Base(opts.Policy.Impressum.Page.Title, opts.Policy.Impressum.Page, opts.Policy.Impressum, localization.GetLocalizer(r)),
|
||||
).ServeHTTP(w, r)
|
||||
}), "GET")
|
||||
}
|
||||
|
||||
90
lib/http.go
90
lib/http.go
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/internal"
|
||||
"github.com/TecharoHQ/anubis/lib/challenge"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/TecharoHQ/anubis/lib/policy"
|
||||
"github.com/TecharoHQ/anubis/web"
|
||||
"github.com/a-h/templ"
|
||||
@@ -21,32 +22,59 @@ import (
|
||||
|
||||
var domainMatchRegexp = regexp.MustCompile(`^((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`)
|
||||
|
||||
func (s *Server) SetCookie(w http.ResponseWriter, name, value, path, host string) {
|
||||
type CookieOpts struct {
|
||||
Value string
|
||||
Host string
|
||||
Path string
|
||||
Name string
|
||||
Expiry time.Duration
|
||||
}
|
||||
|
||||
func (s *Server) SetCookie(w http.ResponseWriter, cookieOpts CookieOpts) {
|
||||
var domain = s.opts.CookieDomain
|
||||
if s.opts.CookieDynamicDomain && domainMatchRegexp.MatchString(host) {
|
||||
if etld, err := publicsuffix.EffectiveTLDPlusOne(host); err == nil {
|
||||
var name = anubis.CookieName
|
||||
var path = "/"
|
||||
if cookieOpts.Name != "" {
|
||||
name = cookieOpts.Name
|
||||
}
|
||||
if cookieOpts.Path != "" {
|
||||
path = cookieOpts.Path
|
||||
}
|
||||
if s.opts.CookieDynamicDomain && domainMatchRegexp.MatchString(cookieOpts.Host) {
|
||||
if etld, err := publicsuffix.EffectiveTLDPlusOne(cookieOpts.Host); err == nil {
|
||||
domain = etld
|
||||
name = anubis.WithDomainCookieName + etld
|
||||
}
|
||||
}
|
||||
|
||||
if cookieOpts.Expiry == 0 {
|
||||
cookieOpts.Expiry = s.opts.CookieExpiration
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Expires: time.Now().Add(s.opts.CookieExpiration),
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Value: cookieOpts.Value,
|
||||
Expires: time.Now().Add(cookieOpts.Expiry),
|
||||
SameSite: http.SameSiteNoneMode,
|
||||
Domain: domain,
|
||||
Secure: s.opts.CookieSecure,
|
||||
Partitioned: s.opts.CookiePartitioned,
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) ClearCookie(w http.ResponseWriter, name, path, host string) {
|
||||
func (s *Server) ClearCookie(w http.ResponseWriter, cookieOpts CookieOpts) {
|
||||
var domain = s.opts.CookieDomain
|
||||
if s.opts.CookieDynamicDomain && domainMatchRegexp.MatchString(host) {
|
||||
if etld, err := publicsuffix.EffectiveTLDPlusOne(host); err == nil {
|
||||
var name = anubis.CookieName
|
||||
var path = "/"
|
||||
if cookieOpts.Name != "" {
|
||||
name = cookieOpts.Name
|
||||
}
|
||||
if cookieOpts.Path != "" {
|
||||
path = cookieOpts.Path
|
||||
}
|
||||
if s.opts.CookieDynamicDomain && domainMatchRegexp.MatchString(cookieOpts.Host) {
|
||||
if etld, err := publicsuffix.EffectiveTLDPlusOne(cookieOpts.Host); err == nil {
|
||||
domain = etld
|
||||
name = anubis.WithDomainCookieName + etld
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +83,10 @@ func (s *Server) ClearCookie(w http.ResponseWriter, name, path, host string) {
|
||||
Value: "",
|
||||
MaxAge: -1,
|
||||
Expires: time.Now().Add(-1 * time.Minute),
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
SameSite: http.SameSiteNoneMode,
|
||||
Partitioned: s.opts.CookiePartitioned,
|
||||
Domain: domain,
|
||||
Secure: s.opts.CookieSecure,
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
@@ -83,9 +112,11 @@ func randomChance(n int) bool {
|
||||
}
|
||||
|
||||
func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *policy.Bot, returnHTTPStatusOnly bool) {
|
||||
localizer := localization.GetLocalizer(r)
|
||||
|
||||
if returnHTTPStatusOnly {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Authorization required"))
|
||||
w.Write([]byte(localizer.T("authorization_required")))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -93,7 +124,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic
|
||||
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && randomChance(64) {
|
||||
lg.Error("client was given a challenge but does not in fact support gzip compression")
|
||||
s.respondWithError(w, r, "Client Error: Please ensure your browser is up to date and try again later.")
|
||||
s.respondWithError(w, r, localizer.T("client_error_browser"))
|
||||
}
|
||||
|
||||
challengesIssued.WithLabelValues("embedded").Add(1)
|
||||
@@ -108,17 +139,18 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic
|
||||
}
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: anubis.TestCookieName,
|
||||
Value: challengeStr,
|
||||
Expires: time.Now().Add(30 * time.Minute),
|
||||
Path: "/",
|
||||
s.SetCookie(w, CookieOpts{
|
||||
Value: challengeStr,
|
||||
Host: r.Host,
|
||||
Path: "/",
|
||||
Name: anubis.TestCookieName,
|
||||
Expiry: 30 * time.Minute,
|
||||
})
|
||||
|
||||
impl, ok := challenge.Get(rule.Challenge.Algorithm)
|
||||
if !ok {
|
||||
lg.Error("check failed", "err", "can't get algorithm", "algorithm", rule.Challenge.Algorithm)
|
||||
s.respondWithError(w, r, fmt.Sprintf("Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to file a bug as Anubis is trying to use challenge method %s but it does not exist in the challenge registry", rule.Challenge.Algorithm))
|
||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -132,7 +164,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic
|
||||
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\"")
|
||||
s.respondWithError(w, r, fmt.Sprintf("%s \"RenderIndex\"", localizer.T("internal_server_error")))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -144,8 +176,10 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic
|
||||
}
|
||||
|
||||
func (s *Server) RenderBench(w http.ResponseWriter, r *http.Request) {
|
||||
localizer := localization.GetLocalizer(r)
|
||||
|
||||
templ.Handler(
|
||||
web.Base("Benchmarking Anubis!", web.Bench(), s.policy.Impressum),
|
||||
web.Base(localizer.T("benchmarking_anubis"), web.Bench(localizer), s.policy.Impressum, localizer),
|
||||
).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
@@ -154,7 +188,9 @@ 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), s.policy.Impressum), templ.WithStatus(status)).ServeHTTP(w, r)
|
||||
localizer := localization.GetLocalizer(r)
|
||||
|
||||
templ.Handler(web.Base(localizer.T("oh_noes"), web.ErrorPage(msg, s.opts.WebmasterEmail, localizer), s.policy.Impressum, localizer), templ.WithStatus(status)).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -189,15 +225,17 @@ func (s *Server) stripBasePrefixFromRequest(r *http.Request) *http.Request {
|
||||
|
||||
func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) {
|
||||
if s.next == nil {
|
||||
localizer := localization.GetLocalizer(r)
|
||||
|
||||
redir := r.FormValue("redir")
|
||||
urlParsed, err := r.URL.Parse(redir)
|
||||
if err != nil {
|
||||
s.respondWithStatus(w, r, "Redirect URL not parseable", http.StatusBadRequest)
|
||||
s.respondWithStatus(w, r, localizer.T("redirect_not_parseable"), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if (len(urlParsed.Host) > 0 && len(s.opts.RedirectDomains) != 0 && !slices.Contains(s.opts.RedirectDomains, urlParsed.Host)) || urlParsed.Host != r.URL.Host {
|
||||
s.respondWithStatus(w, r, "Redirect domain not allowed", http.StatusBadRequest)
|
||||
s.respondWithStatus(w, r, localizer.T("redirect_domain_not_allowed"), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -207,7 +245,7 @@ func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
templ.Handler(
|
||||
web.Base("You are not a bot!", web.StaticHappy(), s.policy.Impressum),
|
||||
web.Base(localizer.T("you_are_not_a_bot"), web.StaticHappy(localizer), s.policy.Impressum, localizer),
|
||||
).ServeHTTP(w, r)
|
||||
} else {
|
||||
requestsProxied.WithLabelValues(r.Host).Inc()
|
||||
|
||||
@@ -24,20 +24,20 @@ func TestSetCookie(t *testing.T) {
|
||||
name: "domain techaro.lol",
|
||||
options: Options{CookieDomain: "techaro.lol"},
|
||||
host: "",
|
||||
cookieName: anubis.WithDomainCookieName + "techaro.lol",
|
||||
cookieName: anubis.CookieName,
|
||||
},
|
||||
{
|
||||
name: "dynamic cookie domain",
|
||||
options: Options{CookieDynamicDomain: true},
|
||||
host: "techaro.lol",
|
||||
cookieName: anubis.WithDomainCookieName + "techaro.lol",
|
||||
cookieName: anubis.CookieName,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
srv := spawnAnubis(t, tt.options)
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
srv.SetCookie(rw, srv.cookieName, "test", "/", tt.host)
|
||||
srv.SetCookie(rw, CookieOpts{Value: "test", Host: tt.host})
|
||||
|
||||
resp := rw.Result()
|
||||
cookies := resp.Cookies()
|
||||
@@ -55,7 +55,7 @@ func TestClearCookie(t *testing.T) {
|
||||
srv := spawnAnubis(t, Options{})
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
srv.ClearCookie(rw, srv.cookieName, "/", "localhost")
|
||||
srv.ClearCookie(rw, CookieOpts{Host: "localhost"})
|
||||
|
||||
resp := rw.Result()
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestClearCookieWithDomain(t *testing.T) {
|
||||
srv := spawnAnubis(t, Options{CookieDomain: "techaro.lol"})
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
srv.ClearCookie(rw, srv.cookieName, "/", "locahost")
|
||||
srv.ClearCookie(rw, CookieOpts{Host: "localhost"})
|
||||
|
||||
resp := rw.Result()
|
||||
|
||||
@@ -92,8 +92,8 @@ func TestClearCookieWithDomain(t *testing.T) {
|
||||
|
||||
ckie := cookies[0]
|
||||
|
||||
if ckie.Name != srv.cookieName {
|
||||
t.Errorf("wanted cookie named %q, got cookie named %q", srv.cookieName, ckie.Name)
|
||||
if ckie.Name != anubis.CookieName {
|
||||
t.Errorf("wanted cookie named %q, got cookie named %q", anubis.CookieName, ckie.Name)
|
||||
}
|
||||
|
||||
if ckie.MaxAge != -1 {
|
||||
@@ -105,7 +105,7 @@ func TestClearCookieWithDynamicDomain(t *testing.T) {
|
||||
srv := spawnAnubis(t, Options{CookieDynamicDomain: true})
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
srv.ClearCookie(rw, srv.cookieName, "/", "xeiaso.net")
|
||||
srv.ClearCookie(rw, CookieOpts{Host: "subdomain.xeiaso.net"})
|
||||
|
||||
resp := rw.Result()
|
||||
|
||||
@@ -117,8 +117,12 @@ func TestClearCookieWithDynamicDomain(t *testing.T) {
|
||||
|
||||
ckie := cookies[0]
|
||||
|
||||
if ckie.Name != anubis.WithDomainCookieName+"xeiaso.net" {
|
||||
t.Errorf("wanted cookie named %q, got cookie named %q", srv.cookieName, ckie.Name)
|
||||
if ckie.Name != anubis.CookieName {
|
||||
t.Errorf("wanted cookie named %q, got cookie named %q", anubis.CookieName, ckie.Name)
|
||||
}
|
||||
|
||||
if ckie.Domain != "xeiaso.net" {
|
||||
t.Errorf("wanted cookie domain %q, got cookie domain %q", "xeiaso.net", ckie.Domain)
|
||||
}
|
||||
|
||||
if ckie.MaxAge != -1 {
|
||||
|
||||
63
lib/localization/locales/en.json
Normal file
63
lib/localization/locales/en.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"loading": "Loading...",
|
||||
"why_am_i_seeing": "Why am I seeing this?",
|
||||
"protected_by": "Protected by",
|
||||
"made_with": "Made with ❤️ in 🇨🇦",
|
||||
"mascot_design": "Mascot design by",
|
||||
"ai_companies_explanation": "You are seeing this because the administrator of this website has set up Anubis to protect the server against the scourge of AI companies aggressively scraping websites. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.",
|
||||
"anubis_compromise": "Anubis is a compromise. Anubis uses a Proof-of-Work scheme in the vein of Hashcash, a proposed proof-of-work scheme for reducing email spam. The idea is that at individual scales the additional load is ignorable, but at mass scraper levels it adds up and makes scraping much more expensive.",
|
||||
"hack_purpose": "Ultimately, this is a hack whose real purpose is to give a \"good enough\" placeholder solution so that more time can be spent on fingerprinting and identifying headless browsers (EG: via how they do font rendering) so that the challenge proof of work page doesn't need to be presented to users that are much more likely to be legitimate.",
|
||||
"jshelter_note": "Please note that Anubis requires the use of modern JavaScript features that plugins like JShelter will disable. Please disable JShelter or other such plugins for this domain.",
|
||||
"version_info": "This website is running Anubis version",
|
||||
"try_again": "Try again",
|
||||
"go_home": "Go home",
|
||||
"contact_webmaster": "or if you believe you should not be blocked, please contact the webmaster at",
|
||||
"connection_security": "Please wait a moment while we ensure the security of your connection.",
|
||||
"javascript_required": "Sadly, you must enable JavaScript to get past this challenge. This is required because AI companies have changed the social contract around how website hosting works. A no-JS solution is a work-in-progress.",
|
||||
"benchmark_requires_js": "Running the benchmark tool requires JavaScript to be enabled.",
|
||||
"difficulty": "Difficulty:",
|
||||
"algorithm": "Algorithm:",
|
||||
"compare": "Compare:",
|
||||
"time": "Time",
|
||||
"iters": "Iters",
|
||||
"time_a": "Time A",
|
||||
"iters_a": "Iters A",
|
||||
"time_b": "Time B",
|
||||
"iters_b": "Iters B",
|
||||
"static_check_endpoint": "This is just a check endpoint for your reverse proxy to use.",
|
||||
"authorization_required": "Authorization required",
|
||||
"cookies_disabled": "Your browser is configured to disable cookies. Anubis requires cookies for the legitimate interest of making sure you are a valid client. Please enable cookies for this domain",
|
||||
"access_denied": "Access Denied: error code",
|
||||
"dronebl_entry": "DroneBL reported an entry",
|
||||
"see_dronebl_lookup": "see",
|
||||
"internal_server_error": "Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around",
|
||||
"invalid_redirect": "Invalid redirect",
|
||||
"redirect_not_parseable": "Redirect URL not parseable",
|
||||
"redirect_domain_not_allowed": "Redirect domain not allowed",
|
||||
"failed_to_sign_jwt": "failed to sign JWT",
|
||||
"invalid_invocation": "Invalid invocation of MakeChallenge",
|
||||
"client_error_browser": "Client Error: Please ensure your browser is up to date and try again later.",
|
||||
"oh_noes": "Oh noes!",
|
||||
"benchmarking_anubis": "Benchmarking Anubis!",
|
||||
"you_are_not_a_bot": "You are not a bot!",
|
||||
"making_sure_not_bot": "Making sure you're not a bot!",
|
||||
"celphase": "CELPHASE",
|
||||
"js_web_crypto_error": "Your browser doesn't have a functioning web.crypto element. Are you viewing this over a secure context?",
|
||||
"js_web_workers_error": "Your browser doesn't support web workers (Anubis uses this to avoid freezing your browser). Do you have a plugin like JShelter installed?",
|
||||
"js_cookies_error": "Your browser doesn't store cookies. Anubis uses cookies to determine which clients have passed challenges by storing a signed token in a cookie. Please enable storing cookies for this domain. The names of the cookies Anubis stores may vary without notice. Cookie names and values are not part of the public API.",
|
||||
"js_context_not_secure": "Your context is not secure!",
|
||||
"js_context_not_secure_msg": "Try connecting over HTTPS or let the admin know to set up HTTPS. For more information, see <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
||||
"js_calculating": "Calculating...",
|
||||
"js_missing_feature": "Missing feature",
|
||||
"js_challenge_error": "Challenge error!",
|
||||
"js_challenge_error_msg": "Failed to resolve check algorithm. You may want to reload the page.",
|
||||
"js_calculating_difficulty": "Calculating...<br/>Difficulty:",
|
||||
"js_speed": "Speed:",
|
||||
"js_verification_longer": "Verification is taking longer than expected. Please do not refresh the page.",
|
||||
"js_success": "Success!",
|
||||
"js_done_took": "Done! Took",
|
||||
"js_iterations": "iterations",
|
||||
"js_finished_reading": "I've finished reading, continue →",
|
||||
"js_calculation_error": "Calculation error!",
|
||||
"js_calculation_error_msg": "Failed to calculate challenge:"
|
||||
}
|
||||
63
lib/localization/locales/es.json
Normal file
63
lib/localization/locales/es.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"loading": "Cargando...",
|
||||
"why_am_i_seeing": "¿Por qué veo esto?",
|
||||
"protected_by": "Protegido por",
|
||||
"made_with": "Hecho con ❤️ en 🇨🇦",
|
||||
"mascot_design": "Diseño de la mascota por",
|
||||
"ai_companies_explanation": "Ves esto porque el administrador de este sitio web ha configurado Anubis para proteger el servidor contra la plaga de empresas de IA que rastrean agresivamente los sitios web. Esto puede y causa tiempo de inactividad para los sitios web, haciendo que sus recursos sean inaccesibles para todos.",
|
||||
"anubis_compromise": "Anubis es un compromiso. Anubis utiliza un esquema de Prueba de Trabajo en la línea de Hashcash, un esquema de prueba de trabajo propuesto para reducir el spam por correo electrónico. La idea es que a escala individual, la carga adicional es insignificante, pero a escala de raspadores masivos, se acumula y hace que el raspado sea mucho más costoso.",
|
||||
"hack_purpose": "En última instancia, esto es un hack cuyo verdadero propósito es dar una solución alternativa \"suficientemente buena\" para que se pueda dedicar más tiempo a la huella digital e identificación de navegadores sin cabeza (por ejemplo: a través de cómo renderizan las fuentes) para que la página de desafío de prueba de trabajo no necesite ser presentada a usuarios que son mucho más propensos a ser legítimos.",
|
||||
"jshelter_note": "Ten en cuenta que Anubis requiere el uso de características modernas de JavaScript que plugins como JShelter deshabilitarán. Por favor, deshabilita JShelter u otros plugins similares para este dominio.",
|
||||
"version_info": "Este sitio web utiliza Anubis versión",
|
||||
"try_again": "Intentar de nuevo",
|
||||
"go_home": "Inicio",
|
||||
"contact_webmaster": "o si crees que no deberías estar bloqueado, por favor contacta al webmaster en",
|
||||
"connection_security": "Espere un momento mientras garantizamos la seguridad de su conexión.",
|
||||
"javascript_required": "Desafortunadamente, necesitas habilitar JavaScript para pasar este desafío. Esto es requerido porque las empresas de IA han cambiado el contrato social sobre cómo funciona el alojamiento de sitios web. Una solución sin JS está en desarrollo.",
|
||||
"benchmark_requires_js": "Ejecutar la herramienta de benchmark requiere que JavaScript esté habilitado.",
|
||||
"difficulty": "Dificultad:",
|
||||
"algorithm": "Algoritmo:",
|
||||
"compare": "Comparar:",
|
||||
"time": "Tiempo",
|
||||
"iters": "Iteraciones",
|
||||
"time_a": "Tiempo A",
|
||||
"iters_a": "Iter. A",
|
||||
"time_b": "Tiempo B",
|
||||
"iters_b": "Iter. B",
|
||||
"static_check_endpoint": "Este es solo un endpoint de verificación para que tu proxy inverso lo use.",
|
||||
"authorization_required": "Autorización requerida",
|
||||
"cookies_disabled": "Tu navegador está configurado para deshabilitar las cookies. Anubis requiere cookies para el interés legítimo de asegurar que eres un cliente válido. Por favor habilita las cookies para este dominio",
|
||||
"access_denied": "Acceso denegado: código de error",
|
||||
"dronebl_entry": "DroneBL reportó una entrada",
|
||||
"see_dronebl_lookup": "ver",
|
||||
"internal_server_error": "Error interno del servidor: el administrador ha configurado mal Anubis. Por favor contacta al administrador y pídele que revise los logs alrededor de",
|
||||
"invalid_redirect": "Redirección inválida",
|
||||
"redirect_not_parseable": "URL de redirección no analizable",
|
||||
"redirect_domain_not_allowed": "Dominio de redirección no permitido",
|
||||
"failed_to_sign_jwt": "falló al firmar JWT",
|
||||
"invalid_invocation": "Invocación inválida de MakeChallenge",
|
||||
"client_error_browser": "Error del cliente: Por favor asegúrate de que tu navegador esté actualizado e inténtalo de nuevo más tarde.",
|
||||
"oh_noes": "¡Oh no!",
|
||||
"benchmarking_anubis": "¡Benchmarking de Anubis!",
|
||||
"you_are_not_a_bot": "¡No eres un robot!",
|
||||
"making_sure_not_bot": "¡Asegurándonos de que no eres un robot!",
|
||||
"celphase": "CELPHASE",
|
||||
"js_web_crypto_error": "Tu navegador no tiene un elemento web.crypto funcional. ¿Estás viendo esta página en un contexto seguro?",
|
||||
"js_web_workers_error": "Tu navegador no soporta web workers (Anubis los usa para evitar bloquear tu navegador). ¿Tienes un plugin como JShelter instalado?",
|
||||
"js_cookies_error": "Tu navegador no almacena cookies. Anubis usa cookies para determinar qué clientes han pasado los desafíos almacenando un token firmado en una cookie. Por favor habilita el almacenamiento de cookies para este dominio. Los nombres de las cookies que Anubis almacena pueden variar sin previo aviso. Los nombres y valores de las cookies no son parte de la API pública.",
|
||||
"js_context_not_secure": "¡Tu contexto no es seguro!",
|
||||
"js_context_not_secure_msg": "Intenta conectarte a través de HTTPS o informa al administrador para configurar HTTPS. Para más información, consulta <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
||||
"js_calculating": "Calculando...",
|
||||
"js_missing_feature": "Característica faltante",
|
||||
"js_challenge_error": "¡Error de desafío!",
|
||||
"js_challenge_error_msg": "Falló al resolver el algoritmo de verificación. Puedes intentar recargar la página.",
|
||||
"js_calculating_difficulty": "Calculando...<br/>Dificultad:",
|
||||
"js_speed": "Velocidad:",
|
||||
"js_verification_longer": "La verificación está tomando más tiempo del esperado. Por favor no actualices la página.",
|
||||
"js_success": "¡Éxito!",
|
||||
"js_done_took": "¡Terminado! Tomó",
|
||||
"js_iterations": "iteraciones",
|
||||
"js_finished_reading": "He terminado de leer, continuar →",
|
||||
"js_calculation_error": "¡Error de cálculo!",
|
||||
"js_calculation_error_msg": "Falló al calcular el desafío:"
|
||||
}
|
||||
63
lib/localization/locales/fr.json
Normal file
63
lib/localization/locales/fr.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"loading": "Chargement...",
|
||||
"why_am_i_seeing": "Pourquoi je vois ceci ?",
|
||||
"protected_by": "Protégé par",
|
||||
"made_with": "Fait avec ❤️ au 🇨🇦",
|
||||
"mascot_design": "Design de la mascotte par",
|
||||
"ai_companies_explanation": "Vous voyez ceci car l'administrateur de ce site web a configuré Anubis pour protéger le serveur contre le fléau des entreprises d'IA qui scrapent agressivement les sites web. Cela peut et cause des temps d'arrêt pour les sites web, ce qui rend leurs ressources inaccessibles pour tout le monde.",
|
||||
"anubis_compromise": "Anubis est un compromis. Anubis utilise un schéma de Preuve de Travail dans la veine de Hashcash, un schéma de preuve de travail proposé pour réduire le spam par email. L'idée est qu'à l'échelle individuelle, la charge supplémentaire est négligeable, mais à l'échelle des scrapers de masse, cela s'accumule et rend le scraping beaucoup plus coûteux.",
|
||||
"hack_purpose": "En fin de compte, c'est un hack dont le véritable objectif est de donner une solution de substitution \"assez bonne\" pour que plus de temps puisse être consacré à l'empreinte digitale et à l'identification des navigateurs sans tête (par exemple : via la façon dont ils font le rendu des polices) afin que la page de défi de preuve de travail n'ait pas besoin d'être présentée aux utilisateurs qui sont beaucoup plus susceptibles d'être légitimes.",
|
||||
"jshelter_note": "Veuillez noter qu'Anubis nécessite l'utilisation de fonctionnalités JavaScript modernes que des plugins comme JShelter désactiveront. Veuillez désactiver JShelter ou d'autres plugins similaires pour ce domaine.",
|
||||
"version_info": "Ce site web utilise Anubis version",
|
||||
"try_again": "Réessayer",
|
||||
"go_home": "Accueil",
|
||||
"contact_webmaster": "ou si vous pensez que vous ne devriez pas être bloqué, veuillez contacter le webmaster à",
|
||||
"connection_security": "Veuillez patienter un instant pendant que nous assurons la sécurité de votre connexion.",
|
||||
"javascript_required": "Malheureusement, vous devez activer JavaScript pour passer ce défi. Ceci est requis car les entreprises d'IA ont changé le contrat social autour du fonctionnement de l'hébergement de sites web. Une solution sans JS est en cours de développement.",
|
||||
"benchmark_requires_js": "L'exécution de l'outil de benchmark nécessite l'activation de JavaScript.",
|
||||
"difficulty": "Difficulté :",
|
||||
"algorithm": "Algorithme :",
|
||||
"compare": "Comparer :",
|
||||
"time": "Temps",
|
||||
"iters": "Itérations",
|
||||
"time_a": "Temps A",
|
||||
"iters_a": "Itér. A",
|
||||
"time_b": "Temps B",
|
||||
"iters_b": "Itér. B",
|
||||
"static_check_endpoint": "Ceci est juste un point de terminaison de vérification pour votre proxy inverse à utiliser.",
|
||||
"authorization_required": "Autorisation requise",
|
||||
"cookies_disabled": "Votre navigateur est configuré pour désactiver les cookies. Anubis nécessite des cookies pour l'intérêt légitime de s'assurer que vous êtes un client valide. Veuillez activer les cookies pour ce domaine",
|
||||
"access_denied": "Accès refusé : code d'erreur",
|
||||
"dronebl_entry": "DroneBL a signalé une entrée",
|
||||
"see_dronebl_lookup": "voir",
|
||||
"internal_server_error": "Erreur interne du serveur : l'administrateur a mal configuré Anubis. Veuillez contacter l'administrateur et lui demander de consulter les logs autour de",
|
||||
"invalid_redirect": "Redirection invalide",
|
||||
"redirect_not_parseable": "URL de redirection non analysable",
|
||||
"redirect_domain_not_allowed": "Domaine de redirection non autorisé",
|
||||
"failed_to_sign_jwt": "échec de la signature JWT",
|
||||
"invalid_invocation": "Invocation invalide de MakeChallenge",
|
||||
"client_error_browser": "Erreur client : Veuillez vous assurer que votre navigateur est à jour et réessayez plus tard.",
|
||||
"oh_noes": "Oh non !",
|
||||
"benchmarking_anubis": "Test de performance d'Anubis !",
|
||||
"you_are_not_a_bot": "Vous n'êtes pas un robot !",
|
||||
"making_sure_not_bot": "Vérification que vous n'êtes pas un robot !",
|
||||
"celphase": "PHASE de CEL",
|
||||
"js_web_crypto_error": "Votre navigateur n'a pas d'élément web.crypto fonctionnel. Consultez-vous cette page dans un contexte sécurisé ?",
|
||||
"js_web_workers_error": "Votre navigateur ne prend pas en charge les web workers (Anubis les utilise pour éviter de bloquer votre navigateur). Avez-vous un plugin comme JShelter installé ?",
|
||||
"js_cookies_error": "Votre navigateur ne stocke pas les cookies. Anubis utilise des cookies pour déterminer quels clients ont réussi les défis en stockant un jeton signé dans un cookie. Veuillez activer le stockage des cookies pour ce domaine. Les noms des cookies qu'Anubis stocke peuvent varier sans préavis. Les noms et valeurs des cookies ne font pas partie de l'API publique.",
|
||||
"js_context_not_secure": "Votre contexte n'est pas sécurisé !",
|
||||
"js_context_not_secure_msg": "Essayez de vous connecter via HTTPS ou informez l'administrateur de configurer HTTPS. Pour plus d'informations, voir <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
||||
"js_calculating": "Calcul en cours...",
|
||||
"js_missing_feature": "Fonctionnalité manquante",
|
||||
"js_challenge_error": "Erreur de défi !",
|
||||
"js_challenge_error_msg": "Échec de la résolution de l'algorithme de vérification. Vous pouvez essayer de recharger la page.",
|
||||
"js_calculating_difficulty": "Calcul en cours...<br/>Difficulté :",
|
||||
"js_speed": "Vitesse :",
|
||||
"js_verification_longer": "La vérification prend plus de temps que prévu. Veuillez ne pas actualiser la page.",
|
||||
"js_success": "Succès !",
|
||||
"js_done_took": "Terminé ! A pris",
|
||||
"js_iterations": "itérations",
|
||||
"js_finished_reading": "J'ai fini de lire, continuer →",
|
||||
"js_calculation_error": "Erreur de calcul !",
|
||||
"js_calculation_error_msg": "Échec du calcul du défi :"
|
||||
}
|
||||
3
lib/localization/locales/manifest.json
Normal file
3
lib/localization/locales/manifest.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"supportedLanguages": ["en", "fr", "es", "pt-BR"]
|
||||
}
|
||||
63
lib/localization/locales/pt-BR.json
Normal file
63
lib/localization/locales/pt-BR.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"loading": "Carregando...",
|
||||
"why_am_i_seeing": "Por que estou vendo isso?",
|
||||
"protected_by": "Protegido por",
|
||||
"made_with": "Feito com ❤️ no 🇨🇦",
|
||||
"mascot_design": "Design do mascote por",
|
||||
"ai_companies_explanation": "Você está vendo isso porque o administrador deste site configurou Anubis para proteger o servidor contra a praga de empresas de IA que realizam scraping agressivo em sites. Isso pode causar, e de fato causa, inoperância nos sites, o que torna seus recursos inacessíveis para todos.",
|
||||
"anubis_compromise": "O Anubis é um meio-termo. Ele utiliza um esquema de Prova de Trabalho (Proof-of-Work) semelhante ao Hashcash, um esquema de Prova de Trabalho proposto para reduzir spam de e-mail. A ideia é que, em escalas individuais, a carga adicional seja insignificante, mas em níveis em massa de scrapers, ela se acumula e torna o scraping muito mais custoso.",
|
||||
"hack_purpose": "Em última análise, este é um hack cujo propósito real é fornecer uma solução \"boa o suficiente\" para que mais tempo possa ser gasto na identificação de navegadores sem interface (por exemplo: por meio de como eles fazem a renderização de fontes), para que a página do desafio da prova de trabalho não precise ser apresentada a usuários que têm muito mais probabilidade de serem legítimos.",
|
||||
"jshelter_note": "Observe que o Anubis requer o uso de recursos JavaScript modernos que plugins como o JShelter desabilitarão. Desabilite o JShelter ou outros plugins semelhantes para este domínio.",
|
||||
"version_info": "Este site está usando o Anubis versão",
|
||||
"try_again": "Tente novamente",
|
||||
"go_home": "Início",
|
||||
"contact_webmaster": "ou se você acredita que não deveria estar bloqueado, contate o webmaster em",
|
||||
"connection_security": "Por favor, aguarde um momento enquanto nós garantimos a segurança de sua conexão.",
|
||||
"javascript_required": "Infelizmente, você deve habilitar JavaScript para passar por este desafio. Isso é necessário porque empresas de IA alteraram o contrato social sobre como a hospedagem de sites funciona. Uma solução que não use JavaScript ainda está sendo desenvolvida.",
|
||||
"benchmark_requires_js": "Para executar a ferramenta de benchmark, é necessário que o JavaScript esteja habilitado.",
|
||||
"difficulty": "Dificuldade:",
|
||||
"algorithm": "Algoritmo:",
|
||||
"compare": "Comparar:",
|
||||
"time": "Tempo",
|
||||
"iters": "Iteração",
|
||||
"time_a": "Tempo A",
|
||||
"iters_a": "Iteração A",
|
||||
"time_b": "Tempo B",
|
||||
"iters_b": "Iteração B",
|
||||
"static_check_endpoint": "Este é apenas um ponto de verificação para seu proxy reverso usar.",
|
||||
"authorization_required": "Autorização necessária",
|
||||
"cookies_disabled": "Seu navegador está configurado para desabilitar cookies. O Anubis requer cookies para o interesse legítimo de garantir que você seja um cliente válido. Habilite os cookies para este domínio.",
|
||||
"access_denied": "Acesso negado: código de erro",
|
||||
"dronebl_entry": "DroneBL relatou uma entrada",
|
||||
"see_dronebl_lookup": "consulte",
|
||||
"internal_server_error": "Erro interno do servidor: o administrador configurou incorretamente o Anubis. Entre em contato com o administrador e peça para analisar os logs relacionados.",
|
||||
"invalid_redirect": "Redirecionamento inválido",
|
||||
"redirect_not_parseable": "URL de redirecionamento não analisável",
|
||||
"redirect_domain_not_allowed": "Domínio de redirecionamento não permitido",
|
||||
"failed_to_sign_jwt": "falha ao assinar JWT",
|
||||
"invalid_invocation": "Invocação inválida de MakeChallenge",
|
||||
"client_error_browser": "Erro do cliente: verifique se seu navegador está atualizado e tente novamente mais tarde..",
|
||||
"oh_noes": "Ah, não!",
|
||||
"benchmarking_anubis": "Fazendo benchmark do Anubis!",
|
||||
"you_are_not_a_bot": "Você não é um bot!",
|
||||
"making_sure_not_bot": "Certificando de que você não é um bot!",
|
||||
"celphase": "CELPHASE",
|
||||
"js_web_crypto_error": "Seu navegador não possui um elemento web.crypto funcional. Você está visualizando isso em um contexto seguro?",
|
||||
"js_web_workers_error": "Seu navegador não oferece suporte a web workers (o Anubis usa isso para evitar que seu navegador trave). Você tem um plugin como o JShelter instalado?",
|
||||
"js_cookies_error": "Seu navegador não armazena cookies. O Anubis usa cookies para determinar quais clientes passaram nos desafios, armazenando um token assinado em um cookie. Habilite o armazenamento de cookies para este domínio. Os nomes dos cookies armazenados pelo Anubis podem variar sem aviso prévio. Os nomes e valores dos cookies não fazem parte da API pública.",
|
||||
"js_context_not_secure": "Seu contexto não é seguro!",
|
||||
"js_context_not_secure_msg": "Tente conectar-se via HTTPS ou avise o administrador para configurar o HTTPS. Para mais informações, consulte o <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
||||
"js_calculating": "Calculando...",
|
||||
"js_missing_feature": "Faltando recurso",
|
||||
"js_challenge_error": "Erro no desafio!",
|
||||
"js_challenge_error_msg": "Falha ao resolver o algoritmo de verificação. Talvez seja necessário recarregar a página.",
|
||||
"js_calculating_difficulty": "Calculando...<br/>Dificuldade:",
|
||||
"js_speed": "Velocidade:",
|
||||
"js_verification_longer": "A verificação está demorando mais do que o esperado. Não atualize a página.",
|
||||
"js_success": "Sucesso!",
|
||||
"js_done_took": "Feito! Levou",
|
||||
"js_iterations": "iterações",
|
||||
"js_finished_reading": "Terminei de ler, continue →",
|
||||
"js_calculation_error": "Erro de cálculo!",
|
||||
"js_calculation_error_msg": "Falha ao calcular o desafio:"
|
||||
}
|
||||
106
lib/localization/localization.go
Normal file
106
lib/localization/localization.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package localization
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
//go:embed locales/*.json
|
||||
var localeFS embed.FS
|
||||
|
||||
type LocalizationService struct {
|
||||
bundle *i18n.Bundle
|
||||
}
|
||||
|
||||
var (
|
||||
globalService *LocalizationService
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func NewLocalizationService() *LocalizationService {
|
||||
once.Do(func() {
|
||||
bundle := i18n.NewBundle(language.English)
|
||||
bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
|
||||
|
||||
// Read all JSON files from the locales directory
|
||||
entries, err := localeFS.ReadDir("locales")
|
||||
if err != nil {
|
||||
// Try fallback - create a minimal service with default messages
|
||||
globalService = &LocalizationService{bundle: bundle}
|
||||
return
|
||||
}
|
||||
|
||||
loadedAny := false
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".json") {
|
||||
filePath := "locales/" + entry.Name()
|
||||
_, err := bundle.LoadMessageFileFS(localeFS, filePath)
|
||||
if err != nil {
|
||||
// Log error but continue with other files
|
||||
continue
|
||||
}
|
||||
loadedAny = true
|
||||
}
|
||||
}
|
||||
|
||||
if !loadedAny {
|
||||
// If no files were loaded successfully, create minimal service
|
||||
globalService = &LocalizationService{bundle: bundle}
|
||||
return
|
||||
}
|
||||
|
||||
globalService = &LocalizationService{bundle: bundle}
|
||||
})
|
||||
|
||||
// Safety check - if globalService is still nil, create a minimal one
|
||||
if globalService == nil {
|
||||
bundle := i18n.NewBundle(language.English)
|
||||
bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
|
||||
globalService = &LocalizationService{bundle: bundle}
|
||||
}
|
||||
|
||||
return globalService
|
||||
}
|
||||
|
||||
func (ls *LocalizationService) GetLocalizer(lang string) *i18n.Localizer {
|
||||
return i18n.NewLocalizer(ls.bundle, lang)
|
||||
}
|
||||
|
||||
func (ls *LocalizationService) GetLocalizerFromRequest(r *http.Request) *i18n.Localizer {
|
||||
if ls == nil || ls.bundle == nil {
|
||||
// Fallback to a basic bundle if service is not properly initialized
|
||||
bundle := i18n.NewBundle(language.English)
|
||||
bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
|
||||
return i18n.NewLocalizer(bundle, "en")
|
||||
}
|
||||
acceptLanguage := r.Header.Get("Accept-Language")
|
||||
return i18n.NewLocalizer(ls.bundle, acceptLanguage, "en")
|
||||
}
|
||||
|
||||
// SimpleLocalizer wraps i18n.Localizer with a more convenient API
|
||||
type SimpleLocalizer struct {
|
||||
Localizer *i18n.Localizer
|
||||
}
|
||||
|
||||
// T provides a concise way to localize messages
|
||||
func (sl *SimpleLocalizer) T(messageID string) string {
|
||||
return sl.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: messageID})
|
||||
}
|
||||
|
||||
// GetLocalizer creates a localizer based on the request's Accept-Language header or forcedLanguage option
|
||||
func GetLocalizer(r *http.Request) *SimpleLocalizer {
|
||||
var localizer *i18n.Localizer
|
||||
if anubis.ForcedLanguage == "" {
|
||||
localizer = NewLocalizationService().GetLocalizerFromRequest(r)
|
||||
} else {
|
||||
localizer = NewLocalizationService().GetLocalizer(anubis.ForcedLanguage)
|
||||
}
|
||||
return &SimpleLocalizer{Localizer: localizer}
|
||||
}
|
||||
116
lib/localization/localization_test.go
Normal file
116
lib/localization/localization_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package localization
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
)
|
||||
|
||||
func TestLocalizationService(t *testing.T) {
|
||||
service := NewLocalizationService()
|
||||
|
||||
t.Run("English localization", func(t *testing.T) {
|
||||
localizer := service.GetLocalizer("en")
|
||||
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
|
||||
if result != "Loading..." {
|
||||
t.Errorf("Expected 'Loading...', got '%s'", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("French localization", func(t *testing.T) {
|
||||
localizer := service.GetLocalizer("fr")
|
||||
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
|
||||
if result != "Chargement..." {
|
||||
t.Errorf("Expected 'Chargement...', got '%s'", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("All required keys exist in English", func(t *testing.T) {
|
||||
localizer := service.GetLocalizer("en")
|
||||
requiredKeys := []string{
|
||||
"loading", "why_am_i_seeing", "protected_by", "made_with",
|
||||
"mascot_design", "try_again", "go_home", "javascript_required",
|
||||
}
|
||||
|
||||
for _, key := range requiredKeys {
|
||||
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: key})
|
||||
if result == "" {
|
||||
t.Errorf("Key '%s' returned empty string", key)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("All required keys exist in French", func(t *testing.T) {
|
||||
localizer := service.GetLocalizer("fr")
|
||||
requiredKeys := []string{
|
||||
"loading", "why_am_i_seeing", "protected_by", "made_with",
|
||||
"mascot_design", "try_again", "go_home", "javascript_required",
|
||||
}
|
||||
|
||||
for _, key := range requiredKeys {
|
||||
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: key})
|
||||
if result == "" {
|
||||
t.Errorf("Key '%s' returned empty string", key)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type manifest struct {
|
||||
SupportedLanguages []string `json:"supported_languages"`
|
||||
}
|
||||
|
||||
func loadManifest(t *testing.T) manifest {
|
||||
t.Helper()
|
||||
|
||||
fin, err := localeFS.Open("locales/manifest.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fin.Close()
|
||||
|
||||
var result manifest
|
||||
if err := json.NewDecoder(fin).Decode(&result); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func TestComprehensiveTranslations(t *testing.T) {
|
||||
service := NewLocalizationService()
|
||||
|
||||
var translations = map[string]any{}
|
||||
fin, err := localeFS.Open("locales/en.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fin.Close()
|
||||
|
||||
if err := json.NewDecoder(fin).Decode(&translations); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var keys []string
|
||||
for k := range translations {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, lang := range loadManifest(t).SupportedLanguages {
|
||||
t.Run(lang, func(t *testing.T) {
|
||||
loc := service.GetLocalizer(lang)
|
||||
sl := SimpleLocalizer{Localizer: loc}
|
||||
for _, key := range keys {
|
||||
t.Run(key, func(t *testing.T) {
|
||||
if result := sl.T(key); result == "" {
|
||||
t.Error("key not defined")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ CacheDirectory=anubis/%i
|
||||
CacheDirectoryMode=0755
|
||||
StateDirectory=anubis/%i
|
||||
StateDirectoryMode=0755
|
||||
RuntimeDirectory=anubis
|
||||
RuntimeDirectory=anubis/%i
|
||||
RuntimeDirectoryMode=0755
|
||||
ReadWritePaths=/run
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ require (
|
||||
github.com/gaissmai/bart v0.20.4 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/google/cel-go v0.25.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/jsha/minica v1.1.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
@@ -43,9 +43,9 @@ require (
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/grpc v1.72.2 // indirect
|
||||
google.golang.org/grpc v1.73.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
k8s.io/apimachinery v0.33.1 // indirect
|
||||
k8s.io/apimachinery v0.33.2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -44,8 +44,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jsha/minica v1.1.0 h1:O2ZbzAN75w4RTB+5+HfjIEvY5nxRqDlwj3ZlLVG5JD8=
|
||||
@@ -113,6 +115,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
|
||||
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -124,6 +127,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4=
|
||||
k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
|
||||
@@ -32,9 +32,13 @@ THE SOFTWARE.
|
||||
for the JavaScript code in this page.
|
||||
*/'
|
||||
|
||||
# Copy localization files to static directory
|
||||
mkdir -p static/locales
|
||||
cp ../lib/localization/locales/*.json static/locales/
|
||||
|
||||
esbuild js/main.mjs --sourcemap --bundle --minify --outfile=static/js/main.mjs "--banner:js=${LICENSE}"
|
||||
gzip -f -k -n static/js/main.mjs
|
||||
zstd -f -k --ultra -22 static/js/main.mjs
|
||||
brotli -fZk static/js/main.mjs
|
||||
|
||||
esbuild js/bench.mjs --sourcemap --bundle --minify --outfile=static/js/bench.mjs
|
||||
esbuild js/bench.mjs --sourcemap --bundle --minify --outfile=static/js/bench.mjs
|
||||
|
||||
21
web/index.go
21
web/index.go
@@ -3,31 +3,32 @@ package web
|
||||
import (
|
||||
"github.com/a-h/templ"
|
||||
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
||||
)
|
||||
|
||||
func Base(title string, body templ.Component, impressum *config.Impressum) templ.Component {
|
||||
return base(title, body, impressum, nil, nil)
|
||||
func Base(title string, body templ.Component, impressum *config.Impressum, localizer *localization.SimpleLocalizer) templ.Component {
|
||||
return base(title, body, impressum, nil, nil, localizer)
|
||||
}
|
||||
|
||||
func BaseWithChallengeAndOGTags(title string, body templ.Component, impressum *config.Impressum, challenge string, rules *config.ChallengeRules, ogTags map[string]string) (templ.Component, error) {
|
||||
func BaseWithChallengeAndOGTags(title string, body templ.Component, impressum *config.Impressum, challenge string, rules *config.ChallengeRules, ogTags map[string]string, localizer *localization.SimpleLocalizer) (templ.Component, error) {
|
||||
return base(title, body, impressum, struct {
|
||||
Rules *config.ChallengeRules `json:"rules"`
|
||||
Challenge string `json:"challenge"`
|
||||
}{
|
||||
Challenge: challenge,
|
||||
Rules: rules,
|
||||
}, ogTags), nil
|
||||
}, ogTags, localizer), nil
|
||||
}
|
||||
|
||||
func Index() templ.Component {
|
||||
return index()
|
||||
func Index(localizer *localization.SimpleLocalizer) templ.Component {
|
||||
return index(localizer)
|
||||
}
|
||||
|
||||
func ErrorPage(msg string, mail string) templ.Component {
|
||||
return errorPage(msg, mail)
|
||||
func ErrorPage(msg string, mail string, localizer *localization.SimpleLocalizer) templ.Component {
|
||||
return errorPage(msg, mail, localizer)
|
||||
}
|
||||
|
||||
func Bench() templ.Component {
|
||||
return bench()
|
||||
func Bench(localizer *localization.SimpleLocalizer) templ.Component {
|
||||
return bench(localizer)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ package web
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
||||
"github.com/TecharoHQ/anubis/xess"
|
||||
)
|
||||
|
||||
templ base(title string, body templ.Component, impressum *config.Impressum, challenge any, ogTags map[string]string) {
|
||||
templ base(title string, body templ.Component, impressum *config.Impressum, challenge any, ogTags map[string]string, localizer *localization.SimpleLocalizer) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -71,14 +72,16 @@ templ base(title string, body templ.Component, impressum *config.Impressum, chal
|
||||
<footer>
|
||||
<center>
|
||||
<p>
|
||||
Protected by <a href="https://github.com/TecharoHQ/anubis">Anubis</a> from <a
|
||||
{ localizer.T("protected_by") } <a href="https://github.com/TecharoHQ/anubis">Anubis</a> from <a
|
||||
href="https://techaro.lol"
|
||||
>Techaro</a>. Made with ❤️ in 🇨🇦.
|
||||
>Techaro</a>. { localizer.T("made_with") }.
|
||||
</p>
|
||||
<p>Mascot design by <a href="https://bsky.app/profile/celphase.bsky.social">CELPHASE</a>.</p>
|
||||
<p>{ localizer.T("mascot_design") } <a href="https://bsky.app/profile/celphase.bsky.social">{ localizer.T("celphase") }</a>.</p>
|
||||
if impressum != nil {
|
||||
<p>@templ.Raw(impressum.Footer)
|
||||
-- <a href={ templ.SafeURL(fmt.Sprintf("%simprint", anubis.APIPrefix)) }>Imprint</a></p>
|
||||
<p>
|
||||
@templ.Raw(impressum.Footer)
|
||||
-- <a href={ templ.SafeURL(fmt.Sprintf("%simprint", anubis.APIPrefix)) }>Imprint</a>
|
||||
</p>
|
||||
}
|
||||
</center>
|
||||
</footer>
|
||||
@@ -87,79 +90,59 @@ templ base(title string, body templ.Component, impressum *config.Impressum, chal
|
||||
</html>
|
||||
}
|
||||
|
||||
templ index() {
|
||||
templ index(localizer *localization.SimpleLocalizer) {
|
||||
<div class="centered-div">
|
||||
<img id="image" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
|
||||
<img style="display:none;" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version }/>
|
||||
<p id="status">Loading...</p>
|
||||
<p id="status">{ localizer.T("loading") }</p>
|
||||
<script async type="module" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version }></script>
|
||||
<div id="progress" role="progressbar" aria-labelledby="status">
|
||||
<div class="bar-inner"></div>
|
||||
</div>
|
||||
<details>
|
||||
<summary>Why am I seeing this?</summary>
|
||||
<summary>{ localizer.T("why_am_i_seeing") }</summary>
|
||||
<p>
|
||||
You are seeing this because the administrator of this website has set up <a
|
||||
href="https://github.com/TecharoHQ/anubis"
|
||||
>Anubis</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>. This can and does cause downtime for the websites, which makes their
|
||||
resources inaccessible for everyone.
|
||||
{ localizer.T("ai_companies_explanation") }
|
||||
</p>
|
||||
<p>
|
||||
Anubis is a compromise. Anubis uses a <a
|
||||
href="https://anubis.techaro.lol/docs/design/why-proof-of-work"
|
||||
>Proof-of-Work</a>
|
||||
scheme in the vein of <a href="https://en.wikipedia.org/wiki/Hashcash">Hashcash</a>, a proposed
|
||||
proof-of-work scheme for reducing email spam. The idea is that at individual scales the additional load is
|
||||
ignorable, but at mass scraper levels it adds up and makes scraping much more expensive.
|
||||
{ localizer.T("anubis_compromise") }
|
||||
</p>
|
||||
<p>
|
||||
Ultimately, this is a hack whose real purpose is to give a "good enough" placeholder solution so that more
|
||||
time can be spent on fingerprinting and identifying headless browsers (EG: via how they do font rendering)
|
||||
so that the challenge proof of work page doesn't need to be presented to users that are much more likely to
|
||||
be legitimate.
|
||||
{ localizer.T("hack_purpose") }
|
||||
</p>
|
||||
<p>
|
||||
Please note that Anubis requires the use of modern JavaScript features that plugins like <a
|
||||
href="https://jshelter.org/"
|
||||
>JShelter</a> will disable. Please disable JShelter or other such
|
||||
plugins for this domain.
|
||||
{ localizer.T("jshelter_note") }
|
||||
</p>
|
||||
<p>This website is running Anubis version <code>{ anubis.Version }</code>.</p>
|
||||
<p>{ localizer.T("version_info") } <code>{ anubis.Version }</code>.</p>
|
||||
</details>
|
||||
<noscript>
|
||||
<p>
|
||||
Sadly, you must enable JavaScript to get past this challenge. This is required because AI companies have
|
||||
changed
|
||||
the social contract around how website hosting works. A no-JS solution is a work-in-progress.
|
||||
{ localizer.T("javascript_required") }
|
||||
</p>
|
||||
</noscript>
|
||||
<div id="testarea"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ errorPage(message string, mail string) {
|
||||
templ errorPage(message string, mail string, localizer *localization.SimpleLocalizer) {
|
||||
<div class="centered-div">
|
||||
<img id="image" alt="Sad Anubis" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/reject.webp?cacheBuster=" + anubis.Version }/>
|
||||
<p>{ message }.</p>
|
||||
<button onClick="window.location.reload();">Try again</button>
|
||||
<button onClick="window.location.reload();">{ localizer.T("try_again") }</button>
|
||||
if mail != "" {
|
||||
<p>
|
||||
<a href="/">Go home</a> or if you believe you should not be blocked, please contact the webmaster at
|
||||
<a href="/">{ localizer.T("go_home") }</a> { localizer.T("contact_webmaster") }
|
||||
<a href={ "mailto:" + templ.SafeURL(mail) }>
|
||||
{ mail }
|
||||
</a>
|
||||
</p>
|
||||
} else {
|
||||
<p><a href="/">Go home</a></p>
|
||||
<p><a href="/">{ localizer.T("go_home") }</a></p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
templ StaticHappy() {
|
||||
templ StaticHappy(localizer *localization.SimpleLocalizer) {
|
||||
<div class="centered-div">
|
||||
<img
|
||||
style="display:none;"
|
||||
@@ -167,25 +150,25 @@ templ StaticHappy() {
|
||||
src={ "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" +
|
||||
anubis.Version }
|
||||
/>
|
||||
<p>This is just a check endpoint for your reverse proxy to use.</p>
|
||||
<p>{ localizer.T("static_check_endpoint") }</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ bench() {
|
||||
templ bench(localizer *localization.SimpleLocalizer) {
|
||||
<div style="height:20rem;display:flex">
|
||||
<table style="margin-top:1rem;display:grid;grid-template:auto 1fr/auto auto;gap:0 0.5rem">
|
||||
<thead
|
||||
style="border-bottom:1px solid black;padding:0.25rem 0;display:grid;grid-template:1fr/subgrid;grid-column:1/-1"
|
||||
>
|
||||
<tr id="table-header" style="display:contents">
|
||||
<th style="width:4.5rem">Time</th>
|
||||
<th style="width:4rem">Iters</th>
|
||||
<th style="width:4.5rem">{ localizer.T("time") }</th>
|
||||
<th style="width:4rem">{ localizer.T("iters") }</th>
|
||||
</tr>
|
||||
<tr id="table-header-compare" style="display:none">
|
||||
<th style="width:4.5rem">Time A</th>
|
||||
<th style="width:4rem">Iters A</th>
|
||||
<th style="width:4.5rem">Time B</th>
|
||||
<th style="width:4rem">Iters B</th>
|
||||
<th style="width:4.5rem">{ localizer.T("time_a") }</th>
|
||||
<th style="width:4rem">{ localizer.T("iters_a") }</th>
|
||||
<th style="width:4.5rem">{ localizer.T("time_b") }</th>
|
||||
<th style="width:4rem">{ localizer.T("iters_b") }</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
@@ -195,25 +178,25 @@ templ bench() {
|
||||
</table>
|
||||
<div class="centered-div">
|
||||
<img id="image" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
|
||||
<p id="status" style="max-width:256px">Loading...</p>
|
||||
<p id="status" style="max-width:256px">{ localizer.T("loading") }</p>
|
||||
<script async type="module" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/bench.mjs?cacheBuster=" + anubis.Version }></script>
|
||||
<div id="sparkline"></div>
|
||||
<noscript>
|
||||
<p>Running the benchmark tool requires JavaScript to be enabled.</p>
|
||||
<p>{ localizer.T("benchmark_requires_js") }</p>
|
||||
</noscript>
|
||||
</div>
|
||||
</div>
|
||||
<form id="controls" style="position:fixed;top:0.5rem;right:0.5rem">
|
||||
<div style="display:flex;justify-content:end">
|
||||
<label for="difficulty-input" style="margin-right:0.5rem">Difficulty:</label>
|
||||
<label for="difficulty-input" style="margin-right:0.5rem">{ localizer.T("difficulty") }</label>
|
||||
<input id="difficulty-input" type="number" name="difficulty" style="width:3rem"/>
|
||||
</div>
|
||||
<div style="margin-top:0.25rem;display:flex;justify-content:end">
|
||||
<label for="algorithm-select" style="margin-right:0.5rem">Algorithm:</label>
|
||||
<label for="algorithm-select" style="margin-right:0.5rem">{ localizer.T("algorithm") }</label>
|
||||
<select id="algorithm-select" name="algorithm"></select>
|
||||
</div>
|
||||
<div style="margin-top:0.25rem;display:flex;justify-content:end">
|
||||
<label for="compare-select" style="margin-right:0.5rem">Compare:</label>
|
||||
<label for="compare-select" style="margin-right:0.5rem">{ localizer.T("compare") }</label>
|
||||
<select id="compare-select" name="compare">
|
||||
<option value="NONE">-</option>
|
||||
</select>
|
||||
|
||||
723
web/index_templ.go
generated
723
web/index_templ.go
generated
@@ -1,6 +1,6 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
// templ: version: v0.3.906
|
||||
package web
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
@@ -11,11 +11,12 @@ import templruntime "github.com/a-h/templ/runtime"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
||||
"github.com/TecharoHQ/anubis/xess"
|
||||
)
|
||||
|
||||
func base(title string, body templ.Component, impressum *config.Impressum, challenge any, ogTags map[string]string) templ.Component {
|
||||
func base(title string, body templ.Component, impressum *config.Impressum, challenge any, ogTags map[string]string, localizer *localization.SimpleLocalizer) 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 {
|
||||
@@ -43,7 +44,7 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
|
||||
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: 14, Col: 17}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 15, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -56,7 +57,7 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
|
||||
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: 15, Col: 61}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 16, Col: 61}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -74,7 +75,7 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
|
||||
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: 19, Col: 24}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 20, Col: 24}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -87,7 +88,7 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
|
||||
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: 19, Col: 42}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 20, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -123,7 +124,7 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
|
||||
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: 68, Col: 49}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 69, Col: 49}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -137,12 +138,64 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<footer><center><p>Protected by <a href=\"https://github.com/TecharoHQ/anubis\">Anubis</a> from <a href=\"https://techaro.lol\">Techaro</a>. Made with ❤️ in 🇨🇦.</p><p>Mascot design by <a href=\"https://bsky.app/profile/celphase.bsky.social\">CELPHASE</a>.</p>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<footer><center><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("protected_by"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 75, Col: 36}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " <a href=\"https://github.com/TecharoHQ/anubis\">Anubis</a> from <a href=\"https://techaro.lol\">Techaro</a>. ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("made_with"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 77, Col: 40}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, ".</p><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("mascot_design"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 79, Col: 39}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " <a href=\"https://bsky.app/profile/celphase.bsky.social\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("celphase"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 79, Col: 123}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</a>.</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if impressum != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<p>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -150,25 +203,25 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "-- <a href=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "-- <a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 templ.SafeURL
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("%simprint", anubis.APIPrefix)))
|
||||
var templ_7745c5c3_Var11 templ.SafeURL
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("%simprint", anubis.APIPrefix)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 81, Col: 70}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 83, Col: 78}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">Imprint</a></p>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">Imprint</a></p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</center></footer></main></body></html>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</center></footer></main></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -176,7 +229,7 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
|
||||
})
|
||||
}
|
||||
|
||||
func index() templ.Component {
|
||||
func index(localizer *localization.SimpleLocalizer) 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 {
|
||||
@@ -192,203 +245,168 @@ func index() templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var8 == nil {
|
||||
templ_7745c5c3_Var8 = templ.NopComponent
|
||||
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var12 == nil {
|
||||
templ_7745c5c3_Var12 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"centered-div\"><img id=\"image\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"centered-div\"><img id=\"image\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.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: 92, Col: 165}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 95, Col: 165}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"> <img style=\"display:none;\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.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: 93, Col: 174}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\"><p id=\"status\">Loading...</p><script async type=\"module\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.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: 95, Col: 136}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\"></script><div id=\"progress\" role=\"progressbar\" aria-labelledby=\"status\"><div class=\"bar-inner\"></div></div><details><summary>Why am I seeing this?</summary><p>You are seeing this because the administrator of this website has set up <a href=\"https://github.com/TecharoHQ/anubis\">Anubis</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>. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.</p><p>Anubis is a compromise. Anubis uses a <a href=\"https://anubis.techaro.lol/docs/design/why-proof-of-work\">Proof-of-Work</a> scheme in the vein of <a href=\"https://en.wikipedia.org/wiki/Hashcash\">Hashcash</a>, a proposed proof-of-work scheme for reducing email spam. The idea is that at individual scales the additional load is ignorable, but at mass scraper levels it adds up and makes scraping much more expensive.</p><p>Ultimately, this is a hack whose real purpose is to give a \"good enough\" placeholder solution so that more time can be spent on fingerprinting and identifying headless browsers (EG: via how they do font rendering) so that the challenge proof of work page doesn't need to be presented to users that are much more likely to be legitimate.</p><p>Please note that Anubis requires the use of modern JavaScript features that plugins like <a href=\"https://jshelter.org/\">JShelter</a> will disable. Please disable JShelter or other such plugins for this domain.</p><p>This website is running Anubis version <code>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.Version)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 131, Col: 67}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</code>.</p></details><noscript><p>Sadly, you must enable JavaScript to get past this challenge. This is required because AI companies have changed the social contract around how website hosting works. A no-JS solution is a work-in-progress.</p></noscript><div id=\"testarea\"></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var13 == nil {
|
||||
templ_7745c5c3_Var13 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<div class=\"centered-div\"><img id=\"image\" alt=\"Sad Anubis\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\"> <img style=\"display:none;\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/reject.webp?cacheBuster=" + anubis.Version)
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.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: 146, Col: 181}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 96, Col: 174}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\"><p>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\"><p id=\"status\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(message)
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("loading"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 147, Col: 14}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 97, Col: 41}
|
||||
}
|
||||
_, 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, 22, ".</p><button onClick=\"window.location.reload();\">Try again</button> ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</p><script async type=\"module\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if mail != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<p><a href=\"/\">Go home</a> or if you believe you should not be blocked, please contact the webmaster at <a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 templ.SafeURL
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinURLErrs("mailto:" + templ.SafeURL(mail))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 152, Col: 45}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var17 string
|
||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(mail)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 153, Col: 11}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</a></p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<p><a href=\"/\">Go home</a></p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 string
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.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: 136}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div>")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func StaticHappy() 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 {
|
||||
return templ_7745c5c3_CtxErr
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\"></script><div id=\"progress\" role=\"progressbar\" aria-labelledby=\"status\"><div class=\"bar-inner\"></div></div><details><summary>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
var templ_7745c5c3_Var17 string
|
||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("why_am_i_seeing"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 103, Col: 44}
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var18 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var18 == nil {
|
||||
templ_7745c5c3_Var18 = templ.NopComponent
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"centered-div\"><img style=\"display:none;\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</summary><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var18 string
|
||||
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("ai_companies_explanation"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 105, Col: 45}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</p><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var19 string
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs("/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" +
|
||||
anubis.Version)
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("anubis_compromise"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 168, Col: 18}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 108, Col: 38}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\"><p>This is just a check endpoint for your reverse proxy to use.</p></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</p><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var20 string
|
||||
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("hack_purpose"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 111, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</p><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var21 string
|
||||
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("jshelter_note"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 114, Col: 34}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</p><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var22 string
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("version_info"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 116, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, " <code>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var23 string
|
||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.Version)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 116, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</code>.</p></details><noscript><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var24 string
|
||||
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("javascript_required"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 120, Col: 40}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</p></noscript><div id=\"testarea\"></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -396,7 +414,7 @@ func StaticHappy() templ.Component {
|
||||
})
|
||||
}
|
||||
|
||||
func bench() templ.Component {
|
||||
func errorPage(message string, mail string, localizer *localization.SimpleLocalizer) 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 {
|
||||
@@ -412,38 +430,385 @@ func bench() templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var20 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var20 == nil {
|
||||
templ_7745c5c3_Var20 = templ.NopComponent
|
||||
templ_7745c5c3_Var25 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var25 == nil {
|
||||
templ_7745c5c3_Var25 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<div style=\"height:20rem;display:flex\"><table style=\"margin-top:1rem;display:grid;grid-template:auto 1fr/auto auto;gap:0 0.5rem\"><thead style=\"border-bottom:1px solid black;padding:0.25rem 0;display:grid;grid-template:1fr/subgrid;grid-column:1/-1\"><tr id=\"table-header\" style=\"display:contents\"><th style=\"width:4.5rem\">Time</th><th style=\"width:4rem\">Iters</th></tr><tr id=\"table-header-compare\" style=\"display:none\"><th style=\"width:4.5rem\">Time A</th><th style=\"width:4rem\">Iters A</th><th style=\"width:4.5rem\">Time B</th><th style=\"width:4rem\">Iters B</th></tr></thead> <tbody id=\"results\" style=\"padding-top:0.25rem;display:grid;grid-template-columns:subgrid;grid-auto-rows:min-content;grid-column:1/-1;row-gap:0.25rem;overflow-y:auto;font-variant-numeric:tabular-nums\"></tbody></table><div class=\"centered-div\"><img id=\"image\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<div class=\"centered-div\"><img id=\"image\" alt=\"Sad Anubis\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var21 string
|
||||
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
|
||||
var templ_7745c5c3_Var26 string
|
||||
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.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: 197, Col: 166}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 129, Col: 181}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\"><p id=\"status\" style=\"max-width:256px\">Loading...</p><script async type=\"module\" src=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\"><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var22 string
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/bench.mjs?cacheBuster=" + anubis.Version)
|
||||
var templ_7745c5c3_Var27 string
|
||||
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(message)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 199, Col: 138}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 130, Col: 14}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\"></script><div id=\"sparkline\"></div><noscript><p>Running the benchmark tool requires JavaScript to be enabled.</p></noscript></div></div><form id=\"controls\" style=\"position:fixed;top:0.5rem;right:0.5rem\"><div style=\"display:flex;justify-content:end\"><label for=\"difficulty-input\" style=\"margin-right:0.5rem\">Difficulty:</label> <input id=\"difficulty-input\" type=\"number\" name=\"difficulty\" style=\"width:3rem\"></div><div style=\"margin-top:0.25rem;display:flex;justify-content:end\"><label for=\"algorithm-select\" style=\"margin-right:0.5rem\">Algorithm:</label> <select id=\"algorithm-select\" name=\"algorithm\"></select></div><div style=\"margin-top:0.25rem;display:flex;justify-content:end\"><label for=\"compare-select\" style=\"margin-right:0.5rem\">Compare:</label> <select id=\"compare-select\" name=\"compare\"><option value=\"NONE\">-</option></select></div></form>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, ".</p><button onClick=\"window.location.reload();\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var28 string
|
||||
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("try_again"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 131, Col: 72}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</button> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if mail != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<p><a href=\"/\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var29 string
|
||||
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("go_home"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 134, Col: 40}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</a> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var30 string
|
||||
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("contact_webmaster"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 134, Col: 81}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " <a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var31 templ.SafeURL
|
||||
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinURLErrs("mailto:" + templ.SafeURL(mail))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 135, Col: 45}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var32 string
|
||||
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(mail)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 136, Col: 11}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</a></p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<p><a href=\"/\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var33 string
|
||||
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("go_home"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 140, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</a></p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func StaticHappy(localizer *localization.SimpleLocalizer) 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 {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var34 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var34 == nil {
|
||||
templ_7745c5c3_Var34 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<div class=\"centered-div\"><img style=\"display:none;\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var35 string
|
||||
templ_7745c5c3_Var35, 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: 151, Col: 18}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\"><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var36 string
|
||||
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("static_check_endpoint"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 153, Col: 43}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</p></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func bench(localizer *localization.SimpleLocalizer) 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 {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var37 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var37 == nil {
|
||||
templ_7745c5c3_Var37 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<div style=\"height:20rem;display:flex\"><table style=\"margin-top:1rem;display:grid;grid-template:auto 1fr/auto auto;gap:0 0.5rem\"><thead style=\"border-bottom:1px solid black;padding:0.25rem 0;display:grid;grid-template:1fr/subgrid;grid-column:1/-1\"><tr id=\"table-header\" style=\"display:contents\"><th style=\"width:4.5rem\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var38 string
|
||||
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("time"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 164, Col: 51}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</th><th style=\"width:4rem\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var39 string
|
||||
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("iters"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 165, Col: 50}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</th></tr><tr id=\"table-header-compare\" style=\"display:none\"><th style=\"width:4.5rem\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var40 string
|
||||
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("time_a"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 168, Col: 53}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</th><th style=\"width:4rem\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var41 string
|
||||
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("iters_a"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 169, Col: 52}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</th><th style=\"width:4.5rem\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var42 string
|
||||
templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("time_b"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 170, Col: 53}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</th><th style=\"width:4rem\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var43 string
|
||||
templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("iters_b"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 171, Col: 52}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</th></tr></thead> <tbody id=\"results\" style=\"padding-top:0.25rem;display:grid;grid-template-columns:subgrid;grid-auto-rows:min-content;grid-column:1/-1;row-gap:0.25rem;overflow-y:auto;font-variant-numeric:tabular-nums\"></tbody></table><div class=\"centered-div\"><img id=\"image\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var44 string
|
||||
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.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: 180, Col: 166}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\"><p id=\"status\" style=\"max-width:256px\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var45 string
|
||||
templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("loading"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 181, Col: 66}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</p><script async type=\"module\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var46 string
|
||||
templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.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: 182, Col: 138}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "\"></script><div id=\"sparkline\"></div><noscript><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var47 string
|
||||
templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("benchmark_requires_js"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 185, Col: 45}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</p></noscript></div></div><form id=\"controls\" style=\"position:fixed;top:0.5rem;right:0.5rem\"><div style=\"display:flex;justify-content:end\"><label for=\"difficulty-input\" style=\"margin-right:0.5rem\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var48 string
|
||||
templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("difficulty"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 191, Col: 88}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</label> <input id=\"difficulty-input\" type=\"number\" name=\"difficulty\" style=\"width:3rem\"></div><div style=\"margin-top:0.25rem;display:flex;justify-content:end\"><label for=\"algorithm-select\" style=\"margin-right:0.5rem\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var49 string
|
||||
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("algorithm"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 195, Col: 87}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</label> <select id=\"algorithm-select\" name=\"algorithm\"></select></div><div style=\"margin-top:0.25rem;display:flex;justify-content:end\"><label for=\"compare-select\" style=\"margin-right:0.5rem\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var50 string
|
||||
templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("compare"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 199, Col: 83}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</label> <select id=\"compare-select\" name=\"compare\"><option value=\"NONE\">-</option></select></div></form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
149
web/js/main.mjs
149
web/js/main.mjs
@@ -18,25 +18,87 @@ const imageURL = (mood, cacheBuster, basePrefix) =>
|
||||
cacheBuster,
|
||||
});
|
||||
|
||||
const dependencies = [
|
||||
{
|
||||
name: "WebCrypto",
|
||||
msg: "Your browser doesn't have a functioning web.crypto element. Are you viewing this over a secure context?",
|
||||
value: window.crypto,
|
||||
},
|
||||
{
|
||||
name: "Web Workers",
|
||||
msg: "Your browser doesn't support web workers (Anubis uses this to avoid freezing your browser). Do you have a plugin like JShelter installed?",
|
||||
value: window.Worker,
|
||||
},
|
||||
{
|
||||
name: "Cookies",
|
||||
msg: "Your browser doesn't store cookies. Anubis uses cookies to determine which clients have passed challenges by storing a signed token in a cookie. Please enable storing cookies for this domain. The names of the cookies Anubis stores may vary without notice. Cookie names and values are not part of the public API.",
|
||||
value: navigator.cookieEnabled,
|
||||
},
|
||||
];
|
||||
// Detect available languages by loading the manifest
|
||||
const getAvailableLanguages = async () => {
|
||||
const basePrefix = JSON.parse(
|
||||
document.getElementById("anubis_base_prefix").textContent,
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`);
|
||||
if (response.ok) {
|
||||
const manifest = await response.json();
|
||||
return manifest.supportedLanguages || ['en'];
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load language manifest, falling back to default languages');
|
||||
}
|
||||
|
||||
// Fallback to default languages if manifest loading fails
|
||||
return ['en'];
|
||||
};
|
||||
|
||||
// Detect browser language
|
||||
const getBrowserLanguage = async () => {
|
||||
const lang = navigator.language || navigator.userLanguage;
|
||||
const availableLanguages = await getAvailableLanguages();
|
||||
|
||||
// Extract the language code (first 2 characters)
|
||||
const langCode = lang.substring(0, 2).toLowerCase();
|
||||
|
||||
// Return the language if supported, or use English
|
||||
return availableLanguages.includes(langCode) ? langCode : 'en';
|
||||
};
|
||||
|
||||
// Load translations from JSON files
|
||||
const loadTranslations = async (lang) => {
|
||||
const basePrefix = JSON.parse(
|
||||
document.getElementById("anubis_base_prefix").textContent,
|
||||
);
|
||||
try {
|
||||
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load translations for ${lang}, falling back to English`);
|
||||
if (lang !== 'en') {
|
||||
return await loadTranslations('en');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
let translations = {};
|
||||
let currentLang;
|
||||
|
||||
// Initialize translations
|
||||
const initTranslations = async () => {
|
||||
currentLang = await getBrowserLanguage();
|
||||
translations = await loadTranslations(currentLang);
|
||||
};
|
||||
|
||||
const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
||||
|
||||
(async () => {
|
||||
// Initialize translations first
|
||||
await initTranslations();
|
||||
|
||||
const dependencies = [
|
||||
{
|
||||
name: "WebCrypto",
|
||||
msg: t('web_crypto_error'),
|
||||
value: window.crypto,
|
||||
},
|
||||
{
|
||||
name: "Web Workers",
|
||||
msg: t('web_workers_error'),
|
||||
value: window.Worker,
|
||||
},
|
||||
{
|
||||
name: "Cookies",
|
||||
msg: t('cookies_error'),
|
||||
value: navigator.cookieEnabled,
|
||||
},
|
||||
];
|
||||
const status = document.getElementById("status");
|
||||
const image = document.getElementById("image");
|
||||
const title = document.getElementById("title");
|
||||
@@ -67,19 +129,19 @@ const dependencies = [
|
||||
|
||||
if (!window.isSecureContext) {
|
||||
ohNoes({
|
||||
titleMsg: "Your context is not secure!",
|
||||
statusMsg: `Try connecting over HTTPS or let the admin know to set up HTTPS. For more information, see <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure">MDN</a>.`,
|
||||
titleMsg: t('context_not_secure'),
|
||||
statusMsg: t('context_not_secure_msg'),
|
||||
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
status.innerHTML = "Calculating...";
|
||||
status.innerHTML = t('calculating');
|
||||
|
||||
for (const { value, name, msg } of dependencies) {
|
||||
if (!value) {
|
||||
ohNoes({
|
||||
titleMsg: `Missing feature ${name}`,
|
||||
titleMsg: `${t('missing_feature')} ${name}`,
|
||||
statusMsg: msg,
|
||||
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
||||
});
|
||||
@@ -94,20 +156,20 @@ const dependencies = [
|
||||
const process = algorithms[rules.algorithm];
|
||||
if (!process) {
|
||||
ohNoes({
|
||||
titleMsg: "Challenge error!",
|
||||
statusMsg: `Failed to resolve check algorithm. You may want to reload the page.`,
|
||||
titleMsg: t('challenge_error'),
|
||||
statusMsg: t('challenge_error_msg'),
|
||||
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
status.innerHTML = `Calculating...<br/>Difficulty: ${rules.report_as}, `;
|
||||
status.innerHTML = `${t('calculating_difficulty')} ${rules.report_as}, `;
|
||||
progress.style.display = "inline-block";
|
||||
|
||||
// the whole text, including "Speed:", as a single node, because some browsers
|
||||
// (Firefox mobile) present screen readers with each node as a separate piece
|
||||
// of text.
|
||||
const rateText = document.createTextNode("Speed: 0kH/s");
|
||||
const rateText = document.createTextNode(`${t('speed')} 0kH/s`);
|
||||
status.appendChild(rateText);
|
||||
|
||||
let lastSpeedUpdate = 0;
|
||||
@@ -125,7 +187,7 @@ const dependencies = [
|
||||
// only update the speed every second so it's less visually distracting
|
||||
if (delta - lastSpeedUpdate > 1000) {
|
||||
lastSpeedUpdate = delta;
|
||||
rateText.data = `Speed: ${(iters / delta).toFixed(3)}kH/s`;
|
||||
rateText.data = `${t('speed')} ${(iters / delta).toFixed(3)}kH/s`;
|
||||
}
|
||||
// the probability of still being on the page is (1 - likelihood) ^ iters.
|
||||
// by definition, half of the time the progress bar only gets to half, so
|
||||
@@ -141,9 +203,7 @@ const dependencies = [
|
||||
if (probability < 0.1 && !showingApology) {
|
||||
status.append(
|
||||
document.createElement("br"),
|
||||
document.createTextNode(
|
||||
"Verification is taking longer than expected. Please do not refresh the page.",
|
||||
),
|
||||
document.createTextNode(t('verification_longer')),
|
||||
);
|
||||
showingApology = true;
|
||||
}
|
||||
@@ -152,11 +212,6 @@ const dependencies = [
|
||||
const t1 = Date.now();
|
||||
console.log({ hash, nonce });
|
||||
|
||||
title.innerHTML = "Success!";
|
||||
status.innerHTML = `Done! Took ${t1 - t0}ms, ${nonce} iterations`;
|
||||
image.src = imageURL("happy", anubisVersion, basePrefix);
|
||||
progress.style.display = "none";
|
||||
|
||||
if (userReadDetails) {
|
||||
const container = document.getElementById("progress");
|
||||
|
||||
@@ -174,7 +229,7 @@ const dependencies = [
|
||||
container.style.outlineOffset = "2px";
|
||||
container.style.width = "min(20rem, 90%)";
|
||||
container.style.margin = "1rem auto 2rem";
|
||||
container.innerHTML = "I've finished reading, continue →";
|
||||
container.innerHTML = t('finished_reading');
|
||||
|
||||
function onDetailsExpand() {
|
||||
const redir = window.location.href;
|
||||
@@ -191,22 +246,20 @@ const dependencies = [
|
||||
container.onclick = onDetailsExpand;
|
||||
setTimeout(onDetailsExpand, 30000);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
const redir = window.location.href;
|
||||
window.location.replace(
|
||||
u(`${basePrefix}/.within.website/x/cmd/anubis/api/pass-challenge`, {
|
||||
response: hash,
|
||||
nonce,
|
||||
redir,
|
||||
elapsedTime: t1 - t0,
|
||||
}),
|
||||
);
|
||||
}, 250);
|
||||
const redir = window.location.href;
|
||||
window.location.replace(
|
||||
u(`${basePrefix}/.within.website/x/cmd/anubis/api/pass-challenge`, {
|
||||
response: hash,
|
||||
nonce,
|
||||
redir,
|
||||
elapsedTime: t1 - t0,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
ohNoes({
|
||||
titleMsg: "Calculation error!",
|
||||
statusMsg: `Failed to calculate challenge: ${err.message}`,
|
||||
titleMsg: t('calculation_error'),
|
||||
statusMsg: `${t('calculation_error_msg')} ${err.message}`,
|
||||
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user