mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-06-10 22:37:59 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b6475caa4 |
@@ -39,5 +39,3 @@ wenet
|
|||||||
qwertiko
|
qwertiko
|
||||||
setuplistener
|
setuplistener
|
||||||
mba
|
mba
|
||||||
xfu
|
|
||||||
xou
|
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ fahedouch
|
|||||||
fastcgi
|
fastcgi
|
||||||
FCr
|
FCr
|
||||||
fcrdns
|
fcrdns
|
||||||
fcvg
|
|
||||||
fediverse
|
fediverse
|
||||||
ffprobe
|
ffprobe
|
||||||
fhdr
|
fhdr
|
||||||
@@ -239,7 +238,6 @@ mnt
|
|||||||
Mojeek
|
Mojeek
|
||||||
mojeekbot
|
mojeekbot
|
||||||
mozilla
|
mozilla
|
||||||
mqvh
|
|
||||||
myclient
|
myclient
|
||||||
mymaster
|
mymaster
|
||||||
mypass
|
mypass
|
||||||
@@ -389,7 +387,6 @@ vnd
|
|||||||
VPS
|
VPS
|
||||||
Vultr
|
Vultr
|
||||||
WAIFU
|
WAIFU
|
||||||
wcg
|
|
||||||
weblate
|
weblate
|
||||||
webmaster
|
webmaster
|
||||||
webpage
|
webpage
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: ./docs
|
context: ./docs
|
||||||
cache-to: type=gha
|
cache-to: type=gha
|
||||||
@@ -53,14 +53,14 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
|
|
||||||
- name: Apply k8s manifests to limsa lominsa
|
- name: Apply k8s manifests to limsa lominsa
|
||||||
uses: actions-hub/kubectl@934aaa4354bbbc3d2176ae8d7cae92d515032dff # v1.35.3
|
uses: actions-hub/kubectl@f8645c756533365a9fc1ae9aad8980b2a892d2c2 # v1.36.0
|
||||||
env:
|
env:
|
||||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||||
with:
|
with:
|
||||||
args: apply -k docs/manifest
|
args: apply -k docs/manifest
|
||||||
|
|
||||||
- name: Apply k8s manifests to limsa lominsa
|
- name: Apply k8s manifests to limsa lominsa
|
||||||
uses: actions-hub/kubectl@934aaa4354bbbc3d2176ae8d7cae92d515032dff # v1.35.3
|
uses: actions-hub/kubectl@f8645c756533365a9fc1ae9aad8980b2a892d2c2 # v1.36.0
|
||||||
env:
|
env:
|
||||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||||
with:
|
with:
|
||||||
context: ./docs
|
context: ./docs
|
||||||
cache-to: type=gha
|
cache-to: type=gha
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
go tool yeet
|
go tool yeet
|
||||||
|
|
||||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: packages
|
name: packages
|
||||||
path: var/*
|
path: var/*
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ jobs:
|
|||||||
- palemoon/amd64
|
- palemoon/amd64
|
||||||
#- palemoon/i386
|
#- palemoon/i386
|
||||||
- robots_txt
|
- robots_txt
|
||||||
- traefik
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -35,7 +34,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
@@ -58,7 +57,7 @@ jobs:
|
|||||||
run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV
|
run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: ${{ env.ARTIFACT_NAME }}
|
name: ${{ env.ARTIFACT_NAME }}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||||
|
|
||||||
- name: Run zizmor 🌈
|
- name: Run zizmor 🌈
|
||||||
run: uvx zizmor --format sarif . > results.sarif
|
run: uvx zizmor --format sarif . > results.sarif
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
category: zizmor
|
category: zizmor
|
||||||
|
|||||||
+1
-1
@@ -259,7 +259,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lg.Info("loading policy file", "fname", *policyFname)
|
lg.Info("loading policy file", "fname", *policyFname)
|
||||||
policy, err := libanubis.LoadPoliciesOrDefault(ctx, *policyFname, *challengeDifficulty, *slogLevel, strings.TrimSpace(*target) == "")
|
policy, err := libanubis.LoadPoliciesOrDefault(ctx, *policyFname, *challengeDifficulty, *slogLevel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("can't parse policy file: %v", err)
|
log.Fatalf("can't parse policy file: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-12
@@ -13,24 +13,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
<!-- This changes the project to: -->
|
<!-- This changes the project to: -->
|
||||||
|
|
||||||
- Patch [GHSA-6wcg-mqvh-fcvg](https://github.com/TecharoHQ/anubis/security/advisories/GHSA-6wcg-mqvh-fcvg) by containing subrequest logic to Anubis instances in subrequest mode
|
- Move metrics server configuration to [the policy file](./admin/policies.mdx#metrics-server).
|
||||||
- Implement robot9001 style delays on the honeypot feature so that the first hit takes 1 millisecond, the second takes 2, etc
|
|
||||||
- Move metrics server configuration to [the policy file](./admin/policies.mdx#metrics-server)
|
|
||||||
- Expose [pprof endpoints](https://pkg.go.dev/net/http/pprof) on the metrics listener to enable profiling Anubis in production.
|
- Expose [pprof endpoints](https://pkg.go.dev/net/http/pprof) on the metrics listener to enable profiling Anubis in production.
|
||||||
- fix: prevent nil pointer panic in challenge validation when threshold rules match during PassChallenge (#1463)
|
- fix: prevent nil pointer panic in challenge validation when threshold rules match during PassChallenge (#1463)
|
||||||
- Instruct reverse proxies to not cache error pages
|
- Instruct reverse proxies to not cache error pages.
|
||||||
- Fixed mixed tab/space indentation in Caddy documentation code block
|
- Fixed mixed tab/space indentation in Caddy documentation code block
|
||||||
- Improve error messages and fix broken REDIRECT_DOMAINS link in docs ([#1193](https://github.com/TecharoHQ/anubis/issues/1193))
|
- Improve error messages and fix broken REDIRECT_DOMAINS link in docs ([#1193](https://github.com/TecharoHQ/anubis/issues/1193))
|
||||||
- Add Bulgarian locale ([#1394](https://github.com/TecharoHQ/anubis/pull/1394))
|
- Add Bulgarian locale ([#1394](https://github.com/TecharoHQ/anubis/pull/1394))
|
||||||
- Fixed case-sensitivity mismatch in geoipchecker.go
|
- Fixed case-sensitivity mismatch in geoipchecker.go
|
||||||
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465))
|
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)).
|
||||||
- Enable [metrics serving via TLS](./admin/policies.mdx#tls), including [mutual TLS (mTLS)](./admin/policies.mdx#mtls)
|
- Enable [metrics serving via TLS](./admin/policies.mdx#tls), including [mutual TLS (mTLS)](./admin/policies.mdx#mtls).
|
||||||
- Enable [HTTP basic auth](./admin/policies.mdx#http-basic-authentication) for the metrics server
|
- Enable [HTTP basic auth](./admin/policies.mdx#http-basic-authentication) for the metrics server.
|
||||||
- Fix a bug in the dataset poisoning maze that could allow denial of service [#1580](https://github.com/TecharoHQ/anubis/issues/1580)
|
- Fix a bug in the dataset poisoning maze that could allow denial of service [#1580](https://github.com/TecharoHQ/anubis/issues/1580).
|
||||||
- Add config option to add ASN to logs/metrics
|
|
||||||
- Log weight when issuing challenge
|
|
||||||
- Keep Anubis server URL state local to each `lib.Server` instance to make embedded use safer
|
|
||||||
- Fix `path_regex` and CEL `path` rules not matching when using Traefik `forwardAuth` middleware. Anubis now checks `X-Forwarded-Uri` (Traefik) in addition to `X-Original-URI` (nginx) when resolving the request path in subrequest mode ([#1628](https://github.com/TecharoHQ/anubis/issues/1628))
|
|
||||||
|
|
||||||
## v1.25.0: Necron
|
## v1.25.0: Necron
|
||||||
|
|
||||||
|
|||||||
@@ -411,7 +411,6 @@ Anubis exposes the following logging settings in the policy file:
|
|||||||
| `level` | [log level](#log-levels) | `info` | The logging level threshold. Any logs that are at or above this threshold will be drained to the sink. Any other logs will be discarded. |
|
| `level` | [log level](#log-levels) | `info` | The logging level threshold. Any logs that are at or above this threshold will be drained to the sink. Any other logs will be discarded. |
|
||||||
| `sink` | string | `stdio`, `file` | The sink where the logs drain to as they are being recorded in Anubis. |
|
| `sink` | string | `stdio`, `file` | The sink where the logs drain to as they are being recorded in Anubis. |
|
||||||
| `parameters` | object | | Parameters for the given logging sink. This will vary based on the logging sink of choice. See below for more information. |
|
| `parameters` | object | | Parameters for the given logging sink. This will vary based on the logging sink of choice. See below for more information. |
|
||||||
| `asn` | bool | `true`, `false` | Add ASN information to logs/metrics. (Requires a Thoth client configured) |
|
|
||||||
|
|
||||||
Anubis supports the following logging sinks:
|
Anubis supports the following logging sinks:
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ require (
|
|||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||||
github.com/go-git/go-git/v5 v5.16.2 // indirect
|
github.com/go-git/go-git/v5 v5.16.2 // indirect
|
||||||
github.com/go-jose/go-jose/v3 v3.0.5 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
|||||||
@@ -189,8 +189,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
|
|||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
||||||
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ=
|
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"math"
|
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -169,9 +168,6 @@ func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
millisecondAmount := math.Pow(float64(networkCount), 2)
|
|
||||||
time.Sleep(time.Duration(millisecondAmount) * time.Millisecond)
|
|
||||||
|
|
||||||
spins := i.makeSpins()
|
spins := i.makeSpins()
|
||||||
affirmations := i.makeAffirmations()
|
affirmations := i.makeAffirmations()
|
||||||
title := i.makeTitle()
|
title := i.makeTitle()
|
||||||
|
|||||||
@@ -595,7 +595,7 @@ func spawnAnubisWithOptions(t *testing.T, basePrefix string) string {
|
|||||||
fmt.Fprintf(w, "<html><body><span id=anubis-test>%d</span></body></html>", time.Now().Unix())
|
fmt.Fprintf(w, "<html><body><span id=anubis-test>%d</span></body></html>", time.Now().Unix())
|
||||||
})
|
})
|
||||||
|
|
||||||
policy, err := libanubis.LoadPoliciesOrDefault(t.Context(), "", anubis.DefaultDifficulty, "info", false)
|
policy, err := libanubis.LoadPoliciesOrDefault(t.Context(), "", anubis.DefaultDifficulty, "info")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-114
@@ -11,7 +11,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -33,8 +32,6 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/lib/policy"
|
"github.com/TecharoHQ/anubis/lib/policy"
|
||||||
"github.com/TecharoHQ/anubis/lib/policy/checker"
|
"github.com/TecharoHQ/anubis/lib/policy/checker"
|
||||||
"github.com/TecharoHQ/anubis/lib/store"
|
"github.com/TecharoHQ/anubis/lib/store"
|
||||||
"github.com/TecharoHQ/anubis/web"
|
|
||||||
iptoasnv1 "github.com/TecharoHQ/thoth-proto/gen/techaro/thoth/iptoasn/v1"
|
|
||||||
|
|
||||||
// challenge implementations
|
// challenge implementations
|
||||||
_ "github.com/TecharoHQ/anubis/lib/challenge/metarefresh"
|
_ "github.com/TecharoHQ/anubis/lib/challenge/metarefresh"
|
||||||
@@ -42,52 +39,31 @@ import (
|
|||||||
_ "github.com/TecharoHQ/anubis/lib/challenge/proofofwork"
|
_ "github.com/TecharoHQ/anubis/lib/challenge/proofofwork"
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextKey int
|
|
||||||
|
|
||||||
const asnContextKey contextKey = iota
|
|
||||||
|
|
||||||
type asnInfo struct {
|
|
||||||
ASN string
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
func asnFromContext(ctx context.Context) (string, string) {
|
|
||||||
if v, ok := ctx.Value(asnContextKey).(asnInfo); ok {
|
|
||||||
return v.ASN, v.Description
|
|
||||||
}
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
challengesIssued = promauto.NewCounterVec(prometheus.CounterOpts{
|
challengesIssued = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Name: "anubis_challenges_issued",
|
Name: "anubis_challenges_issued",
|
||||||
Help: "The total number of challenges issued",
|
Help: "The total number of challenges issued",
|
||||||
}, []string{"method", "asn", "asn_description"})
|
}, []string{"method"})
|
||||||
|
|
||||||
challengesValidated = promauto.NewCounterVec(prometheus.CounterOpts{
|
challengesValidated = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Name: "anubis_challenges_validated",
|
Name: "anubis_challenges_validated",
|
||||||
Help: "The total number of challenges validated",
|
Help: "The total number of challenges validated",
|
||||||
}, []string{"method", "asn", "asn_description"})
|
}, []string{"method"})
|
||||||
|
|
||||||
droneBLHits = promauto.NewCounterVec(prometheus.CounterOpts{
|
droneBLHits = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Name: "anubis_dronebl_hits",
|
Name: "anubis_dronebl_hits",
|
||||||
Help: "The total number of hits from DroneBL",
|
Help: "The total number of hits from DroneBL",
|
||||||
}, []string{"status", "asn", "asn_description"})
|
}, []string{"status"})
|
||||||
|
|
||||||
failedValidations = promauto.NewCounterVec(prometheus.CounterOpts{
|
failedValidations = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Name: "anubis_failed_validations",
|
Name: "anubis_failed_validations",
|
||||||
Help: "The total number of failed validations",
|
Help: "The total number of failed validations",
|
||||||
}, []string{"method", "asn", "asn_description"})
|
}, []string{"method"})
|
||||||
|
|
||||||
requestsProxied = promauto.NewCounterVec(prometheus.CounterOpts{
|
requestsProxied = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Name: "anubis_proxied_requests_total",
|
Name: "anubis_proxied_requests_total",
|
||||||
Help: "Number of requests proxied through Anubis to upstream targets",
|
Help: "Number of requests proxied through Anubis to upstream targets",
|
||||||
}, []string{"host", "asn", "asn_description"})
|
}, []string{"host"})
|
||||||
|
|
||||||
requestsByASN = promauto.NewCounterVec(prometheus.CounterOpts{
|
|
||||||
Name: "anubis_requests_by_asn_total",
|
|
||||||
Help: "Number of requests by ASN",
|
|
||||||
}, []string{"asn", "asn_description"})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -98,70 +74,10 @@ type Server struct {
|
|||||||
OGTags *ogtags.OGTagCache
|
OGTags *ogtags.OGTagCache
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
opts Options
|
opts Options
|
||||||
basePrefix string
|
|
||||||
publicURL string
|
|
||||||
ed25519Priv ed25519.PrivateKey
|
ed25519Priv ed25519.PrivateKey
|
||||||
hs512Secret []byte
|
hs512Secret []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getRequestLogger(r *http.Request) (*slog.Logger, *http.Request) {
|
|
||||||
lg := internal.GetRequestLogger(s.logger, r)
|
|
||||||
|
|
||||||
if s.policy.LogASN && s.policy.ThothClient != nil {
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), 500*time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
ip := r.Header.Get("X-Real-Ip")
|
|
||||||
if info, err := s.policy.ThothClient.IPToASN.Lookup(ctx, &iptoasnv1.LookupRequest{IpAddress: ip}); err == nil && info.GetAnnounced() {
|
|
||||||
asn := strconv.FormatUint(uint64(info.GetAsNumber()), 10)
|
|
||||||
lg = lg.With("asn", info.GetAsNumber(), "asn_description", info.GetDescription())
|
|
||||||
requestsByASN.WithLabelValues(asn, info.GetDescription()).Inc()
|
|
||||||
r = r.WithContext(context.WithValue(r.Context(), asnContextKey, asnInfo{
|
|
||||||
ASN: asn,
|
|
||||||
Description: info.GetDescription(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lg, r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) configuredBasePrefix() string {
|
|
||||||
if s.basePrefix != "" {
|
|
||||||
return s.basePrefix
|
|
||||||
}
|
|
||||||
return strings.TrimRight(s.opts.BasePrefix, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) configuredPublicURL() string {
|
|
||||||
if s.publicURL != "" {
|
|
||||||
return s.publicURL
|
|
||||||
}
|
|
||||||
return s.opts.PublicUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) prefixedPath(path string) string {
|
|
||||||
if !strings.HasPrefix(path, "/") {
|
|
||||||
path = "/" + path
|
|
||||||
}
|
|
||||||
return s.configuredBasePrefix() + path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) cookiePath() string {
|
|
||||||
basePrefix := s.configuredBasePrefix()
|
|
||||||
if basePrefix == "" {
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
return basePrefix + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) renderOptions() web.Options {
|
|
||||||
return web.Options{
|
|
||||||
BasePrefix: s.configuredBasePrefix(),
|
|
||||||
PublicURL: s.configuredPublicURL(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
|
func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
|
||||||
// return ED25519 key if HS512 is not set
|
// return ED25519 key if HS512 is not set
|
||||||
if len(s.hs512Secret) == 0 {
|
if len(s.hs512Secret) == 0 {
|
||||||
@@ -225,7 +141,7 @@ func (s *Server) issueChallenge(ctx context.Context, r *http.Request, lg *slog.L
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lg.Info("new challenge issued", "challenge", id.String(), "weight", cr.Weight)
|
lg.Info("new challenge issued", "challenge", id.String())
|
||||||
|
|
||||||
return &chall, err
|
return &chall, err
|
||||||
}
|
}
|
||||||
@@ -277,7 +193,7 @@ func (s *Server) maybeReverseProxyOrPage(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpStatusOnly bool) {
|
func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpStatusOnly bool) {
|
||||||
lg, r := s.getRequestLogger(r)
|
lg := internal.GetRequestLogger(s.logger, r)
|
||||||
|
|
||||||
if val, _ := s.store.Get(r.Context(), fmt.Sprintf("ogtags:allow:%s%s", r.Host, r.URL.String())); val != nil {
|
if val, _ := s.store.Get(r.Context(), fmt.Sprintf("ogtags:allow:%s%s", r.Host, r.URL.String())); val != nil {
|
||||||
lg.Debug("serving opengraph tag asset")
|
lg.Debug("serving opengraph tag asset")
|
||||||
@@ -285,7 +201,11 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cookiePath := s.cookiePath()
|
// Adjust cookie path if base prefix is not empty
|
||||||
|
cookiePath := "/"
|
||||||
|
if anubis.BasePrefix != "" {
|
||||||
|
cookiePath = strings.TrimSuffix(anubis.BasePrefix, "/") + "/"
|
||||||
|
}
|
||||||
|
|
||||||
cr, rule, err := s.check(r, lg)
|
cr, rule, err := s.check(r, lg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -298,10 +218,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
|||||||
r.Header.Add("X-Anubis-Rule", cr.Name)
|
r.Header.Add("X-Anubis-Rule", cr.Name)
|
||||||
r.Header.Add("X-Anubis-Action", string(cr.Rule))
|
r.Header.Add("X-Anubis-Action", string(cr.Rule))
|
||||||
lg = lg.With("check_result", cr)
|
lg = lg.With("check_result", cr)
|
||||||
{
|
policy.Applications.WithLabelValues(cr.Name, string(cr.Rule)).Add(1)
|
||||||
asn, asnDesc := asnFromContext(r.Context())
|
|
||||||
policy.Applications.WithLabelValues(cr.Name, string(cr.Rule), asn, asnDesc).Add(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := r.Header.Get("X-Real-Ip")
|
ip := r.Header.Get("X-Real-Ip")
|
||||||
|
|
||||||
@@ -379,7 +296,11 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.CheckResult, lg *slog.Logger, rule *policy.Bot) bool {
|
func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.CheckResult, lg *slog.Logger, rule *policy.Bot) bool {
|
||||||
cookiePath := s.cookiePath()
|
// Adjust cookie path if base prefix is not empty
|
||||||
|
cookiePath := "/"
|
||||||
|
if anubis.BasePrefix != "" {
|
||||||
|
cookiePath = strings.TrimSuffix(anubis.BasePrefix, "/") + "/"
|
||||||
|
}
|
||||||
|
|
||||||
localizer := localization.GetLocalizer(r)
|
localizer := localization.GetLocalizer(r)
|
||||||
|
|
||||||
@@ -427,8 +348,7 @@ func (s *Server) handleDNSBL(w http.ResponseWriter, r *http.Request, ip string,
|
|||||||
lg.Error("can't look up ip in dnsbl", "err", err)
|
lg.Error("can't look up ip in dnsbl", "err", err)
|
||||||
}
|
}
|
||||||
db.Set(r.Context(), ip, resp, 24*time.Hour)
|
db.Set(r.Context(), ip, resp, 24*time.Hour)
|
||||||
asn, asnDesc := asnFromContext(r.Context())
|
droneBLHits.WithLabelValues(resp.String()).Inc()
|
||||||
droneBLHits.WithLabelValues(resp.String(), asn, asnDesc).Inc()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp != dnsbl.AllGood {
|
if resp != dnsbl.AllGood {
|
||||||
@@ -446,7 +366,7 @@ func (s *Server) handleDNSBL(w http.ResponseWriter, r *http.Request, ip string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
lg, r := s.getRequestLogger(r)
|
lg := internal.GetRequestLogger(s.logger, r)
|
||||||
localizer := localization.GetLocalizer(r)
|
localizer := localization.GetLocalizer(r)
|
||||||
|
|
||||||
redir := r.FormValue("redir")
|
redir := r.FormValue("redir")
|
||||||
@@ -515,14 +435,11 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
lg.Debug("made challenge", "challenge", chall, "rules", rule.Challenge, "cr", cr)
|
lg.Debug("made challenge", "challenge", chall, "rules", rule.Challenge, "cr", cr)
|
||||||
{
|
challengesIssued.WithLabelValues("api").Inc()
|
||||||
asn, asnDesc := asnFromContext(r.Context())
|
|
||||||
challengesIssued.WithLabelValues("api", asn, asnDesc).Inc()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
lg, r := s.getRequestLogger(r)
|
lg := internal.GetRequestLogger(s.logger, r)
|
||||||
localizer := localization.GetLocalizer(r)
|
localizer := localization.GetLocalizer(r)
|
||||||
|
|
||||||
redir := r.FormValue("redir")
|
redir := r.FormValue("redir")
|
||||||
@@ -542,7 +459,11 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cookiePath := s.cookiePath()
|
// Adjust cookie path if base prefix is not empty
|
||||||
|
cookiePath := "/"
|
||||||
|
if anubis.BasePrefix != "" {
|
||||||
|
cookiePath = strings.TrimSuffix(anubis.BasePrefix, "/") + "/"
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := r.Cookie(anubis.TestCookieName); errors.Is(err, http.ErrNoCookie) {
|
if _, err := r.Cookie(anubis.TestCookieName); errors.Is(err, http.ErrNoCookie) {
|
||||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||||
@@ -609,8 +530,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := impl.Validate(r, lg, in); err != nil {
|
if err := impl.Validate(r, lg, in); err != nil {
|
||||||
asn, asnDesc := asnFromContext(r.Context())
|
failedValidations.WithLabelValues(rule.Challenge.Algorithm).Inc()
|
||||||
failedValidations.WithLabelValues(rule.Challenge.Algorithm, asn, asnDesc).Inc()
|
|
||||||
var cerr *challenge.Error
|
var cerr *challenge.Error
|
||||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||||
lg.Debug("challenge validate call failed", "err", err)
|
lg.Debug("challenge validate call failed", "err", err)
|
||||||
@@ -670,10 +590,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
lg.Debug("can't update information about challenge", "err", err)
|
lg.Debug("can't update information about challenge", "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
challengesValidated.WithLabelValues(rule.Challenge.Algorithm).Inc()
|
||||||
asn, asnDesc := asnFromContext(r.Context())
|
|
||||||
challengesValidated.WithLabelValues(rule.Challenge.Algorithm, asn, asnDesc).Inc()
|
|
||||||
}
|
|
||||||
lg.Debug("challenge passed, redirecting to app")
|
lg.Debug("challenge passed, redirecting to app")
|
||||||
http.Redirect(w, r, redir, http.StatusFound)
|
http.Redirect(w, r, redir, http.StatusFound)
|
||||||
}
|
}
|
||||||
@@ -712,8 +629,7 @@ func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *p
|
|||||||
return cr("bot/"+b.Name, b.Action, weight), &b, nil
|
return cr("bot/"+b.Name, b.Action, weight), &b, nil
|
||||||
case config.RuleWeigh:
|
case config.RuleWeigh:
|
||||||
lg.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust)
|
lg.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust)
|
||||||
asn, asnDesc := asnFromContext(r.Context())
|
policy.Applications.WithLabelValues("bot/"+b.Name, "WEIGH").Add(1)
|
||||||
policy.Applications.WithLabelValues("bot/"+b.Name, "WEIGH", asn, asnDesc).Add(1)
|
|
||||||
weight += b.Weight.Adjust
|
weight += b.Weight.Adjust
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-94
@@ -2,7 +2,6 @@ package lib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -59,7 +58,7 @@ func loadPolicies(t *testing.T, fname string, difficulty int) *policy.ParsedConf
|
|||||||
|
|
||||||
t.Logf("loading policy file: %s", fname)
|
t.Logf("loading policy file: %s", fname)
|
||||||
|
|
||||||
anubisPolicy, err := LoadPoliciesOrDefault(ctx, fname, difficulty, "info", false)
|
anubisPolicy, err := LoadPoliciesOrDefault(ctx, fname, difficulty, "info")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -251,7 +250,7 @@ func TestLoadPolicies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer fin.Close()
|
defer fin.Close()
|
||||||
|
|
||||||
if _, err := policy.ParseConfig(t.Context(), fin, fname, 4, "info", false); err != nil {
|
if _, err := policy.ParseConfig(t.Context(), fin, fname, 4, "info"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -502,11 +501,8 @@ func TestBasePrefix(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
originalBasePrefix := anubis.BasePrefix
|
// Reset the global BasePrefix before each test
|
||||||
anubis.BasePrefix = "/not-this-server"
|
anubis.BasePrefix = ""
|
||||||
t.Cleanup(func() {
|
|
||||||
anubis.BasePrefix = originalBasePrefix
|
|
||||||
})
|
|
||||||
|
|
||||||
pol := loadPolicies(t, "", 4)
|
pol := loadPolicies(t, "", 4)
|
||||||
|
|
||||||
@@ -635,96 +631,10 @@ func TestBasePrefix(t *testing.T) {
|
|||||||
if ckie.Path != expectedPath {
|
if ckie.Path != expectedPath {
|
||||||
t.Errorf("cookie path is wrong, wanted %s, got: %s", expectedPath, ckie.Path)
|
t.Errorf("cookie path is wrong, wanted %s, got: %s", expectedPath, ckie.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
if anubis.BasePrefix != "/not-this-server" {
|
|
||||||
t.Errorf("New should not overwrite anubis.BasePrefix, got %q", anubis.BasePrefix)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBasePrefixUsesServerConfigNotPackageGlobal(t *testing.T) {
|
|
||||||
originalBasePrefix := anubis.BasePrefix
|
|
||||||
anubis.BasePrefix = "/global"
|
|
||||||
t.Cleanup(func() {
|
|
||||||
anubis.BasePrefix = originalBasePrefix
|
|
||||||
})
|
|
||||||
|
|
||||||
pol := loadPolicies(t, "", 4)
|
|
||||||
srv := spawnAnubis(t, Options{
|
|
||||||
Next: http.NewServeMux(),
|
|
||||||
Policy: pol,
|
|
||||||
BasePrefix: "/local/",
|
|
||||||
})
|
|
||||||
|
|
||||||
if anubis.BasePrefix != "/global" {
|
|
||||||
t.Fatalf("New should leave anubis.BasePrefix alone, got %q", anubis.BasePrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/local/.within.website/x/cmd/anubis/api/make-challenge?redir=/", nil)
|
|
||||||
req.Header.Set("X-Real-Ip", "127.0.0.1")
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
|
|
||||||
srv.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
if rr.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected local server prefix route to work, got status %d body %q", rr.Code, rr.Body.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRenderIndexUsesServerBasePrefixNotPackageGlobal(t *testing.T) {
|
|
||||||
originalBasePrefix := anubis.BasePrefix
|
|
||||||
anubis.BasePrefix = "/global"
|
|
||||||
t.Cleanup(func() {
|
|
||||||
anubis.BasePrefix = originalBasePrefix
|
|
||||||
})
|
|
||||||
|
|
||||||
pol := loadPolicies(t, "", 4)
|
|
||||||
srv := spawnAnubis(t, Options{
|
|
||||||
Next: http.NewServeMux(),
|
|
||||||
Policy: pol,
|
|
||||||
BasePrefix: "/local/",
|
|
||||||
})
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/local/protected", nil)
|
|
||||||
req.Header.Set("X-Real-Ip", "127.0.0.1")
|
|
||||||
req.Header.Set("User-Agent", "Mozilla/5.0")
|
|
||||||
req.Header.Set("Accept-Encoding", "gzip")
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
|
|
||||||
srv.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
resp := rr.Result()
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var body strings.Builder
|
|
||||||
reader := io.Reader(resp.Body)
|
|
||||||
if resp.Header.Get("Content-Encoding") == "gzip" {
|
|
||||||
gzipReader, err := gzip.NewReader(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("opening gzip response should not fail: %v", err)
|
|
||||||
}
|
|
||||||
defer gzipReader.Close()
|
|
||||||
reader = gzipReader
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(&body, reader); err != nil {
|
|
||||||
t.Fatalf("reading challenge response should not fail: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
t.Fatalf("expected challenge status %d, got %d body %q", http.StatusOK, resp.StatusCode, body.String())
|
|
||||||
}
|
|
||||||
if !strings.Contains(body.String(), "/local/.within.website/x/cmd/anubis/static/js/main.mjs") {
|
|
||||||
t.Fatalf("expected challenge assets to use server base prefix, body %q", body.String())
|
|
||||||
}
|
|
||||||
if strings.Contains(body.String(), "/global/.within.website") {
|
|
||||||
t.Fatalf("challenge body used package global base prefix: %q", body.String())
|
|
||||||
}
|
|
||||||
if anubis.BasePrefix != "/global" {
|
|
||||||
t.Fatalf("rendering should leave anubis.BasePrefix alone, got %q", anubis.BasePrefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCustomStatusCodes(t *testing.T) {
|
func TestCustomStatusCodes(t *testing.T) {
|
||||||
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
t.Log(r.UserAgent())
|
t.Log(r.UserAgent())
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ func Methods() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IssueInput struct {
|
type IssueInput struct {
|
||||||
BasePrefix string
|
|
||||||
Impressum *config.Impressum
|
Impressum *config.Impressum
|
||||||
Rule *policy.Bot
|
Rule *policy.Bot
|
||||||
Challenge *Challenge
|
Challenge *Challenge
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/TecharoHQ/anubis"
|
||||||
"github.com/TecharoHQ/anubis/lib/challenge"
|
"github.com/TecharoHQ/anubis/lib/challenge"
|
||||||
"github.com/TecharoHQ/anubis/lib/localization"
|
"github.com/TecharoHQ/anubis/lib/localization"
|
||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
@@ -27,7 +28,7 @@ func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := r.URL.Parse(in.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't render page: %w", err)
|
return nil, fmt.Errorf("can't render page: %w", err)
|
||||||
}
|
}
|
||||||
@@ -46,7 +47,7 @@ func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
|
|
||||||
loc := localization.GetLocalizer(r)
|
loc := localization.GetLocalizer(r)
|
||||||
|
|
||||||
result := page(in.BasePrefix, u.String(), in.Rule.Challenge.Difficulty, showMeta, loc)
|
result := page(u.String(), in.Rule.Challenge.Difficulty, showMeta, loc)
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/lib/localization"
|
"github.com/TecharoHQ/anubis/lib/localization"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ page(basePrefix, redir string, difficulty int, showMeta bool, loc *localization.SimpleLocalizer) {
|
templ page(redir string, difficulty int, showMeta bool, loc *localization.SimpleLocalizer) {
|
||||||
<div class="centered-div">
|
<div class="centered-div">
|
||||||
<img id="image" style="width:100%;max-width:256px;" src={ basePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
|
<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={ basePrefix + "/.within.website/x/cmd/anubis/static/img/happy.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">{ loc.T("loading") }</p>
|
<p id="status">{ loc.T("loading") }</p>
|
||||||
<p>{ loc.T("connection_security") }</p>
|
<p>{ loc.T("connection_security") }</p>
|
||||||
if showMeta {
|
if showMeta {
|
||||||
|
|||||||
+5
-5
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/lib/localization"
|
"github.com/TecharoHQ/anubis/lib/localization"
|
||||||
)
|
)
|
||||||
|
|
||||||
func page(basePrefix, redir string, difficulty int, showMeta bool, loc *localization.SimpleLocalizer) templ.Component {
|
func page(redir string, difficulty int, showMeta bool, loc *localization.SimpleLocalizer) templ.Component {
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
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
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
@@ -41,9 +41,9 @@ func page(basePrefix, redir string, difficulty int, showMeta bool, loc *localiza
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var2 string
|
var templ_7745c5c3_Var2 string
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(basePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 12, Col: 158}
|
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))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -54,9 +54,9 @@ func page(basePrefix, redir string, difficulty int, showMeta bool, loc *localiza
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var3 string
|
var templ_7745c5c3_Var3 string
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(basePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version)
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 13, Col: 167}
|
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))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/TecharoHQ/anubis"
|
||||||
"github.com/TecharoHQ/anubis/internal"
|
"github.com/TecharoHQ/anubis/internal"
|
||||||
"github.com/TecharoHQ/anubis/lib/challenge"
|
"github.com/TecharoHQ/anubis/lib/challenge"
|
||||||
"github.com/TecharoHQ/anubis/lib/localization"
|
"github.com/TecharoHQ/anubis/lib/localization"
|
||||||
@@ -42,7 +43,7 @@ func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := r.URL.Parse(in.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't render page: %w", err)
|
return nil, fmt.Errorf("can't render page: %w", err)
|
||||||
}
|
}
|
||||||
@@ -54,7 +55,7 @@ func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
|
|
||||||
loc := localization.GetLocalizer(r)
|
loc := localization.GetLocalizer(r)
|
||||||
|
|
||||||
result := page(in.BasePrefix, u.String(), in.Challenge.RandomData, in.Rule.Challenge.Difficulty, loc)
|
result := page(u.String(), in.Challenge.RandomData, in.Rule.Challenge.Difficulty, loc)
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/lib/localization"
|
"github.com/TecharoHQ/anubis/lib/localization"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ page(basePrefix, redir, challenge string, difficulty int, loc *localization.SimpleLocalizer) {
|
templ page(redir, challenge string, difficulty int, loc *localization.SimpleLocalizer) {
|
||||||
<div class="centered-div">
|
<div class="centered-div">
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<img id="image" style="width:100%;max-width:256px;" src={ basePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
|
<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">{ loc.T("loading") }</p>
|
<p id="status">{ loc.T("loading") }</p>
|
||||||
<p>{ loc.T("connection_security") }</p>
|
<p>{ loc.T("connection_security") }</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,7 +18,7 @@ templ page(basePrefix, redir, challenge string, difficulty int, loc *localizatio
|
|||||||
"difficulty": difficulty,
|
"difficulty": difficulty,
|
||||||
"connection_security_message": loc.T("connection_security"),
|
"connection_security_message": loc.T("connection_security"),
|
||||||
"loading_message": loc.T("loading"),
|
"loading_message": loc.T("loading"),
|
||||||
"pensive_url": basePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version,
|
"pensive_url": anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version,
|
||||||
})
|
})
|
||||||
@templ.ComponentFunc(renderAppJS)
|
@templ.ComponentFunc(renderAppJS)
|
||||||
<noscript>
|
<noscript>
|
||||||
|
|||||||
Generated
+4
-4
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/lib/localization"
|
"github.com/TecharoHQ/anubis/lib/localization"
|
||||||
)
|
)
|
||||||
|
|
||||||
func page(basePrefix, redir, challenge string, difficulty int, loc *localization.SimpleLocalizer) templ.Component {
|
func page(redir, challenge string, difficulty int, loc *localization.SimpleLocalizer) templ.Component {
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
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
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
@@ -39,9 +39,9 @@ func page(basePrefix, redir, challenge string, difficulty int, loc *localization
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var2 string
|
var templ_7745c5c3_Var2 string
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(basePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `preact.templ`, Line: 11, Col: 159}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `preact.templ`, Line: 11, Col: 166}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -83,7 +83,7 @@ func page(basePrefix, redir, challenge string, difficulty int, loc *localization
|
|||||||
"difficulty": difficulty,
|
"difficulty": difficulty,
|
||||||
"connection_security_message": loc.T("connection_security"),
|
"connection_security_message": loc.T("connection_security"),
|
||||||
"loading_message": loc.T("loading"),
|
"loading_message": loc.T("loading"),
|
||||||
"pensive_url": basePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version,
|
"pensive_url": anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version,
|
||||||
}).Render(ctx, templ_7745c5c3_Buffer)
|
}).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func (i *Impl) Setup(mux *http.ServeMux) {}
|
|||||||
|
|
||||||
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *chall.IssueInput) (templ.Component, error) {
|
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *chall.IssueInput) (templ.Component, error) {
|
||||||
loc := localization.GetLocalizer(r)
|
loc := localization.GetLocalizer(r)
|
||||||
return page(in.BasePrefix, loc), nil
|
return page(loc), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
|
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/lib/localization"
|
"github.com/TecharoHQ/anubis/lib/localization"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ page(basePrefix string, localizer *localization.SimpleLocalizer) {
|
templ page(localizer *localization.SimpleLocalizer) {
|
||||||
<div class="centered-div">
|
<div class="centered-div">
|
||||||
<img id="image" style="width:100%;max-width:256px;" src={ basePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
|
<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={ basePrefix + "/.within.website/x/cmd/anubis/static/img/happy.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">{ localizer.T("loading") }</p>
|
<p id="status">{ localizer.T("loading") }</p>
|
||||||
<script async type="module" src={ basePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version }></script>
|
<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 id="progress" role="progressbar" aria-labelledby="status">
|
||||||
<div class="bar-inner"></div>
|
<div class="bar-inner"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+7
-7
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/lib/localization"
|
"github.com/TecharoHQ/anubis/lib/localization"
|
||||||
)
|
)
|
||||||
|
|
||||||
func page(basePrefix string, localizer *localization.SimpleLocalizer) templ.Component {
|
func page(localizer *localization.SimpleLocalizer) templ.Component {
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
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
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
@@ -39,9 +39,9 @@ func page(basePrefix string, localizer *localization.SimpleLocalizer) templ.Comp
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var2 string
|
var templ_7745c5c3_Var2 string
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(basePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 10, Col: 158}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 10, Col: 165}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -52,9 +52,9 @@ func page(basePrefix string, localizer *localization.SimpleLocalizer) templ.Comp
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var3 string
|
var templ_7745c5c3_Var3 string
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(basePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version)
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 11, Col: 167}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 11, Col: 174}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -78,9 +78,9 @@ func page(basePrefix string, localizer *localization.SimpleLocalizer) templ.Comp
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var5 string
|
var templ_7745c5c3_Var5 string
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(basePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version)
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 13, Col: 129}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 13, Col: 136}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|||||||
+20
-14
@@ -55,7 +55,7 @@ type Options struct {
|
|||||||
DifficultyInJWT bool
|
DifficultyInJWT bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int, logLevel string, subrequestMode bool) (*policy.ParsedConfig, error) {
|
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int, logLevel string) (*policy.ParsedConfig, error) {
|
||||||
var fin io.ReadCloser
|
var fin io.ReadCloser
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty
|
|||||||
}
|
}
|
||||||
}(fin)
|
}(fin)
|
||||||
|
|
||||||
anubisPolicy, err := policy.ParseConfig(ctx, fin, fname, defaultDifficulty, logLevel, subrequestMode)
|
anubisPolicy, err := policy.ParseConfig(ctx, fin, fname, defaultDifficulty, logLevel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't parse policy file %s: %w", fname, err)
|
return nil, fmt.Errorf("can't parse policy file %s: %w", fname, err)
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,8 @@ func New(opts Options) (*Server, error) {
|
|||||||
opts.ED25519PrivateKey = priv
|
opts.ED25519PrivateKey = priv
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.BasePrefix = strings.TrimRight(opts.BasePrefix, "/")
|
anubis.BasePrefix = strings.TrimRight(opts.BasePrefix, "/")
|
||||||
|
anubis.PublicUrl = opts.PublicUrl
|
||||||
|
|
||||||
result := &Server{
|
result := &Server{
|
||||||
next: opts.Next,
|
next: opts.Next,
|
||||||
@@ -120,8 +121,6 @@ func New(opts Options) (*Server, error) {
|
|||||||
hs512Secret: opts.HS512Secret,
|
hs512Secret: opts.HS512Secret,
|
||||||
policy: opts.Policy,
|
policy: opts.Policy,
|
||||||
opts: opts,
|
opts: opts,
|
||||||
basePrefix: opts.BasePrefix,
|
|
||||||
publicURL: opts.PublicUrl,
|
|
||||||
OGTags: ogtags.NewOGTagCache(opts.Target, opts.Policy.OpenGraph, opts.Policy.Store, ogtags.TargetOptions{
|
OGTags: ogtags.NewOGTagCache(opts.Target, opts.Policy.OpenGraph, opts.Policy.Store, ogtags.TargetOptions{
|
||||||
Host: opts.TargetHost,
|
Host: opts.TargetHost,
|
||||||
SNI: opts.TargetSNI,
|
SNI: opts.TargetSNI,
|
||||||
@@ -132,20 +131,28 @@ func New(opts Options) (*Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
xessPrefix := result.prefixedPath(xess.BasePrefix)
|
xess.Mount(mux)
|
||||||
mux.Handle(xessPrefix, internal.UnchangingCache(http.StripPrefix(xessPrefix, http.FileServerFS(xess.Static))))
|
|
||||||
|
|
||||||
// Helper to add the server-local base prefix.
|
// Helper to add global prefix
|
||||||
registerWithPrefix := func(pattern string, handler http.Handler, method string) {
|
registerWithPrefix := func(pattern string, handler http.Handler, method string) {
|
||||||
if method != "" {
|
if method != "" {
|
||||||
method = method + " " // methods must end with a space to register with them
|
method = method + " " // methods must end with a space to register with them
|
||||||
}
|
}
|
||||||
|
|
||||||
mux.Handle(method+result.prefixedPath(pattern), handler)
|
// Ensure there's no double slash when concatenating BasePrefix and pattern
|
||||||
|
basePrefix := strings.TrimSuffix(anubis.BasePrefix, "/")
|
||||||
|
prefix := method + basePrefix
|
||||||
|
|
||||||
|
// If pattern doesn't start with a slash, add one
|
||||||
|
if !strings.HasPrefix(pattern, "/") {
|
||||||
|
pattern = "/" + pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
mux.Handle(prefix+pattern, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure there's no double slash when concatenating BasePrefix and StaticPath
|
// Ensure there's no double slash when concatenating BasePrefix and StaticPath
|
||||||
stripPrefix := result.prefixedPath(anubis.StaticPath)
|
stripPrefix := strings.TrimSuffix(anubis.BasePrefix, "/") + anubis.StaticPath
|
||||||
registerWithPrefix(anubis.StaticPath, internal.UnchangingCache(internal.NoBrowsing(http.StripPrefix(stripPrefix, http.FileServerFS(web.Static)))), "")
|
registerWithPrefix(anubis.StaticPath, internal.UnchangingCache(internal.NoBrowsing(http.StripPrefix(stripPrefix, http.FileServerFS(web.Static)))), "")
|
||||||
|
|
||||||
if opts.ServeRobotsTXT {
|
if opts.ServeRobotsTXT {
|
||||||
@@ -159,10 +166,9 @@ func New(opts Options) (*Server, error) {
|
|||||||
|
|
||||||
if opts.Policy.Impressum != nil {
|
if opts.Policy.Impressum != nil {
|
||||||
registerWithPrefix(anubis.APIPrefix+"imprint", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
registerWithPrefix(anubis.APIPrefix+"imprint", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
handler := templ.Handler(
|
templ.Handler(
|
||||||
web.BaseWithOptions(result.renderOptions(), opts.Policy.Impressum.Page.Title, opts.Policy.Impressum.Page, opts.Policy.Impressum, localization.GetLocalizer(r)),
|
web.Base(opts.Policy.Impressum.Page.Title, opts.Policy.Impressum.Page, opts.Policy.Impressum, localization.GetLocalizer(r)),
|
||||||
)
|
).ServeHTTP(w, r)
|
||||||
handler.ServeHTTP(w, r)
|
|
||||||
}), "GET")
|
}), "GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ type Logging struct {
|
|||||||
Sink string `json:"sink"` // Logging sink, either "stdio" or "file"
|
Sink string `json:"sink"` // Logging sink, either "stdio" or "file"
|
||||||
Level *slog.Level `json:"level"` // Log level, if set supersedes the level in flags
|
Level *slog.Level `json:"level"` // Log level, if set supersedes the level in flags
|
||||||
Parameters *LoggingFileConfig `json:"parameters"` // Logging parameters, to be dynamic in the future
|
Parameters *LoggingFileConfig `json:"parameters"` // Logging parameters, to be dynamic in the future
|
||||||
LogASN bool `json:"asn" yaml:"asn"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+4
-4
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestInvalidChallengeMethod(t *testing.T) {
|
func TestInvalidChallengeMethod(t *testing.T) {
|
||||||
if _, err := LoadPoliciesOrDefault(t.Context(), "testdata/invalid-challenge-method.yaml", 4, "info", false); !errors.Is(err, policy.ErrChallengeRuleHasWrongAlgorithm) {
|
if _, err := LoadPoliciesOrDefault(t.Context(), "testdata/invalid-challenge-method.yaml", 4, "info"); !errors.Is(err, policy.ErrChallengeRuleHasWrongAlgorithm) {
|
||||||
t.Fatalf("wanted error %v but got %v", policy.ErrChallengeRuleHasWrongAlgorithm, err)
|
t.Fatalf("wanted error %v but got %v", policy.ErrChallengeRuleHasWrongAlgorithm, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ func TestBadConfigs(t *testing.T) {
|
|||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "bad", st.Name()), anubis.DefaultDifficulty, "info", false); err == nil {
|
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "bad", st.Name()), anubis.DefaultDifficulty, "info"); err == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
@@ -44,13 +44,13 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
t.Run("with-thoth", func(t *testing.T) {
|
t.Run("with-thoth", func(t *testing.T) {
|
||||||
ctx := thothmock.WithMockThoth(t)
|
ctx := thothmock.WithMockThoth(t)
|
||||||
if _, err := LoadPoliciesOrDefault(ctx, filepath.Join("config", "testdata", "good", st.Name()), anubis.DefaultDifficulty, "info", false); err != nil {
|
if _, err := LoadPoliciesOrDefault(ctx, filepath.Join("config", "testdata", "good", st.Name()), anubis.DefaultDifficulty, "info"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("without-thoth", func(t *testing.T) {
|
t.Run("without-thoth", func(t *testing.T) {
|
||||||
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "good", st.Name()), anubis.DefaultDifficulty, "info", false); err != nil {
|
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "good", st.Name()), anubis.DefaultDifficulty, "info"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+23
-43
@@ -193,7 +193,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
localizer := localization.GetLocalizer(r)
|
localizer := localization.GetLocalizer(r)
|
||||||
|
|
||||||
if returnHTTPStatusOnly {
|
if returnHTTPStatusOnly {
|
||||||
if s.configuredPublicURL() == "" {
|
if s.opts.PublicUrl == "" {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
w.Write([]byte(localizer.T("authorization_required")))
|
w.Write([]byte(localizer.T("authorization_required")))
|
||||||
} else {
|
} else {
|
||||||
@@ -207,7 +207,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lg, r := s.getRequestLogger(r)
|
lg := internal.GetRequestLogger(s.logger, r)
|
||||||
|
|
||||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && randomChance(64) {
|
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")
|
lg.Error("client was given a challenge but does not in fact support gzip compression")
|
||||||
@@ -215,10 +215,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
challengesIssued.WithLabelValues("embedded").Add(1)
|
||||||
asn, asnDesc := asnFromContext(r.Context())
|
|
||||||
challengesIssued.WithLabelValues("embedded", asn, asnDesc).Add(1)
|
|
||||||
}
|
|
||||||
chall, err := s.issueChallenge(r.Context(), r, lg, cr, rule)
|
chall, err := s.issueChallenge(r.Context(), r, lg, cr, rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Error("can't get challenge", "err", err)
|
lg.Error("can't get challenge", "err", err)
|
||||||
@@ -263,7 +260,6 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
in := &challenge.IssueInput{
|
in := &challenge.IssueInput{
|
||||||
BasePrefix: s.configuredBasePrefix(),
|
|
||||||
Impressum: s.policy.Impressum,
|
Impressum: s.policy.Impressum,
|
||||||
Rule: rule,
|
Rule: rule,
|
||||||
Challenge: chall,
|
Challenge: chall,
|
||||||
@@ -278,8 +274,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
page := web.BaseWithChallengeAndOGTagsWithOptions(
|
page := web.BaseWithChallengeAndOGTags(
|
||||||
s.renderOptions(),
|
|
||||||
localizer.T("making_sure_not_bot"),
|
localizer.T("making_sure_not_bot"),
|
||||||
component,
|
component,
|
||||||
s.policy.Impressum,
|
s.policy.Impressum,
|
||||||
@@ -311,31 +306,29 @@ func (s *Server) constructRedirectURL(r *http.Request) (string, error) {
|
|||||||
case "http", "https":
|
case "http", "https":
|
||||||
// allowed
|
// allowed
|
||||||
default:
|
default:
|
||||||
lg, _ := s.getRequestLogger(r)
|
lg := internal.GetRequestLogger(s.logger, r)
|
||||||
lg.Warn("invalid protocol in X-Forwarded-Proto", "proto", proto)
|
lg.Warn("invalid protocol in X-Forwarded-Proto", "proto", proto)
|
||||||
return "", errors.New(localizer.T("invalid_redirect"))
|
return "", errors.New(localizer.T("invalid_redirect"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if host is allowed in RedirectDomains (supports '*' via glob)
|
// Check if host is allowed in RedirectDomains (supports '*' via glob)
|
||||||
if len(s.opts.RedirectDomains) > 0 && !matchRedirectDomain(s.opts.RedirectDomains, host) {
|
if len(s.opts.RedirectDomains) > 0 && !matchRedirectDomain(s.opts.RedirectDomains, host) {
|
||||||
lg, _ := s.getRequestLogger(r)
|
lg := internal.GetRequestLogger(s.logger, r)
|
||||||
lg.Debug("domain not allowed", "domain", host)
|
lg.Debug("domain not allowed", "domain", host)
|
||||||
return "", errors.New(localizer.T("redirect_domain_not_allowed"))
|
return "", errors.New(localizer.T("redirect_domain_not_allowed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
redir := proto + "://" + host + uri
|
redir := proto + "://" + host + uri
|
||||||
escapedURL := url.QueryEscape(redir)
|
escapedURL := url.QueryEscape(redir)
|
||||||
return fmt.Sprintf("%s/.within.website/?redir=%s", s.configuredPublicURL(), escapedURL), nil
|
return fmt.Sprintf("%s/.within.website/?redir=%s", s.opts.PublicUrl, escapedURL), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RenderBench(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) RenderBench(w http.ResponseWriter, r *http.Request) {
|
||||||
localizer := localization.GetLocalizer(r)
|
localizer := localization.GetLocalizer(r)
|
||||||
opts := s.renderOptions()
|
|
||||||
|
|
||||||
handler := templ.Handler(
|
templ.Handler(
|
||||||
web.BaseWithOptions(opts, localizer.T("benchmarking_anubis"), web.BenchWithOptions(opts, localizer), s.policy.Impressum, localizer),
|
web.Base(localizer.T("benchmarking_anubis"), web.Bench(localizer), s.policy.Impressum, localizer),
|
||||||
)
|
).ServeHTTP(w, r)
|
||||||
handler.ServeHTTP(w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) respondWithError(w http.ResponseWriter, r *http.Request, message, code string) {
|
func (s *Server) respondWithError(w http.ResponseWriter, r *http.Request, message, code string) {
|
||||||
@@ -344,12 +337,10 @@ func (s *Server) respondWithError(w http.ResponseWriter, r *http.Request, messag
|
|||||||
|
|
||||||
func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg, code string, status int) {
|
func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg, code string, status int) {
|
||||||
localizer := localization.GetLocalizer(r)
|
localizer := localization.GetLocalizer(r)
|
||||||
opts := s.renderOptions()
|
|
||||||
|
|
||||||
component := web.BaseWithOptions(
|
component := web.Base(
|
||||||
opts,
|
|
||||||
localizer.T("oh_noes"),
|
localizer.T("oh_noes"),
|
||||||
web.ErrorPageWithOptions(opts, msg, s.opts.WebmasterEmail, code, localizer),
|
web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer),
|
||||||
s.policy.Impressum,
|
s.policy.Impressum,
|
||||||
localizer,
|
localizer,
|
||||||
)
|
)
|
||||||
@@ -358,25 +349,17 @@ func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if strings.HasPrefix(r.URL.Path, s.prefixedPath(anubis.StaticPath)) {
|
if strings.HasPrefix(r.URL.Path, anubis.BasePrefix+anubis.StaticPath) {
|
||||||
s.mux.ServeHTTP(w, r)
|
s.mux.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
} else if strings.HasPrefix(r.URL.Path, s.prefixedPath(xess.BasePrefix)) {
|
} else if strings.HasPrefix(r.URL.Path, anubis.BasePrefix+xess.BasePrefix) {
|
||||||
s.mux.ServeHTTP(w, r)
|
s.mux.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward robots.txt requests to mux when ServeRobotsTXT is enabled
|
// Forward robots.txt requests to mux when ServeRobotsTXT is enabled
|
||||||
if s.opts.ServeRobotsTXT {
|
if s.opts.ServeRobotsTXT {
|
||||||
path := r.URL.Path
|
path := strings.TrimPrefix(r.URL.Path, anubis.BasePrefix)
|
||||||
basePrefix := s.configuredBasePrefix()
|
|
||||||
if basePrefix != "" {
|
|
||||||
if !strings.HasPrefix(path, basePrefix) {
|
|
||||||
s.maybeReverseProxyOrPage(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
path = strings.TrimPrefix(path, basePrefix)
|
|
||||||
}
|
|
||||||
if path == "/robots.txt" || path == "/.well-known/robots.txt" {
|
if path == "/robots.txt" || path == "/.well-known/robots.txt" {
|
||||||
s.mux.ServeHTTP(w, r)
|
s.mux.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
@@ -387,11 +370,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) stripBasePrefixFromRequest(r *http.Request) *http.Request {
|
func (s *Server) stripBasePrefixFromRequest(r *http.Request) *http.Request {
|
||||||
basePrefix := s.configuredBasePrefix()
|
if !s.opts.StripBasePrefix || s.opts.BasePrefix == "" {
|
||||||
if !s.opts.StripBasePrefix || basePrefix == "" {
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
basePrefix := strings.TrimSuffix(s.opts.BasePrefix, "/")
|
||||||
path := r.URL.Path
|
path := r.URL.Path
|
||||||
|
|
||||||
if !strings.HasPrefix(path, basePrefix) {
|
if !strings.HasPrefix(path, basePrefix) {
|
||||||
@@ -432,7 +415,7 @@ func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) {
|
|||||||
case "", "http", "https":
|
case "", "http", "https":
|
||||||
// allowed: empty scheme means relative URL
|
// allowed: empty scheme means relative URL
|
||||||
default:
|
default:
|
||||||
lg, _ := s.getRequestLogger(r)
|
lg := internal.GetRequestLogger(s.logger, r)
|
||||||
lg.Warn("XSS attempt blocked, invalid redirect scheme", "scheme", urlParsed.Scheme, "redir", redir)
|
lg.Warn("XSS attempt blocked, invalid redirect scheme", "scheme", urlParsed.Scheme, "redir", redir)
|
||||||
s.respondWithStatus(w, r, localizer.T("invalid_redirect"), "", http.StatusBadRequest)
|
s.respondWithStatus(w, r, localizer.T("invalid_redirect"), "", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -444,7 +427,7 @@ func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) {
|
|||||||
hostMismatch := r.URL.Host != "" && urlParsed.Host != "" && urlParsed.Host != r.URL.Host
|
hostMismatch := r.URL.Host != "" && urlParsed.Host != "" && urlParsed.Host != r.URL.Host
|
||||||
|
|
||||||
if hostNotAllowed || hostMismatch {
|
if hostNotAllowed || hostMismatch {
|
||||||
lg, _ := s.getRequestLogger(r)
|
lg := internal.GetRequestLogger(s.logger, r)
|
||||||
lg.Debug("domain not allowed", "domain", urlParsed.Host)
|
lg.Debug("domain not allowed", "domain", urlParsed.Host)
|
||||||
s.respondWithStatus(w, r, localizer.T("redirect_domain_not_allowed"), makeCode(err), http.StatusBadRequest)
|
s.respondWithStatus(w, r, localizer.T("redirect_domain_not_allowed"), makeCode(err), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -455,14 +438,11 @@ func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := s.renderOptions()
|
templ.Handler(
|
||||||
handler := templ.Handler(
|
web.Base(localizer.T("you_are_not_a_bot"), web.StaticHappy(localizer), s.policy.Impressum, localizer),
|
||||||
web.BaseWithOptions(opts, localizer.T("you_are_not_a_bot"), web.StaticHappyWithOptions(opts, localizer), s.policy.Impressum, localizer),
|
).ServeHTTP(w, r)
|
||||||
)
|
|
||||||
handler.ServeHTTP(w, r)
|
|
||||||
} else {
|
} else {
|
||||||
asn, asnDesc := asnFromContext(r.Context())
|
requestsProxied.WithLabelValues(r.Host).Inc()
|
||||||
requestsProxied.WithLabelValues(r.Host, asn, asnDesc).Inc()
|
|
||||||
r = s.stripBasePrefixFromRequest(r)
|
r = s.stripBasePrefixFromRequest(r)
|
||||||
s.next.ServeHTTP(w, r)
|
s.next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func (s *Server) run(ctx context.Context, lg *slog.Logger) error {
|
|||||||
ErrorLog: internal.GetFilteredHTTPLogger(),
|
ErrorLog: internal.GetFilteredHTTPLogger(),
|
||||||
}
|
}
|
||||||
|
|
||||||
ln, metricsURL, err := internal.SetupListener(s.Config.Network, s.Config.Bind, s.Config.SocketMode)
|
ln, metricsURL, err := internal.SetupListener(s.Config.Bind, s.Config.Network, s.Config.SocketMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't setup listener: %w", err)
|
return fmt.Errorf("can't setup listener: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ import (
|
|||||||
type CELChecker struct {
|
type CELChecker struct {
|
||||||
program cel.Program
|
program cel.Program
|
||||||
src string
|
src string
|
||||||
subRequestMode bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCELChecker(cfg *config.ExpressionOrList, dnsObj *dns.Dns, subRequestMode bool) (*CELChecker, error) {
|
func NewCELChecker(cfg *config.ExpressionOrList, dnsObj *dns.Dns) (*CELChecker, error) {
|
||||||
env, err := expressions.BotEnvironment(dnsObj)
|
env, err := expressions.BotEnvironment(dnsObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -32,7 +31,6 @@ func NewCELChecker(cfg *config.ExpressionOrList, dnsObj *dns.Dns, subRequestMode
|
|||||||
return &CELChecker{
|
return &CELChecker{
|
||||||
src: cfg.String(),
|
src: cfg.String(),
|
||||||
program: program,
|
program: program,
|
||||||
subRequestMode: subRequestMode,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +39,7 @@ func (cc *CELChecker) Hash() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cc *CELChecker) Check(r *http.Request) (bool, error) {
|
func (cc *CELChecker) Check(r *http.Request) (bool, error) {
|
||||||
result, _, err := cc.program.ContextEval(r.Context(), &CELRequest{r, cc.subRequestMode})
|
result, _, err := cc.program.ContextEval(r.Context(), &CELRequest{r})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -56,7 +54,6 @@ func (cc *CELChecker) Check(r *http.Request) (bool, error) {
|
|||||||
|
|
||||||
type CELRequest struct {
|
type CELRequest struct {
|
||||||
*http.Request
|
*http.Request
|
||||||
subRequestMode bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr *CELRequest) Parent() cel.Activation { return nil }
|
func (cr *CELRequest) Parent() cel.Activation { return nil }
|
||||||
@@ -74,14 +71,6 @@ func (cr *CELRequest) ResolveName(name string) (any, bool) {
|
|||||||
case "userAgent":
|
case "userAgent":
|
||||||
return cr.UserAgent(), true
|
return cr.UserAgent(), true
|
||||||
case "path":
|
case "path":
|
||||||
if cr.subRequestMode {
|
|
||||||
if xou := cr.Header.Get("X-Original-URI"); xou != "" {
|
|
||||||
return xou, true
|
|
||||||
}
|
|
||||||
if xfu := cr.Header.Get("X-Forwarded-Uri"); xfu != "" {
|
|
||||||
return xfu, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cr.URL.Path, true
|
return cr.URL.Path, true
|
||||||
case "query":
|
case "query":
|
||||||
return expressions.URLValues{Values: cr.URL.Query()}, true
|
return expressions.URLValues{Values: cr.URL.Query()}, true
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestCELChecker_MapIterationWrappers(t *testing.T) {
|
|||||||
Expression: `headers.exists(k, k == "Accept") && query.exists(k, k == "format")`,
|
Expression: `headers.exists(k, k == "Accept") && query.exists(k, k == "format")`,
|
||||||
}
|
}
|
||||||
|
|
||||||
checker, err := NewCELChecker(cfg, newTestDNS(t), false)
|
checker, err := NewCELChecker(cfg, newTestDNS(t))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("creating CEL checker failed: %v", err)
|
t.Fatalf("creating CEL checker failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -42,77 +42,3 @@ func TestCELChecker_MapIterationWrappers(t *testing.T) {
|
|||||||
t.Fatal("expected expression to evaluate true")
|
t.Fatal("expected expression to evaluate true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCELChecker_PathWithForwardedUri(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expression string
|
|
||||||
xForwardedUri string
|
|
||||||
urlPath string
|
|
||||||
subRequestMode bool
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "path matches X-Forwarded-Uri in subrequest mode",
|
|
||||||
expression: `path.startsWith("/admin")`,
|
|
||||||
xForwardedUri: "/admin/secret",
|
|
||||||
urlPath: "/.within.website/x/cmd/anubis/api/check",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "path with query string",
|
|
||||||
expression: `path.startsWith("/api/secret")`,
|
|
||||||
xForwardedUri: "/api/secret?token=abc",
|
|
||||||
urlPath: "/.within.website/x/cmd/anubis/api/check",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "path falls back to url path when no header",
|
|
||||||
expression: `path == "/public/page"`,
|
|
||||||
urlPath: "/public/page",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-subrequest mode ignores X-Forwarded-Uri",
|
|
||||||
expression: `path.startsWith("/admin")`,
|
|
||||||
xForwardedUri: "/admin/secret",
|
|
||||||
urlPath: "/public/page",
|
|
||||||
subRequestMode: false,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
cfg := &config.ExpressionOrList{
|
|
||||||
Expression: tt.expression,
|
|
||||||
}
|
|
||||||
checker, err := NewCELChecker(cfg, newTestDNS(t), tt.subRequestMode)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("NewCELChecker() error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://example.com"+tt.urlPath, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("http.NewRequest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.xForwardedUri != "" {
|
|
||||||
req.Header.Set("X-Forwarded-Uri", tt.xForwardedUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := checker.Check(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Check() error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Check() = %v, want %v (subRequestMode=%v, urlPath=%q, X-Forwarded-Uri=%q)",
|
|
||||||
got, tt.want, tt.subRequestMode, tt.urlPath, tt.xForwardedUri)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -96,29 +96,23 @@ func (hmc *HeaderMatchesChecker) Hash() string {
|
|||||||
type PathChecker struct {
|
type PathChecker struct {
|
||||||
regexp *regexp.Regexp
|
regexp *regexp.Regexp
|
||||||
hash string
|
hash string
|
||||||
subRequestMode bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPathChecker(rexStr string, subrequestMode bool) (checker.Impl, error) {
|
func NewPathChecker(rexStr string) (checker.Impl, error) {
|
||||||
rex, err := regexp.Compile(strings.TrimSpace(rexStr))
|
rex, err := regexp.Compile(strings.TrimSpace(rexStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%w: regex %s failed parse: %w", ErrMisconfiguration, rexStr, err)
|
return nil, fmt.Errorf("%w: regex %s failed parse: %w", ErrMisconfiguration, rexStr, err)
|
||||||
}
|
}
|
||||||
return &PathChecker{rex, internal.FastHash(rexStr), subrequestMode}, nil
|
return &PathChecker{rex, internal.FastHash(rexStr)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc *PathChecker) Check(r *http.Request) (bool, error) {
|
func (pc *PathChecker) Check(r *http.Request) (bool, error) {
|
||||||
if pc.subRequestMode {
|
|
||||||
originalUrl := r.Header.Get("X-Original-URI")
|
originalUrl := r.Header.Get("X-Original-URI")
|
||||||
if originalUrl == "" {
|
|
||||||
originalUrl = r.Header.Get("X-Forwarded-Uri")
|
|
||||||
}
|
|
||||||
if originalUrl != "" {
|
if originalUrl != "" {
|
||||||
if pc.regexp.MatchString(originalUrl) {
|
if pc.regexp.MatchString(originalUrl) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if pc.regexp.MatchString(r.URL.Path) {
|
if pc.regexp.MatchString(r.URL.Path) {
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|||||||
+2
-223
@@ -272,8 +272,8 @@ func TestPathChecker_XOriginalURI(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Create the PathChecker in subrequest mode so X-Original-URI is honored.
|
// Create the PathChecker
|
||||||
pc, err := NewPathChecker(tt.regex, true)
|
pc, err := NewPathChecker(tt.regex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !tt.expectError {
|
if !tt.expectError {
|
||||||
t.Fatalf("NewPathChecker() unexpected error: %v", err)
|
t.Fatalf("NewPathChecker() unexpected error: %v", err)
|
||||||
@@ -305,224 +305,3 @@ func TestPathChecker_XOriginalURI(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPathChecker_GHSA_6wcg_mqvh_fcvg is a regression test for
|
|
||||||
// https://github.com/TecharoHQ/anubis/security/advisories/GHSA-6wcg-mqvh-fcvg.
|
|
||||||
//
|
|
||||||
// PR https://github.com/TecharoHQ/anubis/pull/1015 added the ability for
|
|
||||||
// reverse proxies using Anubis in subrequest auth mode to look at the path
|
|
||||||
// of a request as there are many rules in the wild that rely on checking
|
|
||||||
// the path. This is how access to things like robots.txt or anything in the
|
|
||||||
// .well-known directory is unaffected by Anubis.
|
|
||||||
//
|
|
||||||
// However this logic was also enabled for non-subrequest deployments of Anubis,
|
|
||||||
// meaning that a specially crafted request could include a /.well-known/
|
|
||||||
// path in it and then get around Anubis with little effort.
|
|
||||||
//
|
|
||||||
// This fix gates the logic behind a new plumbed variable named subrequestMode
|
|
||||||
// that only fires when Anubis is running in subrequest auth mode. This
|
|
||||||
// properly contains that workaround so that the logic does not fire in
|
|
||||||
// most deployments.
|
|
||||||
func TestPathChecker_GHSA_6wcg_mqvh_fcvg(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
regex string
|
|
||||||
urlPath string
|
|
||||||
xOriginalURI string
|
|
||||||
subRequestMode bool
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "default mode ignores spoofed X-Original-URI when real path matches",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
urlPath: "/admin/secret",
|
|
||||||
xOriginalURI: "/public/index",
|
|
||||||
subRequestMode: false,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default mode ignores spoofed X-Original-URI when real path does not match",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
urlPath: "/public/index",
|
|
||||||
xOriginalURI: "/admin/secret",
|
|
||||||
subRequestMode: false,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default mode without X-Original-URI matches real path",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
urlPath: "/admin/dashboard",
|
|
||||||
xOriginalURI: "",
|
|
||||||
subRequestMode: false,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "subrequest mode honors X-Original-URI",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
urlPath: "/auth",
|
|
||||||
xOriginalURI: "/admin/secret",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "subrequest mode falls back to URL.Path when X-Original-URI does not match",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
urlPath: "/admin/dashboard",
|
|
||||||
xOriginalURI: "/public/index",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "subrequest mode with empty X-Original-URI uses URL.Path",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
urlPath: "/admin/dashboard",
|
|
||||||
xOriginalURI: "",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
pc, err := NewPathChecker(tt.regex, tt.subRequestMode)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("NewPathChecker(%q, %v) returned error: %v", tt.regex, tt.subRequestMode, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://example.com"+tt.urlPath, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("http.NewRequest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.xOriginalURI != "" {
|
|
||||||
req.Header.Set("X-Original-URI", tt.xOriginalURI)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := pc.Check(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Check() unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Check() = %v, want %v (subRequestMode=%v, urlPath=%q, X-Original-URI=%q)",
|
|
||||||
got, tt.want, tt.subRequestMode, tt.urlPath, tt.xOriginalURI)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathChecker_XForwardedUri(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
regex string
|
|
||||||
xForwardedUri string
|
|
||||||
xOriginalURI string
|
|
||||||
urlPath string
|
|
||||||
subRequestMode bool
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "X-Forwarded-Uri matches regex in subrequest mode",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
xForwardedUri: "/admin/users",
|
|
||||||
urlPath: "/.within.website/x/cmd/anubis/api/check",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "X-Forwarded-Uri with query string",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
xForwardedUri: "/admin/users?page=1",
|
|
||||||
urlPath: "/.within.website/x/cmd/anubis/api/check",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "X-Original-URI takes priority over X-Forwarded-Uri",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
xForwardedUri: "/public/page",
|
|
||||||
xOriginalURI: "/admin/users",
|
|
||||||
urlPath: "/.within.website/x/cmd/anubis/api/check",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "falls back to X-Forwarded-Uri when no X-Original-URI",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
xForwardedUri: "/admin/dashboard",
|
|
||||||
urlPath: "/.within.website/x/cmd/anubis/api/check",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "neither header matches, url path matches",
|
|
||||||
regex: "^/public/.*",
|
|
||||||
xForwardedUri: "/admin/users",
|
|
||||||
urlPath: "/public/page",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nothing matches",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
xForwardedUri: "/public/page",
|
|
||||||
urlPath: "/.within.website/x/cmd/anubis/api/check",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-subrequest mode ignores X-Forwarded-Uri",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
xForwardedUri: "/admin/users",
|
|
||||||
urlPath: "/public/page",
|
|
||||||
subRequestMode: false,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-subrequest mode uses url path",
|
|
||||||
regex: "^/admin/.*",
|
|
||||||
xForwardedUri: "/public/page",
|
|
||||||
urlPath: "/admin/secret",
|
|
||||||
subRequestMode: false,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty X-Forwarded-Uri falls back to url path",
|
|
||||||
regex: "^/check$",
|
|
||||||
urlPath: "/check",
|
|
||||||
subRequestMode: true,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
pc, err := NewPathChecker(tt.regex, tt.subRequestMode)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("NewPathChecker(%q, %v) returned error: %v", tt.regex, tt.subRequestMode, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://example.com"+tt.urlPath, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("http.NewRequest: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.xForwardedUri != "" {
|
|
||||||
req.Header.Set("X-Forwarded-Uri", tt.xForwardedUri)
|
|
||||||
}
|
|
||||||
if tt.xOriginalURI != "" {
|
|
||||||
req.Header.Set("X-Original-URI", tt.xOriginalURI)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := pc.Check(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Check() unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Check() = %v, want %v (subRequestMode=%v, urlPath=%q, X-Forwarded-Uri=%q, X-Original-URI=%q)",
|
|
||||||
got, tt.want, tt.subRequestMode, tt.urlPath, tt.xForwardedUri, tt.xOriginalURI)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-14
@@ -27,7 +27,7 @@ var (
|
|||||||
Applications = promauto.NewCounterVec(prometheus.CounterOpts{
|
Applications = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Name: "anubis_policy_results",
|
Name: "anubis_policy_results",
|
||||||
Help: "The results of each policy rule",
|
Help: "The results of each policy rule",
|
||||||
}, []string{"rule", "action", "asn", "asn_description"})
|
}, []string{"rule", "action"})
|
||||||
|
|
||||||
ErrChallengeRuleHasWrongAlgorithm = errors.New("config.Bot.ChallengeRules: algorithm is invalid")
|
ErrChallengeRuleHasWrongAlgorithm = errors.New("config.Bot.ChallengeRules: algorithm is invalid")
|
||||||
warnedAboutThresholds = &atomic.Bool{}
|
warnedAboutThresholds = &atomic.Bool{}
|
||||||
@@ -47,8 +47,6 @@ type ParsedConfig struct {
|
|||||||
Dns *dns.Dns
|
Dns *dns.Dns
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
Metrics *config.Metrics
|
Metrics *config.Metrics
|
||||||
ThothClient *thoth.Client
|
|
||||||
LogASN bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newParsedConfig(orig *config.Config) *ParsedConfig {
|
func newParsedConfig(orig *config.Config) *ParsedConfig {
|
||||||
@@ -60,7 +58,7 @@ func newParsedConfig(orig *config.Config) *ParsedConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDifficulty int, logLevel string, subrequestMode bool) (*ParsedConfig, error) {
|
func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDifficulty int, logLevel string) (*ParsedConfig, error) {
|
||||||
c, err := config.Load(fin, fname)
|
c, err := config.Load(fin, fname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -72,10 +70,6 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
|
|||||||
|
|
||||||
result := newParsedConfig(c)
|
result := newParsedConfig(c)
|
||||||
result.DefaultDifficulty = defaultDifficulty
|
result.DefaultDifficulty = defaultDifficulty
|
||||||
result.LogASN = c.Logging.LogASN
|
|
||||||
if hasThothClient {
|
|
||||||
result.ThothClient = tc
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Logging.Level != nil {
|
if c.Logging.Level != nil {
|
||||||
logLevel = c.Logging.Level.String()
|
logLevel = c.Logging.Level.String()
|
||||||
@@ -100,10 +94,6 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
|
|||||||
|
|
||||||
lg := result.Logger.With("at", "config-validate")
|
lg := result.Logger.With("at", "config-validate")
|
||||||
|
|
||||||
if result.LogASN && !hasThothClient {
|
|
||||||
lg.Warn("logging.asn is enabled but no Thoth client is configured; ASN logging and metrics will be skipped. Please read https://anubis.techaro.lol/docs/admin/thoth for more information")
|
|
||||||
}
|
|
||||||
|
|
||||||
stFac, ok := store.Get(c.Store.Backend)
|
stFac, ok := store.Get(c.Store.Backend)
|
||||||
switch ok {
|
switch ok {
|
||||||
case true:
|
case true:
|
||||||
@@ -152,7 +142,7 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.PathRegex != nil {
|
if b.PathRegex != nil {
|
||||||
c, err := NewPathChecker(*b.PathRegex, subrequestMode)
|
c, err := NewPathChecker(*b.PathRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s path regex: %w", b.Name, err))
|
validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s path regex: %w", b.Name, err))
|
||||||
} else {
|
} else {
|
||||||
@@ -170,7 +160,7 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.Expression != nil {
|
if b.Expression != nil {
|
||||||
c, err := NewCELChecker(b.Expression, result.Dns, subrequestMode)
|
c, err := NewCELChecker(b.Expression, result.Dns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s expressions: %w", b.Name, err))
|
validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s expressions: %w", b.Name, err))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestDefaultPolicyMustParse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer fin.Close()
|
defer fin.Close()
|
||||||
|
|
||||||
if _, err := ParseConfig(ctx, fin, "botPolicies.yaml", anubis.DefaultDifficulty, "info", false); err != nil {
|
if _, err := ParseConfig(ctx, fin, "botPolicies.yaml", anubis.DefaultDifficulty, "info"); err != nil {
|
||||||
t.Fatalf("can't parse config: %v", err)
|
t.Fatalf("can't parse config: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
defer fin.Close()
|
defer fin.Close()
|
||||||
|
|
||||||
ctx := thothmock.WithMockThoth(t)
|
ctx := thothmock.WithMockThoth(t)
|
||||||
if _, err := ParseConfig(ctx, fin, fin.Name(), anubis.DefaultDifficulty, "info", false); err != nil {
|
if _, err := ParseConfig(ctx, fin, fin.Name(), anubis.DefaultDifficulty, "info"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -53,7 +53,7 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer fin.Close()
|
defer fin.Close()
|
||||||
|
|
||||||
if _, err := ParseConfig(t.Context(), fin, fin.Name(), anubis.DefaultDifficulty, "info", false); err != nil {
|
if _, err := ParseConfig(t.Context(), fin, fin.Name(), anubis.DefaultDifficulty, "info"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -77,7 +77,7 @@ func TestBadConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer fin.Close()
|
defer fin.Close()
|
||||||
|
|
||||||
if _, err := ParseConfig(ctx, fin, fin.Name(), anubis.DefaultDifficulty, "info", false); err == nil {
|
if _, err := ParseConfig(ctx, fin, fin.Name(), anubis.DefaultDifficulty, "info"); err == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
|
|||||||
Generated
+537
-622
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -20,11 +20,11 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^21.0.1",
|
"@commitlint/cli": "^20.5.0",
|
||||||
"@commitlint/config-conventional": "^21.0.1",
|
"@commitlint/config-conventional": "^20.5.0",
|
||||||
"baseline-browser-mapping": "^2.10.30",
|
"baseline-browser-mapping": "^2.10.15",
|
||||||
"cssnano": "^8.0.1",
|
"cssnano": "^7.1.4",
|
||||||
"cssnano-preset-advanced": "^8.0.1",
|
"cssnano-preset-advanced": "^7.0.12",
|
||||||
"esbuild": "^0.28.0",
|
"esbuild": "^0.28.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"playwright": "^1.52.0",
|
"playwright": "^1.52.0",
|
||||||
@@ -32,11 +32,11 @@
|
|||||||
"postcss-import": "^16.1.1",
|
"postcss-import": "^16.1.1",
|
||||||
"postcss-import-url": "^7.2.0",
|
"postcss-import-url": "^7.2.0",
|
||||||
"postcss-url": "^10.1.3",
|
"postcss-url": "^10.1.3",
|
||||||
"prettier": "^3.8.3"
|
"prettier": "^3.8.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-crypto/sha256-js": "^5.2.0",
|
"@aws-crypto/sha256-js": "^5.2.0",
|
||||||
"preact": "^10.29.2"
|
"preact": "^10.29.1"
|
||||||
},
|
},
|
||||||
"commitlint": {
|
"commitlint": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
bots:
|
|
||||||
- name: block-admin-via-regex
|
|
||||||
path_regex: ^/admin(/.*)?$
|
|
||||||
action: DENY
|
|
||||||
|
|
||||||
- name: block-secret-via-cel
|
|
||||||
expression:
|
|
||||||
all:
|
|
||||||
- 'path.startsWith("/api/secret")'
|
|
||||||
action: DENY
|
|
||||||
|
|
||||||
- import: (data)/meta/default-config.yaml
|
|
||||||
|
|
||||||
status_codes:
|
|
||||||
CHALLENGE: 200
|
|
||||||
DENY: 403
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
services:
|
|
||||||
traefik:
|
|
||||||
image: traefik:v3.3
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- 8080:80
|
|
||||||
volumes:
|
|
||||||
- ./traefik.yml:/etc/traefik/traefik.yml:ro
|
|
||||||
- ./http.yaml:/config/http.yaml:ro
|
|
||||||
|
|
||||||
anubis:
|
|
||||||
image: ko.local/anubis
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
BIND: ":8080"
|
|
||||||
TARGET: " "
|
|
||||||
POLICY_FNAME: /etc/techaro/anubis.yaml
|
|
||||||
PUBLIC_URL: http://localhost:8080/.within.website/x/cmd/anubis
|
|
||||||
COOKIE_DOMAIN: localhost
|
|
||||||
USE_REMOTE_ADDRESS: "true"
|
|
||||||
volumes:
|
|
||||||
- ./anubis.yaml:/etc/techaro/anubis.yaml
|
|
||||||
|
|
||||||
backend:
|
|
||||||
image: ghcr.io/xe/x/httpdebug
|
|
||||||
pull_policy: always
|
|
||||||
restart: always
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
http:
|
|
||||||
middlewares:
|
|
||||||
anubis:
|
|
||||||
forwardAuth:
|
|
||||||
address: http://anubis:8080/.within.website/x/cmd/anubis/api/check
|
|
||||||
trustForwardHeader: true
|
|
||||||
|
|
||||||
routers:
|
|
||||||
anubis-assets:
|
|
||||||
rule: Host(`localhost`) && PathPrefix(`/.within.website/x/cmd/anubis`)
|
|
||||||
entryPoints:
|
|
||||||
- web
|
|
||||||
service: anubis
|
|
||||||
backend:
|
|
||||||
rule: Host(`localhost`)
|
|
||||||
entryPoints:
|
|
||||||
- web
|
|
||||||
service: backend
|
|
||||||
middlewares:
|
|
||||||
- anubis
|
|
||||||
|
|
||||||
services:
|
|
||||||
anubis:
|
|
||||||
loadBalancer:
|
|
||||||
servers:
|
|
||||||
- url: http://anubis:8080
|
|
||||||
backend:
|
|
||||||
loadBalancer:
|
|
||||||
servers:
|
|
||||||
- url: http://backend:3000
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Smoke test for https://github.com/TecharoHQ/anubis/issues/1628
|
|
||||||
//
|
|
||||||
// Traefik's forwardAuth middleware calls Anubis at the literal path
|
|
||||||
// /.within.website/x/cmd/anubis/api/check and conveys the original URL in the
|
|
||||||
// X-Forwarded-Uri header. Path-targeting policy rules must match that header
|
|
||||||
// (not r.URL.Path), otherwise every request looks like a request to /check.
|
|
||||||
|
|
||||||
const BASE = "http://localhost:8080";
|
|
||||||
const UA = "Mozilla/5.0 (compatible; AnubisTraefikSmoke/1.0)";
|
|
||||||
|
|
||||||
const cases = [
|
|
||||||
{ path: "/", expected: 307, why: "control: no DENY rule, default challenge redirect" },
|
|
||||||
{ path: "/free", expected: 307, why: "control: no DENY rule, default challenge redirect" },
|
|
||||||
{ path: "/admin", expected: 403, why: "path_regex must match X-Forwarded-Uri, not 307 or 200" },
|
|
||||||
{ path: "/admin/users", expected: 403, why: "path_regex must match X-Forwarded-Uri, not 307 or 200" },
|
|
||||||
{ path: "/api/secret", expected: 403, why: "CEL path must match X-Forwarded-Uri, not 307 or 200" },
|
|
||||||
];
|
|
||||||
|
|
||||||
let failed = false;
|
|
||||||
|
|
||||||
for (const c of cases) {
|
|
||||||
const resp = await fetch(`${BASE}${c.path}`, {
|
|
||||||
headers: { "User-Agent": UA },
|
|
||||||
redirect: "manual",
|
|
||||||
});
|
|
||||||
const ok = resp.status === c.expected;
|
|
||||||
console.log(
|
|
||||||
`${ok ? "PASS" : "FAIL"}: GET ${c.path} → ${resp.status} (want ${c.expected}: ${c.why})`,
|
|
||||||
);
|
|
||||||
if (!ok) failed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
process.exit(failed ? 1 : 0);
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -eo pipefail
|
|
||||||
|
|
||||||
export VERSION=${GITHUB_SHA:-devel}-test
|
|
||||||
export KO_DOCKER_REPO=ko.local
|
|
||||||
|
|
||||||
set -u
|
|
||||||
|
|
||||||
source ../lib/lib.sh
|
|
||||||
|
|
||||||
build_anubis_ko
|
|
||||||
|
|
||||||
function cleanup() {
|
|
||||||
docker compose down -t 1 || :
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup EXIT SIGINT
|
|
||||||
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
backoff-retry --try-count 20 node ./test.mjs
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
entryPoints:
|
|
||||||
web:
|
|
||||||
address: ":80"
|
|
||||||
|
|
||||||
providers:
|
|
||||||
file:
|
|
||||||
directory: /config
|
|
||||||
watch: false
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
||||||
+4
-46
@@ -7,43 +7,17 @@ import (
|
|||||||
|
|
||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis"
|
|
||||||
"github.com/TecharoHQ/anubis/lib/challenge"
|
"github.com/TecharoHQ/anubis/lib/challenge"
|
||||||
"github.com/TecharoHQ/anubis/lib/config"
|
"github.com/TecharoHQ/anubis/lib/config"
|
||||||
"github.com/TecharoHQ/anubis/lib/localization"
|
"github.com/TecharoHQ/anubis/lib/localization"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options carries per-server render state for Anubis pages. Embedders can
|
|
||||||
// render multiple Server instances in one process without mutating package
|
|
||||||
// globals.
|
|
||||||
type Options struct {
|
|
||||||
BasePrefix string
|
|
||||||
PublicURL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultOptions preserves the legacy package-global behavior for callers that
|
|
||||||
// render web components directly.
|
|
||||||
func DefaultOptions() Options {
|
|
||||||
return Options{
|
|
||||||
BasePrefix: anubis.BasePrefix,
|
|
||||||
PublicURL: anubis.PublicUrl,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Base(title string, body templ.Component, impressum *config.Impressum, localizer *localization.SimpleLocalizer) templ.Component {
|
func Base(title string, body templ.Component, impressum *config.Impressum, localizer *localization.SimpleLocalizer) templ.Component {
|
||||||
return BaseWithOptions(DefaultOptions(), title, body, impressum, localizer)
|
return base(title, body, impressum, nil, nil, localizer)
|
||||||
}
|
|
||||||
|
|
||||||
func BaseWithOptions(opts Options, title string, body templ.Component, impressum *config.Impressum, localizer *localization.SimpleLocalizer) templ.Component {
|
|
||||||
return base(opts, title, body, impressum, nil, nil, localizer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BaseWithChallengeAndOGTags(title string, body templ.Component, impressum *config.Impressum, challenge *challenge.Challenge, rules *config.ChallengeRules, ogTags map[string]string, localizer *localization.SimpleLocalizer) templ.Component {
|
func BaseWithChallengeAndOGTags(title string, body templ.Component, impressum *config.Impressum, challenge *challenge.Challenge, rules *config.ChallengeRules, ogTags map[string]string, localizer *localization.SimpleLocalizer) templ.Component {
|
||||||
return BaseWithChallengeAndOGTagsWithOptions(DefaultOptions(), title, body, impressum, challenge, rules, ogTags, localizer)
|
return base(title, body, impressum, struct {
|
||||||
}
|
|
||||||
|
|
||||||
func BaseWithChallengeAndOGTagsWithOptions(opts Options, title string, body templ.Component, impressum *config.Impressum, challenge *challenge.Challenge, rules *config.ChallengeRules, ogTags map[string]string, localizer *localization.SimpleLocalizer) templ.Component {
|
|
||||||
return base(opts, title, body, impressum, struct {
|
|
||||||
Rules *config.ChallengeRules `json:"rules"`
|
Rules *config.ChallengeRules `json:"rules"`
|
||||||
Challenge any `json:"challenge"`
|
Challenge any `json:"challenge"`
|
||||||
}{
|
}{
|
||||||
@@ -53,27 +27,11 @@ func BaseWithChallengeAndOGTagsWithOptions(opts Options, title string, body temp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ErrorPage(msg, mail, code string, localizer *localization.SimpleLocalizer) templ.Component {
|
func ErrorPage(msg, mail, code string, localizer *localization.SimpleLocalizer) templ.Component {
|
||||||
return ErrorPageWithOptions(DefaultOptions(), msg, mail, code, localizer)
|
return errorPage(msg, mail, code, localizer)
|
||||||
}
|
|
||||||
|
|
||||||
func ErrorPageWithOptions(opts Options, msg, mail, code string, localizer *localization.SimpleLocalizer) templ.Component {
|
|
||||||
return errorPage(opts, msg, mail, code, localizer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func StaticHappy(localizer *localization.SimpleLocalizer) templ.Component {
|
|
||||||
return StaticHappyWithOptions(DefaultOptions(), localizer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func StaticHappyWithOptions(opts Options, localizer *localization.SimpleLocalizer) templ.Component {
|
|
||||||
return staticHappy(opts, localizer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Bench(localizer *localization.SimpleLocalizer) templ.Component {
|
func Bench(localizer *localization.SimpleLocalizer) templ.Component {
|
||||||
return BenchWithOptions(DefaultOptions(), localizer)
|
return bench(localizer)
|
||||||
}
|
|
||||||
|
|
||||||
func BenchWithOptions(opts Options, localizer *localization.SimpleLocalizer) templ.Component {
|
|
||||||
return bench(opts, localizer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func honeypotLink(href string) templ.Component {
|
func honeypotLink(href string) templ.Component {
|
||||||
|
|||||||
+13
-13
@@ -9,12 +9,12 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ base(opts Options, title string, body templ.Component, impressum *config.Impressum, challenge any, ogTags map[string]string, localizer *localization.SimpleLocalizer) {
|
templ base(title string, body templ.Component, impressum *config.Impressum, challenge any, ogTags map[string]string, localizer *localization.SimpleLocalizer) {
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang={ localizer.GetLang() }>
|
<html lang={ localizer.GetLang() }>
|
||||||
<head>
|
<head>
|
||||||
<title>{ title }</title>
|
<title>{ title }</title>
|
||||||
<link rel="stylesheet" href={ opts.BasePrefix + xess.URL }/>
|
<link rel="stylesheet" href={ anubis.BasePrefix + xess.URL }/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<meta name="robots" content="noindex,nofollow"/>
|
<meta name="robots" content="noindex,nofollow"/>
|
||||||
for key, value := range ogTags {
|
for key, value := range ogTags {
|
||||||
@@ -60,11 +60,11 @@ templ base(opts Options, title string, body templ.Component, impressum *config.I
|
|||||||
</style>
|
</style>
|
||||||
@templ.JSONScript("anubis_version", anubis.Version)
|
@templ.JSONScript("anubis_version", anubis.Version)
|
||||||
@templ.JSONScript("anubis_challenge", challenge)
|
@templ.JSONScript("anubis_challenge", challenge)
|
||||||
@templ.JSONScript("anubis_base_prefix", opts.BasePrefix)
|
@templ.JSONScript("anubis_base_prefix", anubis.BasePrefix)
|
||||||
@templ.JSONScript("anubis_public_url", opts.PublicURL)
|
@templ.JSONScript("anubis_public_url", anubis.PublicUrl)
|
||||||
</head>
|
</head>
|
||||||
<body id="top">
|
<body id="top">
|
||||||
@honeypotLink(opts.BasePrefix + fmt.Sprintf("%shoneypot/%s/init", anubis.APIPrefix, uuid.NewString()))
|
@honeypotLink(anubis.BasePrefix + fmt.Sprintf("%shoneypot/%s/init", anubis.APIPrefix, uuid.NewString()))
|
||||||
<main>
|
<main>
|
||||||
<h1 id="title" class="centered-div">{ title }</h1>
|
<h1 id="title" class="centered-div">{ title }</h1>
|
||||||
@body
|
@body
|
||||||
@@ -79,7 +79,7 @@ templ base(opts Options, title string, body templ.Component, impressum *config.I
|
|||||||
if impressum != nil {
|
if impressum != nil {
|
||||||
<p>
|
<p>
|
||||||
@templ.Raw(impressum.Footer)
|
@templ.Raw(impressum.Footer)
|
||||||
-- <a href={ templ.SafeURL(opts.BasePrefix + fmt.Sprintf("%simprint", anubis.APIPrefix)) }>Imprint</a>
|
-- <a href={ templ.SafeURL(anubis.BasePrefix + fmt.Sprintf("%simprint", anubis.APIPrefix)) }>Imprint</a>
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
<p>{ localizer.T("version_info") } <code>{ anubis.Version }</code>.</p>
|
<p>{ localizer.T("version_info") } <code>{ anubis.Version }</code>.</p>
|
||||||
@@ -90,9 +90,9 @@ templ base(opts Options, title string, body templ.Component, impressum *config.I
|
|||||||
</html>
|
</html>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ errorPage(opts Options, message, mail, code string, localizer *localization.SimpleLocalizer) {
|
templ errorPage(message, mail, code string, localizer *localization.SimpleLocalizer) {
|
||||||
<div class="centered-div">
|
<div class="centered-div">
|
||||||
<img id="image" alt="Sad Anubis" style="width:100%;max-width:256px;" src={ opts.BasePrefix + "/.within.website/x/cmd/anubis/static/img/reject.webp?cacheBuster=" + anubis.Version }/>
|
<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>
|
<p>{ message }.</p>
|
||||||
if code != "" {
|
if code != "" {
|
||||||
<code><pre>{ code }</pre></code>
|
<code><pre>{ code }</pre></code>
|
||||||
@@ -110,19 +110,19 @@ templ errorPage(opts Options, message, mail, code string, localizer *localizatio
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ staticHappy(opts Options, localizer *localization.SimpleLocalizer) {
|
templ StaticHappy(localizer *localization.SimpleLocalizer) {
|
||||||
<div class="centered-div">
|
<div class="centered-div">
|
||||||
<img
|
<img
|
||||||
style="display:none;"
|
style="display:none;"
|
||||||
style="width:100%;max-width:256px;"
|
style="width:100%;max-width:256px;"
|
||||||
src={ opts.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" +
|
src={ "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" +
|
||||||
anubis.Version }
|
anubis.Version }
|
||||||
/>
|
/>
|
||||||
<p>{ localizer.T("static_check_endpoint") }</p>
|
<p>{ localizer.T("static_check_endpoint") }</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ bench(opts Options, localizer *localization.SimpleLocalizer) {
|
templ bench(localizer *localization.SimpleLocalizer) {
|
||||||
<div style="height:20rem;display:flex">
|
<div style="height:20rem;display:flex">
|
||||||
<table style="margin-top:1rem;display:grid;grid-template:auto 1fr/auto auto;gap:0 0.5rem">
|
<table style="margin-top:1rem;display:grid;grid-template:auto 1fr/auto auto;gap:0 0.5rem">
|
||||||
<thead
|
<thead
|
||||||
@@ -145,9 +145,9 @@ templ bench(opts Options, localizer *localization.SimpleLocalizer) {
|
|||||||
></tbody>
|
></tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="centered-div">
|
<div class="centered-div">
|
||||||
<img id="image" style="width:100%;max-width:256px;" src={ opts.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
|
<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">{ localizer.T("loading") }</p>
|
<p id="status" style="max-width:256px">{ localizer.T("loading") }</p>
|
||||||
<script async type="module" src={ opts.BasePrefix + "/.within.website/x/cmd/anubis/static/js/bench.mjs?cacheBuster=" + anubis.Version }></script>
|
<script async type="module" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/bench.mjs?cacheBuster=" + anubis.Version }></script>
|
||||||
<div id="sparkline"></div>
|
<div id="sparkline"></div>
|
||||||
<noscript>
|
<noscript>
|
||||||
<p>{ localizer.T("benchmark_requires_js") }</p>
|
<p>{ localizer.T("benchmark_requires_js") }</p>
|
||||||
|
|||||||
Generated
+18
-18
@@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func base(opts Options, title string, body templ.Component, impressum *config.Impressum, challenge any, ogTags map[string]string, localizer *localization.SimpleLocalizer) 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) {
|
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
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
@@ -69,9 +69,9 @@ func base(opts Options, title string, body templ.Component, impressum *config.Im
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var4 templ.SafeURL
|
var templ_7745c5c3_Var4 templ.SafeURL
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(opts.BasePrefix + xess.URL)
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(anubis.BasePrefix + xess.URL)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 17, Col: 59}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 17, Col: 61}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -125,11 +125,11 @@ func base(opts Options, title string, body templ.Component, impressum *config.Im
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templ.JSONScript("anubis_base_prefix", opts.BasePrefix).Render(ctx, templ_7745c5c3_Buffer)
|
templ_7745c5c3_Err = templ.JSONScript("anubis_base_prefix", anubis.BasePrefix).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templ.JSONScript("anubis_public_url", opts.PublicURL).Render(ctx, templ_7745c5c3_Buffer)
|
templ_7745c5c3_Err = templ.JSONScript("anubis_public_url", anubis.PublicUrl).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ func base(opts Options, title string, body templ.Component, impressum *config.Im
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = honeypotLink(opts.BasePrefix+fmt.Sprintf("%shoneypot/%s/init", anubis.APIPrefix, uuid.NewString())).Render(ctx, templ_7745c5c3_Buffer)
|
templ_7745c5c3_Err = honeypotLink(anubis.BasePrefix+fmt.Sprintf("%shoneypot/%s/init", anubis.APIPrefix, uuid.NewString())).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -245,9 +245,9 @@ func base(opts Options, title string, body templ.Component, impressum *config.Im
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var13 templ.SafeURL
|
var templ_7745c5c3_Var13 templ.SafeURL
|
||||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(opts.BasePrefix + fmt.Sprintf("%simprint", anubis.APIPrefix)))
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(anubis.BasePrefix + fmt.Sprintf("%simprint", anubis.APIPrefix)))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 82, Col: 96}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 82, Col: 98}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -292,7 +292,7 @@ func base(opts Options, title string, body templ.Component, impressum *config.Im
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorPage(opts Options, message, mail, code string, localizer *localization.SimpleLocalizer) templ.Component {
|
func errorPage(message, mail, code string, localizer *localization.SimpleLocalizer) templ.Component {
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
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
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
@@ -318,9 +318,9 @@ func errorPage(opts Options, message, mail, code string, localizer *localization
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var17 string
|
var templ_7745c5c3_Var17 string
|
||||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(opts.BasePrefix + "/.within.website/x/cmd/anubis/static/img/reject.webp?cacheBuster=" + anubis.Version)
|
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/reject.webp?cacheBuster=" + anubis.Version)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 95, Col: 179}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 95, Col: 181}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -446,7 +446,7 @@ func errorPage(opts Options, message, mail, code string, localizer *localization
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func staticHappy(opts Options, localizer *localization.SimpleLocalizer) templ.Component {
|
func StaticHappy(localizer *localization.SimpleLocalizer) templ.Component {
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
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
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
@@ -472,7 +472,7 @@ func staticHappy(opts Options, localizer *localization.SimpleLocalizer) templ.Co
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var26 string
|
var templ_7745c5c3_Var26 string
|
||||||
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(opts.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" +
|
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs("/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" +
|
||||||
anubis.Version)
|
anubis.Version)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 119, Col: 18}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 119, Col: 18}
|
||||||
@@ -502,7 +502,7 @@ func staticHappy(opts Options, localizer *localization.SimpleLocalizer) templ.Co
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func bench(opts Options, localizer *localization.SimpleLocalizer) templ.Component {
|
func bench(localizer *localization.SimpleLocalizer) templ.Component {
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
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
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
@@ -606,9 +606,9 @@ func bench(opts Options, localizer *localization.SimpleLocalizer) templ.Componen
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var35 string
|
var templ_7745c5c3_Var35 string
|
||||||
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(opts.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
|
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 148, Col: 164}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 148, Col: 166}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -632,9 +632,9 @@ func bench(opts Options, localizer *localization.SimpleLocalizer) templ.Componen
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var37 string
|
var templ_7745c5c3_Var37 string
|
||||||
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(opts.BasePrefix + "/.within.website/x/cmd/anubis/static/js/bench.mjs?cacheBuster=" + anubis.Version)
|
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/bench.mjs?cacheBuster=" + anubis.Version)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 150, Col: 136}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 150, Col: 138}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|||||||
+1
-1
@@ -59,7 +59,7 @@ func TestBasePrefixInLinks(t *testing.T) {
|
|||||||
|
|
||||||
// Render the base template to a buffer
|
// Render the base template to a buffer
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
component := base(Options{BasePrefix: tt.basePrefix}, tt.name, templ.NopComponent, impressum, nil, nil, localizer)
|
component := base(tt.name, templ.NopComponent, impressum, nil, nil, localizer)
|
||||||
err := component.Render(context.Background(), &buf)
|
err := component.Render(context.Background(), &buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to render template: %v", err)
|
t.Fatalf("failed to render template: %v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user