mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-05-09 16:42:52 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a3e97accc | |||
| c112419c45 | |||
| 423db33e20 | |||
| 1831effb59 | |||
| e0ece7d333 | |||
| 3eab1d873d | |||
| c7b31d0ca9 | |||
| 3154ff5004 | |||
| 5186d7d3ad | |||
| c6d968874d | |||
| 14a8d0c75e | |||
| 0ea13dcee2 | |||
| a2a4cdebd6 | |||
| 168fe79802 | |||
| 865ba0983e | |||
| 27c994d3ce | |||
| 22412d0e22 | |||
| c5ff5f0f26 | |||
| 78fe07a78f | |||
| 04fef9e033 | |||
| fa518e1b8c | |||
| f38210fd84 |
@@ -1,12 +1,12 @@
|
|||||||
<!--
|
<!--
|
||||||
delete me and describe your change here, give enough context for a maintainer to understand what and why
|
delete me and describe your change here, give enough context for a maintainer to understand what and why
|
||||||
|
|
||||||
See https://anubis.techaro.lol/docs/developer/code-quality for more information
|
See https://github.com/TecharoHQ/anubis/blob/main/CONTRIBUTING.md for more information
|
||||||
-->
|
-->
|
||||||
|
|
||||||
Checklist:
|
Checklist:
|
||||||
|
|
||||||
- [ ] Added a description of the changes to the `[Unreleased]` section of docs/docs/CHANGELOG.md
|
- [ ] Added a description of the changes to the `[Unreleased]` section of docs/docs/CHANGELOG.md
|
||||||
- [ ] Added test cases to [the relevant parts of the codebase](https://anubis.techaro.lol/docs/developer/code-quality)
|
- [ ] Added test cases to [the relevant parts of the codebase](https://github.com/TecharoHQ/anubis/blob/main/CONTRIBUTING.md)
|
||||||
- [ ] Ran integration tests `npm run test:integration` (unsupported on Windows, please use WSL)
|
- [ ] Ran integration tests `npm run test:integration` (unsupported on Windows, please use WSL)
|
||||||
- [ ] All of my commits have [verified signatures](https://anubis.techaro.lol/docs/developer/signed-commits)
|
- [ ] All of my commits have [verified signatures](https://anubis.techaro.lol/docs/developer/signed-commits)
|
||||||
|
|||||||
@@ -26,3 +26,8 @@ blocklists
|
|||||||
rififi
|
rififi
|
||||||
prolocation
|
prolocation
|
||||||
Prolocation
|
Prolocation
|
||||||
|
Necron
|
||||||
|
Stargate
|
||||||
|
FFXIV
|
||||||
|
uvensys
|
||||||
|
de
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ FCr
|
|||||||
fcrdns
|
fcrdns
|
||||||
fediverse
|
fediverse
|
||||||
ffprobe
|
ffprobe
|
||||||
|
FFXIV
|
||||||
fhdr
|
fhdr
|
||||||
financials
|
financials
|
||||||
finfos
|
finfos
|
||||||
@@ -238,6 +239,7 @@ mymaster
|
|||||||
mypass
|
mypass
|
||||||
myuser
|
myuser
|
||||||
nbf
|
nbf
|
||||||
|
Necron
|
||||||
nepeat
|
nepeat
|
||||||
netsurf
|
netsurf
|
||||||
nginx
|
nginx
|
||||||
@@ -329,12 +331,13 @@ Spambot
|
|||||||
spammer
|
spammer
|
||||||
sparkline
|
sparkline
|
||||||
spyderbot
|
spyderbot
|
||||||
|
srcip
|
||||||
srv
|
srv
|
||||||
stackoverflow
|
stackoverflow
|
||||||
|
Stargate
|
||||||
startprecmd
|
startprecmd
|
||||||
stoppostcmd
|
stoppostcmd
|
||||||
storetest
|
storetest
|
||||||
srcip
|
|
||||||
strcmp
|
strcmp
|
||||||
subgrid
|
subgrid
|
||||||
subr
|
subr
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ 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@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "1.25.7"
|
||||||
|
|
||||||
- name: install node deps
|
- name: install node deps
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -26,18 +26,18 @@ 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@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
|
|||||||
@@ -36,17 +36,17 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||||
|
|
||||||
- name: Log into registry
|
- name: Log into registry
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
with:
|
with:
|
||||||
images: ${{ env.IMAGE }}
|
images: ${{ env.IMAGE }}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
SLOG_LEVEL: debug
|
SLOG_LEVEL: debug
|
||||||
|
|
||||||
- name: Generate artifact attestation
|
- name: Generate artifact attestation
|
||||||
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
|
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||||
with:
|
with:
|
||||||
subject-name: ${{ env.IMAGE }}
|
subject-name: ${{ env.IMAGE }}
|
||||||
subject-digest: ${{ steps.build.outputs.digest }}
|
subject-digest: ${{ steps.build.outputs.digest }}
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
|
|
||||||
- name: Log into registry
|
- name: Log into registry
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: techarohq
|
username: techarohq
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/techarohq/anubis/docs
|
images: ghcr.io/techarohq/anubis/docs
|
||||||
tags: |
|
tags: |
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.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@3ece3793e7a9fe94effe257d03ac834c815ea87d # v1.35.1
|
uses: actions-hub/kubectl@5ada4e2c02eacc03978c2437e95c8b0f979a9619 # v1.35.2
|
||||||
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@3ece3793e7a9fe94effe257d03ac834c815ea87d # v1.35.1
|
uses: actions-hub/kubectl@5ada4e2c02eacc03978c2437e95c8b0f979a9619 # v1.35.2
|
||||||
env:
|
env:
|
||||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/techarohq/anubis/docs
|
images: ghcr.io/techarohq/anubis/docs
|
||||||
tags: |
|
tags: |
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||||
with:
|
with:
|
||||||
context: ./docs
|
context: ./docs
|
||||||
cache-to: type=gha
|
cache-to: type=gha
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Check go.mod and go.sum in main directory
|
- name: Check go.mod and go.sum in main directory
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ 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@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
@@ -55,10 +55,10 @@ jobs:
|
|||||||
run: npm run test
|
run: npm run test
|
||||||
|
|
||||||
- name: Lint with staticcheck
|
- name: Lint with staticcheck
|
||||||
uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0
|
uses: dominikh/staticcheck-action@9716614d4101e79b4340dd97b10e54d68234e431 # v1.4.1
|
||||||
with:
|
with:
|
||||||
version: "latest"
|
version: "latest"
|
||||||
|
|
||||||
- name: Govulncheck
|
- name: Govulncheck
|
||||||
run: |
|
run: |
|
||||||
go tool govulncheck ./...
|
go tool govulncheck ./... ||:
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ 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@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: install node deps
|
- name: install node deps
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ 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@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: install node deps
|
- name: install node deps
|
||||||
run: |
|
run: |
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
go tool yeet
|
go tool yeet
|
||||||
|
|
||||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: packages
|
name: packages
|
||||||
path: var/*
|
path: var/*
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||||
|
|
||||||
@@ -57,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@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: ${{ env.ARTIFACT_NAME }}
|
name: ${{ env.ARTIFACT_NAME }}
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Log into registry
|
- name: Log into registry
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
run: |
|
run: |
|
||||||
cd ./test/ssh-ci
|
cd ./test/ssh-ci
|
||||||
|
|||||||
@@ -12,14 +12,15 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
ssh:
|
ssh:
|
||||||
if: github.repository == 'TecharoHQ/anubis'
|
if: github.repository == 'TecharoHQ/anubis'
|
||||||
runs-on: alrest-techarohq
|
#runs-on: alrest-techarohq
|
||||||
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
host:
|
host:
|
||||||
- riscv64
|
- riscv64
|
||||||
- ppc64le
|
- ppc64le
|
||||||
- aarch64-4k
|
#- aarch64-4k
|
||||||
- aarch64-16k
|
#- aarch64-16k
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -29,15 +30,15 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install CI target SSH key
|
- name: Install CI target SSH key
|
||||||
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4 # v2.7.0
|
uses: shimataro/ssh-key-action@6b84f2e793b32fa0b03a379cadadec75cc539391 # v2.8.0
|
||||||
with:
|
with:
|
||||||
key: ${{ secrets.CI_SSH_KEY }}
|
key: ${{ secrets.CI_SSH_KEY }}
|
||||||
name: id_rsa
|
name: id_rsa
|
||||||
known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }}
|
known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }}
|
||||||
|
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Run CI
|
- name: Run CI
|
||||||
run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }}
|
run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ build: assets
|
|||||||
lint: assets
|
lint: assets
|
||||||
$(GO) vet ./...
|
$(GO) vet ./...
|
||||||
$(GO) tool staticcheck ./...
|
$(GO) tool staticcheck ./...
|
||||||
$(GO) tool govulncheck ./...
|
|
||||||
|
|
||||||
prebaked-build:
|
prebaked-build:
|
||||||
$(GO) build -o ./var/anubis -ldflags "-X 'github.com/TecharoHQ/anubis.Version=$(VERSION)'" ./cmd/anubis
|
$(GO) build -o ./var/anubis -ldflags "-X 'github.com/TecharoHQ/anubis.Version=$(VERSION)'" ./cmd/anubis
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ Anubis is brought to you by sponsors and donors like:
|
|||||||
|
|
||||||
### Gold Tier
|
### Gold Tier
|
||||||
|
|
||||||
|
<a href="https://www.unipromos.com/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="./docs/static/img/sponsors/unipromos.webp" alt="Unipromos" height="64" />
|
||||||
|
</a>
|
||||||
|
<a href="https://uvensys.de/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="./docs/static/img/sponsors/uvensys.webp" alt="Uvensys" height="64">
|
||||||
|
</a>
|
||||||
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
<img src="./docs/static/img/sponsors/distrust-logo.webp" alt="Distrust" height="64">
|
<img src="./docs/static/img/sponsors/distrust-logo.webp" alt="Distrust" height="64">
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
+2
-2
@@ -418,8 +418,8 @@ func main() {
|
|||||||
|
|
||||||
var redirectDomainsList []string
|
var redirectDomainsList []string
|
||||||
if *redirectDomains != "" {
|
if *redirectDomains != "" {
|
||||||
domains := strings.Split(*redirectDomains, ",")
|
domains := strings.SplitSeq(*redirectDomains, ",")
|
||||||
for _, domain := range domains {
|
for domain := range domains {
|
||||||
_, err = url.Parse(domain)
|
_, err = url.Parse(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("cannot parse redirect-domain %q: %s", domain, err.Error())
|
log.Fatalf("cannot parse redirect-domain %q: %s", domain, err.Error())
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis/lib/config"
|
"github.com/TecharoHQ/anubis/lib/config"
|
||||||
@@ -210,11 +211,8 @@ func parseRobotsTxt(input io.Reader) ([]RobotsRule, error) {
|
|||||||
|
|
||||||
// Mark blacklisted user agents (those with "Disallow: /")
|
// Mark blacklisted user agents (those with "Disallow: /")
|
||||||
for i := range rules {
|
for i := range rules {
|
||||||
for _, disallow := range rules[i].Disallows {
|
if slices.Contains(rules[i].Disallows, "/") {
|
||||||
if disallow == "/" {
|
rules[i].IsBlacklist = true
|
||||||
rules[i].IsBlacklist = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,8 +158,8 @@ func TestDataFileConversion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(*outputFormat) == "yaml" {
|
if strings.ToLower(*outputFormat) == "yaml" {
|
||||||
var actualData []interface{}
|
var actualData []any
|
||||||
var expectedData []interface{}
|
var expectedData []any
|
||||||
|
|
||||||
err = yaml.Unmarshal(actualOutput, &actualData)
|
err = yaml.Unmarshal(actualOutput, &actualData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -178,8 +178,8 @@ func TestDataFileConversion(t *testing.T) {
|
|||||||
t.Errorf("Output mismatch for %s\nExpected:\n%s\n\nActual:\n%s", tc.name, expectedStr, actualStr)
|
t.Errorf("Output mismatch for %s\nExpected:\n%s\n\nActual:\n%s", tc.name, expectedStr, actualStr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var actualData []interface{}
|
var actualData []any
|
||||||
var expectedData []interface{}
|
var expectedData []any
|
||||||
|
|
||||||
err = json.Unmarshal(actualOutput, &actualData)
|
err = json.Unmarshal(actualOutput, &actualData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -419,6 +419,6 @@ Disallow: /`
|
|||||||
|
|
||||||
// compareData performs a deep comparison of two data structures,
|
// compareData performs a deep comparison of two data structures,
|
||||||
// ignoring differences that are semantically equivalent in YAML/JSON
|
// ignoring differences that are semantically equivalent in YAML/JSON
|
||||||
func compareData(actual, expected interface{}) bool {
|
func compareData(actual, expected any) bool {
|
||||||
return reflect.DeepEqual(actual, expected)
|
return reflect.DeepEqual(actual, expected)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
- fix: prevent nil pointer panic in challenge validation when threshold rules match during PassChallenge (#1463)
|
||||||
|
- Instruct reverse proxies to not cache error pages.
|
||||||
|
- Fixed mixed tab/space indentation in Caddy documentation code block
|
||||||
|
- Rewrite main proof of work challenge to use Preact instead of Vanilla.js ([#1149](https://github.com/TecharoHQ/anubis/issues/1149))
|
||||||
|
|
||||||
<!-- This changes the project to: -->
|
<!-- This changes the project to: -->
|
||||||
|
|
||||||
## v1.25.0: Necron
|
## v1.25.0: Necron
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ yourdomain.example.com {
|
|||||||
tls your@email.address
|
tls your@email.address
|
||||||
|
|
||||||
reverse_proxy http://anubis:3000 {
|
reverse_proxy http://anubis:3000 {
|
||||||
header_up X-Real-Ip {remote_host}
|
header_up X-Real-Ip {remote_host}
|
||||||
header_up X-Http-Version {http.request.proto}
|
header_up X-Http-Version {http.request.proto}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ This simply enables SSL offloading, sets some useful and required headers and ro
|
|||||||
|
|
||||||
Due to the fact that HAProxy can decode JWT, we are able to verify the Anubis token directly in HAProxy and route the traffic to the specific backends ourselves.
|
Due to the fact that HAProxy can decode JWT, we are able to verify the Anubis token directly in HAProxy and route the traffic to the specific backends ourselves.
|
||||||
|
|
||||||
|
Mind that rule logic to allow Git HTTP and other legit bot traffic to bypass is delegated from Anubis to HAProxy then. If required, you should implement any whitelisting in HAProxy using `acl_anubis_ignore` yourself.
|
||||||
|
|
||||||
In this example are three applications behind one HAProxy frontend. Only App1 and App2 are secured via Anubis; App3 is open for everyone. The path `/excluded/path` can also be accessed by anyone.
|
In this example are three applications behind one HAProxy frontend. Only App1 and App2 are secured via Anubis; App3 is open for everyone. The path `/excluded/path` can also be accessed by anyone.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|||||||
@@ -130,3 +130,52 @@ Then point your Ingress to the Anubis port:
|
|||||||
# diff-add
|
# diff-add
|
||||||
name: anubis
|
name: anubis
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Envoy Gateway
|
||||||
|
|
||||||
|
If you are using envoy-gateway, the `X-Real-Ip` header is not set by default, but Anubis does require it. You can resolve this by adding the header, either on the specific `HTTPRoute` where Anubis is listening, or on the `ClientTrafficPolicy` to apply it to any number of Gateways:
|
||||||
|
|
||||||
|
HTTPRoute:
|
||||||
|
```yaml
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: app-route
|
||||||
|
spec:
|
||||||
|
hostnames: ["app.domain.tld"]
|
||||||
|
parentRefs:
|
||||||
|
- name: envoy-external
|
||||||
|
namespace: network
|
||||||
|
sectionName: https
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- identifier: *app
|
||||||
|
port: anubis
|
||||||
|
filters:
|
||||||
|
- type: RequestHeaderModifier
|
||||||
|
requestHeaderModifier:
|
||||||
|
set:
|
||||||
|
- name: X-Real-Ip
|
||||||
|
value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
|
||||||
|
```
|
||||||
|
|
||||||
|
Applying to any number of Gateways:
|
||||||
|
```yaml
|
||||||
|
apiVersion: gateway.envoyproxy.io/v1alpha1
|
||||||
|
kind: ClientTrafficPolicy
|
||||||
|
metadata:
|
||||||
|
name: envoy
|
||||||
|
spec:
|
||||||
|
headers:
|
||||||
|
earlyRequestHeaders:
|
||||||
|
set:
|
||||||
|
- name: X-Real-Ip
|
||||||
|
value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
|
||||||
|
clientIPDetection:
|
||||||
|
xForwardedFor:
|
||||||
|
trustedCIDRs:
|
||||||
|
- 10.96.0.0/16 # Cluster pod CIDR
|
||||||
|
targetSelectors: # These will apply to all Gateways
|
||||||
|
- group: gateway.networking.k8s.io
|
||||||
|
kind: Gateway
|
||||||
|
```
|
||||||
|
|||||||
@@ -393,6 +393,32 @@ logging:
|
|||||||
|
|
||||||
When files are rotated out, the old files will be named after the rotation timestamp in [RFC 3339 format](https://www.rfc-editor.org/rfc/rfc3339).
|
When files are rotated out, the old files will be named after the rotation timestamp in [RFC 3339 format](https://www.rfc-editor.org/rfc/rfc3339).
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
If you are running Anubis in systemd via a native package, the default systemd unit settings are very restrictive and will forbid writing to folders in `/var/log`. In order to fix this, please make a [drop-in unit](https://www.flatcar.org/docs/latest/setup/systemd/drop-in-units/) like the following:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# /etc/systemd/anubis@instance-name.service.d/50-var-log-readwrite.conf
|
||||||
|
[Service]
|
||||||
|
ReadWritePaths=/run /var/log/anubis
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you write this to the correct place, reload the systemd configuration:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
And then restart Anubis:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sudo systemctl restart anubis@instance-name
|
||||||
|
```
|
||||||
|
|
||||||
|
You may be required to make drop-ins for each Anubis instance depending on the facts and circumstances of your deployment.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
### `stdio` sink
|
### `stdio` sink
|
||||||
|
|
||||||
By default, Anubis logs everything to the standard error stream of its process. This requires no configuration:
|
By default, Anubis logs everything to the standard error stream of its process. This requires no configuration:
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
title: Code quality guidelines
|
|
||||||
---
|
|
||||||
|
|
||||||
When submitting code to Anubis, please take the time to consider the fact that this project is security software. If things go bad, bots can pummel sites into oblivion. This is not ideal for uptime.
|
|
||||||
|
|
||||||
As such, code reviews will be a bit more strict than you have seen in other projects. This is not people trying to be mean, this is a side effect of taking the problem seriously.
|
|
||||||
|
|
||||||
When making code changes, try to do the following:
|
|
||||||
|
|
||||||
- If you're submitting a bugfix, add a test case for it
|
|
||||||
- If you're changing the JavaScript, make sure the integration tests pass (`npm run test:integration`)
|
|
||||||
|
|
||||||
## Commit messages
|
|
||||||
|
|
||||||
Anubis follows the Go project's conventions for commit messages. In general, an ideal commit message should read like this:
|
|
||||||
|
|
||||||
```text
|
|
||||||
path/to/folder: brief description of the change
|
|
||||||
|
|
||||||
If the change is subtle, has implementation consequences, or is otherwise
|
|
||||||
not entirely self-describing: take the time to spell out why. If things
|
|
||||||
are very subtle, please also amend the documentation accordingly
|
|
||||||
```
|
|
||||||
|
|
||||||
The subject of a commit message should be the second half of the sentence "This commit changes the Anubis project to:". Here's a few examples:
|
|
||||||
|
|
||||||
- `disable DroneBL by default`
|
|
||||||
- `port the challenge to WebAssembly`
|
|
||||||
|
|
||||||
The extended commit message is also your place to give rationale for a new feature. When maintainers are reviewing your code, they will use this to figure out if the burden from feature maintainership is worth the merge.
|
|
||||||
@@ -35,6 +35,12 @@ Anubis is brought to you by sponsors and donors like:
|
|||||||
|
|
||||||
### Gold Tier
|
### Gold Tier
|
||||||
|
|
||||||
|
<a href="https://www.unipromos.com/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="/img/sponsors/unipromos.webp" alt="Uvensys" height="64" />
|
||||||
|
</a>
|
||||||
|
<a href="https://uvensys.de/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="/img/sponsors/uvensys.webp" alt="Uvensys" height="64" />
|
||||||
|
</a>
|
||||||
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
<img src="/img/sponsors/distrust-logo.webp" alt="Distrust" height="64" />
|
<img src="/img/sponsors/distrust-logo.webp" alt="Distrust" height="64" />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -38,10 +38,8 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
|||||||
- https://squirreljme.cc/
|
- https://squirreljme.cc/
|
||||||
- https://superlove.sayitditto.net/
|
- https://superlove.sayitditto.net/
|
||||||
- https://svnweb.freebsd.org/
|
- https://svnweb.freebsd.org/
|
||||||
- https://trac.ffmpeg.org/
|
|
||||||
- https://tumfatig.net/
|
- https://tumfatig.net/
|
||||||
- https://wiki.archlinux.org/
|
- https://wiki.archlinux.org/
|
||||||
- https://wiki.dolphin-emu.org/
|
|
||||||
- https://wiki.freepascal.org/
|
- https://wiki.freepascal.org/
|
||||||
- https://wiki.koha-community.org/
|
- https://wiki.koha-community.org/
|
||||||
- https://www.cfaarchive.org/
|
- https://www.cfaarchive.org/
|
||||||
@@ -53,6 +51,11 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
|||||||
- https://bbs.archlinux32.org/
|
- https://bbs.archlinux32.org/
|
||||||
- https://bugs.archlinux32.org/
|
- https://bugs.archlinux32.org/
|
||||||
</details>
|
</details>
|
||||||
|
- <details>
|
||||||
|
<summary>Dolphin Emulator</summary>
|
||||||
|
- https://forums.dolphin-emu.org/
|
||||||
|
- https://wiki.dolphin-emu.org/
|
||||||
|
</details>
|
||||||
- <details>
|
- <details>
|
||||||
<summary>Duke University</summary>
|
<summary>Duke University</summary>
|
||||||
- https://repository.duke.edu/
|
- https://repository.duke.edu/
|
||||||
@@ -60,6 +63,11 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
|||||||
- https://find.library.duke.edu/
|
- https://find.library.duke.edu/
|
||||||
- https://nicholas.duke.edu/
|
- https://nicholas.duke.edu/
|
||||||
</details>
|
</details>
|
||||||
|
- <details>
|
||||||
|
<summary>FFmpeg</summary>
|
||||||
|
- https://git.ffmpeg.org/
|
||||||
|
- https://trac.ffmpeg.org/
|
||||||
|
</details>
|
||||||
- <details>
|
- <details>
|
||||||
<summary>Forschungszentrum Jülich</summary>
|
<summary>Forschungszentrum Jülich</summary>
|
||||||
- https://juser.fz-juelich.de/
|
- https://juser.fz-juelich.de/
|
||||||
@@ -112,11 +120,8 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
|||||||
- https://git.kernel.org/
|
- https://git.kernel.org/
|
||||||
- https://lore.kernel.org/
|
- https://lore.kernel.org/
|
||||||
</details>
|
</details>
|
||||||
- <details>
|
|
||||||
<summary>The United Nations</summary>
|
|
||||||
- https://policytoolbox.iiep.unesco.org/
|
|
||||||
</details>
|
|
||||||
- <details>
|
- <details>
|
||||||
<summary>Valve Corporation</summary>
|
<summary>Valve Corporation</summary>
|
||||||
- https://developer.valvesoftware.com/wiki/Main_Page
|
- https://developer.valvesoftware.com/wiki/Main_Page
|
||||||
|
- https://wiki.teamfortress.com/wiki/Main_Page
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
@@ -36,7 +36,7 @@ func Glob(pattern, subj string) bool {
|
|||||||
end := len(parts) - 1
|
end := len(parts) - 1
|
||||||
|
|
||||||
// Go over the leading parts and ensure they match.
|
// Go over the leading parts and ensure they match.
|
||||||
for i := 0; i < end; i++ {
|
for i := range end {
|
||||||
idx := strings.Index(subj, parts[i])
|
idx := strings.Index(subj, parts[i])
|
||||||
|
|
||||||
switch i {
|
switch i {
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func TestHashCollisions(t *testing.T) {
|
|||||||
for _, prefix := range prefixes {
|
for _, prefix := range prefixes {
|
||||||
for _, suffix := range suffixes {
|
for _, suffix := range suffixes {
|
||||||
for _, variation := range variations {
|
for _, variation := range variations {
|
||||||
for i := 0; i < 100; i++ {
|
for i := range 100 {
|
||||||
input := fmt.Sprintf("%s%s%s-%d", prefix, suffix, variation, i)
|
input := fmt.Sprintf("%s%s%s-%d", prefix, suffix, variation, i)
|
||||||
hash := XXHash64sum(input)
|
hash := XXHash64sum(input)
|
||||||
if existing, exists := xxhashHashes[hash]; exists {
|
if existing, exists := xxhashHashes[hash]; exists {
|
||||||
@@ -211,7 +211,7 @@ func TestHashCollisions(t *testing.T) {
|
|||||||
|
|
||||||
seqCount := 0
|
seqCount := 0
|
||||||
for _, pattern := range patterns {
|
for _, pattern := range patterns {
|
||||||
for i := 0; i < 10000; i++ {
|
for i := range 10000 {
|
||||||
input := fmt.Sprintf(pattern, i)
|
input := fmt.Sprintf(pattern, i)
|
||||||
hash := XXHash64sum(input)
|
hash := XXHash64sum(input)
|
||||||
if existing, exists := xxhashHashes[hash]; exists {
|
if existing, exists := xxhashHashes[hash]; exists {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func (i *Impl) makeAffirmations() []string {
|
|||||||
count := rand.IntN(5) + 1
|
count := rand.IntN(5) + 1
|
||||||
|
|
||||||
var result []string
|
var result []string
|
||||||
for j := 0; j < count; j++ {
|
for range count {
|
||||||
result = append(result, i.affirmation.Spin())
|
result = append(result, i.affirmation.Spin())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ func (i *Impl) makeSpins() []string {
|
|||||||
count := rand.IntN(5) + 1
|
count := rand.IntN(5) + 1
|
||||||
|
|
||||||
var result []string
|
var result []string
|
||||||
for j := 0; j < count; j++ {
|
for range count {
|
||||||
result = append(result, i.body.Spin())
|
result = append(result, i.body.Spin())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ func (lo *ListOr[T]) UnmarshalJSON(data []byte) error {
|
|||||||
|
|
||||||
// Check if first non-whitespace character is '['
|
// Check if first non-whitespace character is '['
|
||||||
firstChar := data[0]
|
firstChar := data[0]
|
||||||
for i := 0; i < len(data); i++ {
|
for i := range data {
|
||||||
if data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
if data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||||
firstChar = data[i]
|
firstChar = data[i]
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func TestMemoryUsage(t *testing.T) {
|
|||||||
|
|
||||||
// Run getTarget many times
|
// Run getTarget many times
|
||||||
u, _ := url.Parse("/path/to/resource?query=1&foo=bar&baz=qux")
|
u, _ := url.Parse("/path/to/resource?query=1&foo=bar&baz=qux")
|
||||||
for i := 0; i < 10000; i++ {
|
for range 10000 {
|
||||||
_ = cache.getTarget(u)
|
_ = cache.getTarget(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ func TestMemoryUsage(t *testing.T) {
|
|||||||
runtime.GC()
|
runtime.GC()
|
||||||
runtime.ReadMemStats(&m1)
|
runtime.ReadMemStats(&m1)
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
for range 1000 {
|
||||||
_ = cache.extractOGTags(doc)
|
_ = cache.extractOGTags(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ogtags
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -78,7 +79,7 @@ func FuzzGetTarget(f *testing.F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure no memory corruption by calling multiple times
|
// Ensure no memory corruption by calling multiple times
|
||||||
for i := 0; i < 3; i++ {
|
for range 3 {
|
||||||
result2 := cache.getTarget(u)
|
result2 := cache.getTarget(u)
|
||||||
if result != result2 {
|
if result != result2 {
|
||||||
t.Errorf("getTarget not deterministic: %q != %q", result, result2)
|
t.Errorf("getTarget not deterministic: %q != %q", result, result2)
|
||||||
@@ -148,11 +149,8 @@ func FuzzExtractOGTags(f *testing.F) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
for _, tag := range cache.approvedTags {
|
if slices.Contains(cache.approvedTags, property) {
|
||||||
if property == tag {
|
approved = true
|
||||||
approved = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
@@ -260,11 +258,8 @@ func FuzzExtractMetaTagInfo(f *testing.F) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
for _, tag := range cache.approvedTags {
|
if slices.Contains(cache.approvedTags, property) {
|
||||||
if property == tag {
|
approved = true
|
||||||
approved = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ogtags
|
package ogtags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
@@ -65,10 +66,8 @@ func (c *OGTagCache) extractMetaTagInfo(n *html.Node) (property, content string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check exact matches
|
// Check exact matches
|
||||||
for _, tag := range c.approvedTags {
|
if slices.Contains(c.approvedTags, propertyKey) {
|
||||||
if propertyKey == tag {
|
return propertyKey, content
|
||||||
return propertyKey, content
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", content
|
return "", content
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ func TestPlaywrightBrowser(t *testing.T) {
|
|||||||
|
|
||||||
var performedAction action
|
var performedAction action
|
||||||
var err error
|
var err error
|
||||||
for i := 0; i < 5; i++ {
|
for i := range 5 {
|
||||||
performedAction, err = executeTestCase(t, tc, typ, anubisURL)
|
performedAction, err = executeTestCase(t, tc, typ, anubisURL)
|
||||||
if performedAction == tc.action {
|
if performedAction == tc.action {
|
||||||
break
|
break
|
||||||
|
|||||||
+24
-5
@@ -81,11 +81,11 @@ type Server struct {
|
|||||||
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 {
|
||||||
return func(token *jwt.Token) (interface{}, error) {
|
return func(token *jwt.Token) (any, error) {
|
||||||
return s.ed25519Priv.Public().(ed25519.PublicKey), nil
|
return s.ed25519Priv.Public().(ed25519.PublicKey), nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return func(token *jwt.Token) (interface{}, error) {
|
return func(token *jwt.Token) (any, error) {
|
||||||
return s.hs512Secret, nil
|
return s.hs512Secret, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +106,13 @@ func (s *Server) issueChallenge(ctx context.Context, r *http.Request, lg *slog.L
|
|||||||
//return nil, errors.New("[unexpected] this codepath should be impossible, asked to issue a challenge for a non-challenge rule")
|
//return nil, errors.New("[unexpected] this codepath should be impossible, asked to issue a challenge for a non-challenge rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rule.Challenge == nil {
|
||||||
|
rule.Challenge = &config.ChallengeRules{
|
||||||
|
Difficulty: s.policy.DefaultDifficulty,
|
||||||
|
Algorithm: config.DefaultAlgorithm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
id, err := uuid.NewV7()
|
id, err := uuid.NewV7()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -134,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())
|
lg.Info("new challenge issued", "challenge", id.String(), "method", chall.Method)
|
||||||
|
|
||||||
return &chall, err
|
return &chall, err
|
||||||
}
|
}
|
||||||
@@ -491,7 +498,11 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
chall, err := s.getChallenge(r)
|
chall, err := s.getChallenge(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Error("getChallenge failed", "err", err)
|
lg.Error("getChallenge failed", "err", err)
|
||||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err))
|
algorithm := "unknown"
|
||||||
|
if rule.Challenge != nil {
|
||||||
|
algorithm = rule.Challenge.Algorithm
|
||||||
|
}
|
||||||
|
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,8 +649,16 @@ func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *p
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matches {
|
if matches {
|
||||||
|
challRules := t.Challenge
|
||||||
|
if challRules == nil {
|
||||||
|
// Non-CHALLENGE thresholds (ALLOW/DENY) don't have challenge config.
|
||||||
|
// Use an empty struct so hydrateChallengeRule can fill from stored
|
||||||
|
// challenge data during validation, rather than baking in defaults
|
||||||
|
// that could mismatch the difficulty the client actually solved for.
|
||||||
|
challRules = &config.ChallengeRules{}
|
||||||
|
}
|
||||||
return cr("threshold/"+t.Name, t.Action, weight), &policy.Bot{
|
return cr("threshold/"+t.Name, t.Action, weight), &policy.Bot{
|
||||||
Challenge: t.Challenge,
|
Challenge: challRules,
|
||||||
Rules: &checker.List{},
|
Rules: &checker.List{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -38,8 +38,8 @@ func NewTLogWriter(t *testing.T) io.Writer {
|
|||||||
|
|
||||||
// Write splits input on newlines and logs each line separately.
|
// Write splits input on newlines and logs each line separately.
|
||||||
func (w *TLogWriter) Write(p []byte) (n int, err error) {
|
func (w *TLogWriter) Write(p []byte) (n int, err error) {
|
||||||
lines := strings.Split(string(p), "\n")
|
lines := strings.SplitSeq(string(p), "\n")
|
||||||
for _, line := range lines {
|
for line := range lines {
|
||||||
if line != "" {
|
if line != "" {
|
||||||
w.t.Log(line)
|
w.t.Log(line)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ var (
|
|||||||
ErrFailed = errors.New("challenge: user failed challenge")
|
ErrFailed = errors.New("challenge: user failed challenge")
|
||||||
ErrMissingField = errors.New("challenge: missing field")
|
ErrMissingField = errors.New("challenge: missing field")
|
||||||
ErrInvalidFormat = errors.New("challenge: field has invalid format")
|
ErrInvalidFormat = errors.New("challenge: field has invalid format")
|
||||||
|
ErrInvalidInput = errors.New("challenge: input is nil or missing required fields")
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewError(verb, publicReason string, privateReason error) *Error {
|
func NewError(verb, publicReason string, privateReason error) *Error {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package challenge
|
package challenge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -50,12 +51,44 @@ type IssueInput struct {
|
|||||||
Store store.Interface
|
Store store.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (in *IssueInput) Valid() error {
|
||||||
|
if in == nil {
|
||||||
|
return fmt.Errorf("%w: IssueInput is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule == nil {
|
||||||
|
return fmt.Errorf("%w: Rule is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Rule.Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ValidateInput struct {
|
type ValidateInput struct {
|
||||||
Rule *policy.Bot
|
Rule *policy.Bot
|
||||||
Challenge *Challenge
|
Challenge *Challenge
|
||||||
Store store.Interface
|
Store store.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (in *ValidateInput) Valid() error {
|
||||||
|
if in == nil {
|
||||||
|
return fmt.Errorf("%w: ValidateInput is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule == nil {
|
||||||
|
return fmt.Errorf("%w: Rule is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Rule.Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Impl interface {
|
type Impl interface {
|
||||||
// Setup registers any additional routes with the Impl for assets or API routes.
|
// Setup registers any additional routes with the Impl for assets or API routes.
|
||||||
Setup(mux *http.ServeMux)
|
Setup(mux *http.ServeMux)
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ type Impl struct{}
|
|||||||
func (i *Impl) Setup(mux *http.ServeMux) {}
|
func (i *Impl) Setup(mux *http.ServeMux) {}
|
||||||
|
|
||||||
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
u, err := r.URL.Parse(anubis.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)
|
||||||
@@ -49,6 +53,10 @@ func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return challenge.NewError("validate", "invalid input", err)
|
||||||
|
}
|
||||||
|
|
||||||
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 800 * time.Millisecond)
|
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 800 * time.Millisecond)
|
||||||
|
|
||||||
if time.Now().Before(wantTime) {
|
if time.Now().Before(wantTime) {
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ type impl struct{}
|
|||||||
func (i *impl) Setup(mux *http.ServeMux) {}
|
func (i *impl) Setup(mux *http.ServeMux) {}
|
||||||
|
|
||||||
func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
u, err := r.URL.Parse(anubis.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)
|
||||||
@@ -57,6 +61,10 @@ func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return challenge.NewError("validate", "invalid input", err)
|
||||||
|
}
|
||||||
|
|
||||||
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 80 * time.Millisecond)
|
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 80 * time.Millisecond)
|
||||||
|
|
||||||
if time.Now().Before(wantTime) {
|
if time.Now().Before(wantTime) {
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return chall.NewError("validate", "invalid input", err)
|
||||||
|
}
|
||||||
|
|
||||||
rule := in.Rule
|
rule := in.Rule
|
||||||
challenge := in.Challenge.RandomData
|
challenge := in.Challenge.RandomData
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,12 @@ import (
|
|||||||
|
|
||||||
templ page(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={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
|
<img style="display:none;" src={ anubis.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 }/>
|
<div id="app">
|
||||||
<p id="status">{ localizer.T("loading") }</p>
|
<img style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
|
||||||
<script async type="module" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version }></script>
|
<p id="status">{ localizer.T("loading") }</p>
|
||||||
<div id="progress" role="progressbar" aria-labelledby="status">
|
|
||||||
<div class="bar-inner"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<script async type="module" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version }></script>
|
||||||
<details>
|
<details>
|
||||||
if anubis.UseSimplifiedExplanation {
|
if anubis.UseSimplifiedExplanation {
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
+16
-16
@@ -34,27 +34,27 @@ func page(localizer *localization.SimpleLocalizer) templ.Component {
|
|||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"centered-div\"><img id=\"image\" style=\"width:100%;max-width:256px;\" src=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"centered-div\"><img style=\"display:none;\" src=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
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(anubis.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/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: 10, Col: 165}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 10, Col: 138}
|
||||||
}
|
}
|
||||||
_, 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 {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"> <img style=\"display:none;\" style=\"width:100%;max-width:256px;\" src=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><div id=\"app\"><img style=\"width:100%;max-width:256px;\" src=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
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(anubis.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/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: 11, Col: 174}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 12, Col: 155}
|
||||||
}
|
}
|
||||||
_, 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 {
|
||||||
@@ -67,26 +67,26 @@ func page(localizer *localization.SimpleLocalizer) templ.Component {
|
|||||||
var templ_7745c5c3_Var4 string
|
var templ_7745c5c3_Var4 string
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("loading"))
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("loading"))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 12, Col: 41}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 13, Col: 42}
|
||||||
}
|
}
|
||||||
_, 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 {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p><script async type=\"module\" src=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p></div><script async type=\"module\" src=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
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(anubis.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: 136}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 15, 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 {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"></script><div id=\"progress\" role=\"progressbar\" aria-labelledby=\"status\"><div class=\"bar-inner\"></div></div><details>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"></script><details>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ func page(localizer *localization.SimpleLocalizer) templ.Component {
|
|||||||
var templ_7745c5c3_Var6 string
|
var templ_7745c5c3_Var6 string
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("simplified_explanation"))
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("simplified_explanation"))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 20, Col: 44}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 19, Col: 44}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -116,7 +116,7 @@ func page(localizer *localization.SimpleLocalizer) templ.Component {
|
|||||||
var templ_7745c5c3_Var7 string
|
var templ_7745c5c3_Var7 string
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("ai_companies_explanation"))
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("ai_companies_explanation"))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 24, Col: 46}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 23, Col: 46}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -129,7 +129,7 @@ func page(localizer *localization.SimpleLocalizer) templ.Component {
|
|||||||
var templ_7745c5c3_Var8 string
|
var templ_7745c5c3_Var8 string
|
||||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("anubis_compromise"))
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("anubis_compromise"))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 27, Col: 39}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 26, Col: 39}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -142,7 +142,7 @@ func page(localizer *localization.SimpleLocalizer) templ.Component {
|
|||||||
var templ_7745c5c3_Var9 string
|
var templ_7745c5c3_Var9 string
|
||||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("hack_purpose"))
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("hack_purpose"))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 30, Col: 34}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 29, Col: 34}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -155,7 +155,7 @@ func page(localizer *localization.SimpleLocalizer) templ.Component {
|
|||||||
var templ_7745c5c3_Var10 string
|
var templ_7745c5c3_Var10 string
|
||||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("jshelter_note"))
|
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("jshelter_note"))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 33, Col: 35}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 32, Col: 35}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
@@ -173,7 +173,7 @@ func page(localizer *localization.SimpleLocalizer) templ.Component {
|
|||||||
var templ_7745c5c3_Var11 string
|
var templ_7745c5c3_Var11 string
|
||||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("javascript_required"))
|
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("javascript_required"))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 39, Col: 40}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 38, Col: 40}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|||||||
@@ -30,6 +30,62 @@ func mkRequest(t *testing.T, values map[string]string) *http.Request {
|
|||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestValidateNilRuleChallenge reproduces the panic from
|
||||||
|
// https://github.com/TecharoHQ/anubis/issues/1463
|
||||||
|
//
|
||||||
|
// When a threshold rule matches during PassChallenge, check() can return
|
||||||
|
// a policy.Bot with Challenge == nil. After hydrateChallengeRule fails to
|
||||||
|
// run (or the error path hits before it), Validate dereferences
|
||||||
|
// rule.Challenge.Difficulty and panics.
|
||||||
|
func TestValidateNilRuleChallenge(t *testing.T) {
|
||||||
|
i := &Impl{Algorithm: "fast"}
|
||||||
|
lg := slog.With()
|
||||||
|
|
||||||
|
// This is the exact response for SHA256("hunter" + "0") with 0 leading zeros required.
|
||||||
|
const challengeStr = "hunter"
|
||||||
|
const response = "2652bdba8fb4d2ab39ef28d8534d7694c557a4ae146c1e9237bd8d950280500e"
|
||||||
|
|
||||||
|
req := mkRequest(t, map[string]string{
|
||||||
|
"nonce": "0",
|
||||||
|
"elapsedTime": "69",
|
||||||
|
"response": response,
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
input *challenge.ValidateInput
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil-rule-challenge",
|
||||||
|
input: &challenge.ValidateInput{
|
||||||
|
Rule: &policy.Bot{},
|
||||||
|
Challenge: &challenge.Challenge{RandomData: challengeStr},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil-rule",
|
||||||
|
input: &challenge.ValidateInput{
|
||||||
|
Challenge: &challenge.Challenge{RandomData: challengeStr},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil-challenge",
|
||||||
|
input: &challenge.ValidateInput{Rule: &policy.Bot{Challenge: &config.ChallengeRules{Algorithm: "fast"}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil-input",
|
||||||
|
input: nil,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := i.Validate(req, lg, tc.input)
|
||||||
|
if !errors.Is(err, challenge.ErrInvalidInput) {
|
||||||
|
t.Fatalf("expected ErrInvalidInput, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBasic(t *testing.T) {
|
func TestBasic(t *testing.T) {
|
||||||
i := &Impl{Algorithm: "fast"}
|
i := &Impl{Algorithm: "fast"}
|
||||||
bot := &policy.Bot{
|
bot := &policy.Bot{
|
||||||
|
|||||||
@@ -228,8 +228,8 @@ type ImportStatement struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (is *ImportStatement) open() (fs.File, error) {
|
func (is *ImportStatement) open() (fs.File, error) {
|
||||||
if strings.HasPrefix(is.Import, "(data)/") {
|
if after, ok := strings.CutPrefix(is.Import, "(data)/"); ok {
|
||||||
fname := strings.TrimPrefix(is.Import, "(data)/")
|
fname := after
|
||||||
fin, err := data.BotPolicies.Open(fname)
|
fin, err := data.BotPolicies.Open(fname)
|
||||||
return fin, err
|
return fin, err
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@ func (sc StatusCodes) Valid() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fileConfig struct {
|
type fileConfig struct {
|
||||||
OpenGraph openGraphFileConfig `json:"openGraph,omitempty"`
|
OpenGraph openGraphFileConfig `json:"openGraph"`
|
||||||
Impressum *Impressum `json:"impressum,omitempty"`
|
Impressum *Impressum `json:"impressum,omitempty"`
|
||||||
Store *Store `json:"store"`
|
Store *Store `json:"store"`
|
||||||
Bots []BotOrImport `json:"bots"`
|
Bots []BotOrImport `json:"bots"`
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ func TestBotValid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, cs := range tests {
|
for _, cs := range tests {
|
||||||
cs := cs
|
|
||||||
t.Run(cs.name, func(t *testing.T) {
|
t.Run(cs.name, func(t *testing.T) {
|
||||||
err := cs.bot.Valid()
|
err := cs.bot.Valid()
|
||||||
if err == nil && cs.err == nil {
|
if err == nil && cs.err == nil {
|
||||||
@@ -216,7 +215,6 @@ func TestConfigValidKnownGood(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
fin, err := os.Open(filepath.Join("testdata", "good", st.Name()))
|
fin, err := os.Open(filepath.Join("testdata", "good", st.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -303,7 +301,6 @@ func TestConfigValidBad(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
fin, err := os.Open(filepath.Join("testdata", "bad", st.Name()))
|
fin, err := os.Open(filepath.Join("testdata", "bad", st.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ func TestBadConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
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"); 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)
|
||||||
@@ -42,7 +41,6 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
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)
|
||||||
|
|||||||
+20
-8
@@ -182,10 +182,7 @@ func makeCode(err error) string {
|
|||||||
enc := base64.StdEncoding.EncodeToString(buf.Bytes())
|
enc := base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
for i := 0; i < len(enc); i += width {
|
for i := 0; i < len(enc); i += width {
|
||||||
end := i + width
|
end := min(i+width, len(enc))
|
||||||
if end > len(enc) {
|
|
||||||
end = len(enc)
|
|
||||||
}
|
|
||||||
builder.WriteString(enc[i:end])
|
builder.WriteString(enc[i:end])
|
||||||
builder.WriteByte('\n')
|
builder.WriteByte('\n')
|
||||||
}
|
}
|
||||||
@@ -222,8 +219,12 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
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)
|
||||||
|
algorithm := "unknown"
|
||||||
|
if rule.Challenge != nil {
|
||||||
|
algorithm = rule.Challenge.Algorithm
|
||||||
|
}
|
||||||
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
||||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err))
|
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,9 +249,13 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
|
|
||||||
impl, ok := challenge.Get(chall.Method)
|
impl, ok := challenge.Get(chall.Method)
|
||||||
if !ok {
|
if !ok {
|
||||||
lg.Error("check failed", "err", "can't get algorithm", "algorithm", rule.Challenge.Algorithm)
|
algorithm := "unknown"
|
||||||
|
if rule.Challenge != nil {
|
||||||
|
algorithm = rule.Challenge.Algorithm
|
||||||
|
}
|
||||||
|
lg.Error("check failed", "err", "can't get algorithm", "algorithm", algorithm)
|
||||||
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
||||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err))
|
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +338,14 @@ 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)
|
||||||
|
|
||||||
templ.Handler(web.Base(localizer.T("oh_noes"), web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer), s.policy.Impressum, localizer), templ.WithStatus(status)).ServeHTTP(w, r)
|
component := web.Base(
|
||||||
|
localizer.T("oh_noes"),
|
||||||
|
web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer),
|
||||||
|
s.policy.Impressum,
|
||||||
|
localizer,
|
||||||
|
)
|
||||||
|
handler := internal.NoStoreCache(templ.Handler(component, templ.WithStatus(status)))
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis"
|
"github.com/TecharoHQ/anubis"
|
||||||
|
"github.com/TecharoHQ/anubis/internal"
|
||||||
"github.com/TecharoHQ/anubis/lib/policy"
|
"github.com/TecharoHQ/anubis/lib/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -191,3 +192,34 @@ func TestRenderIndexUnauthorized(t *testing.T) {
|
|||||||
t.Errorf("expected body %q, got %q", "Authorization required", body)
|
t.Errorf("expected body %q, got %q", "Authorization required", body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoCacheOnError(t *testing.T) {
|
||||||
|
pol := loadPolicies(t, "testdata/useragent.yaml", 0)
|
||||||
|
srv := spawnAnubis(t, Options{Policy: pol})
|
||||||
|
ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
for userAgent, expectedCacheControl := range map[string]string{
|
||||||
|
"DENY": "no-store",
|
||||||
|
"CHALLENGE": "no-store",
|
||||||
|
"ALLOW": "",
|
||||||
|
} {
|
||||||
|
t.Run(userAgent, func(t *testing.T) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", userAgent)
|
||||||
|
|
||||||
|
resp, err := ts.Client().Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Header.Get("Cache-Control") != expectedCacheControl {
|
||||||
|
t.Errorf("wanted Cache-Control header %q, got %q", expectedCacheControl, resp.Header.Get("Cache-Control"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_finished_reading": "Čtení dokončeno, pokračovat →",
|
"js_finished_reading": "Čtení dokončeno, pokračovat →",
|
||||||
"js_calculation_error": "Chyba výpočtu!",
|
"js_calculation_error": "Chyba výpočtu!",
|
||||||
"js_calculation_error_msg": "Nepodařilo se vypočítat výzvu:",
|
"js_calculation_error_msg": "Nepodařilo se vypočítat výzvu:",
|
||||||
"missing_required_forwarded_headers": "Chybějící požadované hlavičky X-Forwarded-*"
|
"missing_required_forwarded_headers": "Chybějící požadované hlavičky X-Forwarded-*",
|
||||||
|
"js_challenge_data_missing": "Data pro ověření chybí. Načtěte prosím stránku znovu."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_iterations": "Iterationen",
|
"js_iterations": "Iterationen",
|
||||||
"js_finished_reading": "Fertig gelesen – weiter zur Seite →",
|
"js_finished_reading": "Fertig gelesen – weiter zur Seite →",
|
||||||
"js_calculation_error": "Berechnungsfehler!",
|
"js_calculation_error": "Berechnungsfehler!",
|
||||||
"js_calculation_error_msg": "Fehler bei der Berechnung der Prüfung:"
|
"js_calculation_error_msg": "Fehler bei der Berechnung der Prüfung:",
|
||||||
|
"js_challenge_data_missing": "Die Prüfungsdaten fehlen. Bitte laden Sie die Seite neu."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_iterations": "iterations",
|
"js_iterations": "iterations",
|
||||||
"js_finished_reading": "I've finished reading, continue →",
|
"js_finished_reading": "I've finished reading, continue →",
|
||||||
"js_calculation_error": "Calculation error!",
|
"js_calculation_error": "Calculation error!",
|
||||||
"js_calculation_error_msg": "Failed to calculate challenge:"
|
"js_calculation_error_msg": "Failed to calculate challenge:",
|
||||||
|
"js_challenge_data_missing": "Challenge data is missing. Please reload the page."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "¡Error de cálculo!",
|
"js_calculation_error": "¡Error de cálculo!",
|
||||||
"js_calculation_error_msg": "Falló al calcular el desafío:",
|
"js_calculation_error_msg": "Falló al calcular el desafío:",
|
||||||
"missing_required_forwarded_headers": "Faltan los encabezados X-Forwarded-* requeridos",
|
"missing_required_forwarded_headers": "Faltan los encabezados X-Forwarded-* requeridos",
|
||||||
"simplified_explanation": "Esta es una medida contra bots y solicitudes maliciosas similar a un CAPTCHA. Sin embargo, en lugar de tener que hacer el trabajo usted mismo, a su navegador se le asigna una tarea de cálculo que debe resolver para garantizar que es un cliente válido. Este concepto se llama <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Prueba de trabajo</a>. La tarea se calcula en unos segundos y se le concede acceso al sitio web. Gracias por su comprensión y paciencia."
|
"simplified_explanation": "Esta es una medida contra bots y solicitudes maliciosas similar a un CAPTCHA. Sin embargo, en lugar de tener que hacer el trabajo usted mismo, a su navegador se le asigna una tarea de cálculo que debe resolver para garantizar que es un cliente válido. Este concepto se llama <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Prueba de trabajo</a>. La tarea se calcula en unos segundos y se le concede acceso al sitio web. Gracias por su comprensión y paciencia.",
|
||||||
|
"js_challenge_data_missing": "Faltan los datos del desafío. Por favor, recargue la página."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Arvutamise viga!",
|
"js_calculation_error": "Arvutamise viga!",
|
||||||
"js_calculation_error_msg": "Ei suutnud kontrolli arvutada:",
|
"js_calculation_error_msg": "Ei suutnud kontrolli arvutada:",
|
||||||
"missing_required_forwarded_headers": "Puuduvad nõutud X-Forwarded-* päised",
|
"missing_required_forwarded_headers": "Puuduvad nõutud X-Forwarded-* päised",
|
||||||
"simplified_explanation": "See on meede robotite ja pahatahtlike päringute vastu, mis sarnaneb CAPTCHA-le. Kuid selle asemel, et peaksite ise tööd tegema, antakse teie brauserile arvutusülesanne, mille see peab lahendama, et tagada selle kehtivus kliendina. Seda kontseptsiooni nimetatakse <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Töötõendiks</a>. Ülesanne arvutatakse mõne sekundiga ja teile antakse juurdepääs veebisaidile. Täname teid mõistva suhtumise ja kannatlikkuse eest."
|
"simplified_explanation": "See on meede robotite ja pahatahtlike päringute vastu, mis sarnaneb CAPTCHA-le. Kuid selle asemel, et peaksite ise tööd tegema, antakse teie brauserile arvutusülesanne, mille see peab lahendama, et tagada selle kehtivus kliendina. Seda kontseptsiooni nimetatakse <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Töötõendiks</a>. Ülesanne arvutatakse mõne sekundiga ja teile antakse juurdepääs veebisaidile. Täname teid mõistva suhtumise ja kannatlikkuse eest.",
|
||||||
|
"js_challenge_data_missing": "Kontrollülesande andmed puuduvad. Palun laadige leht uuesti."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Laskentavirhe!",
|
"js_calculation_error": "Laskentavirhe!",
|
||||||
"js_calculation_error_msg": "Haasteen laskenta ei onnistunut:",
|
"js_calculation_error_msg": "Haasteen laskenta ei onnistunut:",
|
||||||
"missing_required_forwarded_headers": "Puuttuvat vaaditut X-Forwarded-* otsikot",
|
"missing_required_forwarded_headers": "Puuttuvat vaaditut X-Forwarded-* otsikot",
|
||||||
"simplified_explanation": "Tämä on toimenpide botteja ja haitallisia pyyntöjä vastaan, joka on samanlainen kuin CAPTCHA. Sen sijaan, että joutuisit tekemään työtä itse, selaimesi saa laskentatehtävän, joka sen on ratkaistava varmistaakseen, että se on kelvollinen asiakas. Tätä käsitettä kutsutaan nimellä <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Työtodistus</a>. Tehtävä lasketaan muutamassa sekunnissa ja saat pääsyn verkkosivustolle. Kiitos ymmärryksestäsi ja kärsivällisyydestäsi."
|
"simplified_explanation": "Tämä on toimenpide botteja ja haitallisia pyyntöjä vastaan, joka on samanlainen kuin CAPTCHA. Sen sijaan, että joutuisit tekemään työtä itse, selaimesi saa laskentatehtävän, joka sen on ratkaistava varmistaakseen, että se on kelvollinen asiakas. Tätä käsitettä kutsutaan nimellä <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Työtodistus</a>. Tehtävä lasketaan muutamassa sekunnissa ja saat pääsyn verkkosivustolle. Kiitos ymmärryksestäsi ja kärsivällisyydestäsi.",
|
||||||
|
"js_challenge_data_missing": "Haastetiedot puuttuvat. Lataa sivu uudelleen."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Error sa pagkalkula!",
|
"js_calculation_error": "Error sa pagkalkula!",
|
||||||
"js_calculation_error_msg": "Nabigong ikalkula ang hamon:",
|
"js_calculation_error_msg": "Nabigong ikalkula ang hamon:",
|
||||||
"missing_required_forwarded_headers": "Nawawala ang kinakailangang X-Forwarded-* na mga header",
|
"missing_required_forwarded_headers": "Nawawala ang kinakailangang X-Forwarded-* na mga header",
|
||||||
"simplified_explanation": "Ito ay isang panukala laban sa mga bot at malisyosong mga kahilingan na katulad ng isang CAPTCHA. Gayunpaman, sa halip na ikaw mismo ang gumawa ng trabaho, binibigyan ang iyong browser ng isang gawain sa pagkalkula na kailangan nitong lutasin upang matiyak na ito ay isang wastong kliyente. Ang konseptong ito ay tinatawag na <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Ang gawain ay kinakalkula sa loob ng ilang segundo at binibigyan ka ng access sa website. Salamat sa iyong pag-unawa at pasensya."
|
"simplified_explanation": "Ito ay isang panukala laban sa mga bot at malisyosong mga kahilingan na katulad ng isang CAPTCHA. Gayunpaman, sa halip na ikaw mismo ang gumawa ng trabaho, binibigyan ang iyong browser ng isang gawain sa pagkalkula na kailangan nitong lutasin upang matiyak na ito ay isang wastong kliyente. Ang konseptong ito ay tinatawag na <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Ang gawain ay kinakalkula sa loob ng ilang segundo at binibigyan ka ng access sa website. Salamat sa iyong pag-unawa at pasensya.",
|
||||||
|
"js_challenge_data_missing": "Nawawala ang data ng hamon. Mangyaring i-reload ang pahina."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +1,67 @@
|
|||||||
{
|
{
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
"why_am_i_seeing": "Pourquoi je vois ceci ?",
|
"why_am_i_seeing": "Comment suis-je arrivé·e ici ?",
|
||||||
"protected_by": "Protégé par",
|
"protected_by": "Protégé par",
|
||||||
"protected_from": "From",
|
"protected_from": "de",
|
||||||
"made_with": "Fait avec ❤️ au 🇨🇦",
|
"made_with": "Fait avec ❤️ au 🇨🇦",
|
||||||
"mascot_design": "Design de la mascotte par",
|
"mascot_design": "Design de la mascotte par",
|
||||||
"ai_companies_explanation": "Vous voyez ceci car l'administrateur de ce site web a configuré Anubis pour protéger le serveur contre le fléau des entreprises d'IA qui scrapent agressivement les sites web. Cela peut et cause des temps d'arrêt pour les sites web, ce qui rend leurs ressources inaccessibles pour tout le monde.",
|
"ai_companies_explanation": "Vous voyez cette page car l'administrateur·rice de ce site Web a configuré Anubis pour protéger le serveur contre le fléau des entreprises d'IA qui récupèrent agressivement le contenu des sites Web. Cela perturbe leur fonctionnement et rend leurs ressources inaccessibles pour tout le monde.",
|
||||||
"anubis_compromise": "Anubis est un compromis. Anubis utilise un schéma de Preuve de Travail dans la veine de Hashcash, un schéma de preuve de travail proposé pour réduire le spam par email. L'idée est qu'à l'échelle individuelle, la charge supplémentaire est négligeable, mais à l'échelle des scrapers de masse, cela s'accumule et rend le scraping beaucoup plus coûteux.",
|
"anubis_compromise": "Anubis est un compromis. Anubis utilise un procédé de preuve de travail similaire à Hashcash, un procédé de preuve de travail proposé pour réduire le spam par e-mail. L'idée est qu'à l'échelle individuelle, la charge supplémentaire est négligeable, mais à l'échelle des scrapers de masse, la charge s'accumule et le scraping devient beaucoup plus coûteux.",
|
||||||
"hack_purpose": "En fin de compte, il s'agit d'une solution de substitution afin de consacrer plus de temps à l'identification et à l'empreinte digitale des navigateurs sans tête (par exemple, via leur rendu de police) afin que la page de preuve de travail du défi n'ait pas besoin d'être présentée aux utilisateurs qui sont beaucoup plus susceptibles d'être légitimes.",
|
"hack_purpose": "En fin de compte, il s'agit d'une solution de substitution permettant de consacrer plus de temps à l'identification et à la prise d'empreintes des navigateurs headless (par exemple, en reconnaissant leur rendu des polices), pour que, à terme, la page de défi utilisant la preuve de travail n'ait plus besoin d'être présentée aux utilisateur·rices qui sont beaucoup plus susceptibles d'être légitimes.",
|
||||||
"jshelter_note": "Veuillez noter qu'Anubis nécessite l'utilisation de fonctionnalités JavaScript modernes que des plugins comme JShelter désactiveront. Veuillez désactiver JShelter ou d'autres plugins similaires pour ce domaine.",
|
"jshelter_note": "Veuillez noter qu'Anubis nécessite l'utilisation de fonctionnalités JavaScript modernes qui peuvent être désactivées par des plugins comme JShelter. Veuillez désactiver JShelter ou tout autre plugin similaire pour ce domaine.",
|
||||||
"version_info": "Ce site web utilise Anubis version",
|
"version_info": "Ce site Web utilise Anubis version",
|
||||||
"try_again": "Réessayer",
|
"try_again": "Réessayer",
|
||||||
"go_home": "Accueil",
|
"go_home": "Accueil",
|
||||||
"contact_webmaster": "ou si vous pensez que vous ne devriez pas être bloqué, veuillez contacter le webmaster à",
|
"contact_webmaster": "ou si vous pensez que vous ne devriez pas être bloqué, veuillez contacter le webmaster à l'adresse",
|
||||||
"connection_security": "Veuillez patienter un instant pendant que nous assurons la sécurité de votre connexion.",
|
"connection_security": "Veuillez patienter un instant pendant que nous assurons la sécurité de votre connexion.",
|
||||||
"javascript_required": "Malheureusement, vous devez activer JavaScript pour passer ce défi. Ceci est requis car les entreprises d'IA ont changé le contrat social autour du fonctionnement de l'hébergement de sites web. Une solution sans JS est en cours de développement.",
|
"javascript_required": "Malheureusement, vous devez activer JavaScript pour passer cette page de défi. Cette obligation est imposée par les entreprises d'IA, qui ont décidé de modifier unilatéralement les termes du contrat social régissant l'hébergement de sites Web. Une solution sans JavaScript est en cours de développement.",
|
||||||
"benchmark_requires_js": "L'exécution de l'outil de benchmark nécessite l'activation de JavaScript.",
|
"benchmark_requires_js": "L'exécution de l'outil de benchmark nécessite l'activation de JavaScript.",
|
||||||
"difficulty": "Difficulté :",
|
"difficulty": "Difficulté :",
|
||||||
"algorithm": "Algorithme :",
|
"algorithm": "Algorithme :",
|
||||||
"compare": "Comparer :",
|
"compare": "Comparer :",
|
||||||
"time": "Temps",
|
"time": "Temps",
|
||||||
"iters": "Itérations",
|
"iters": "Itérations",
|
||||||
"time_a": "Temps A",
|
"time_a": "Temps A",
|
||||||
"iters_a": "Itér. A",
|
"iters_a": "Itér. A",
|
||||||
"time_b": "Temps B",
|
"time_b": "Temps B",
|
||||||
"iters_b": "Itér. B",
|
"iters_b": "Itér. B",
|
||||||
"static_check_endpoint": "Ceci est juste un point de terminaison de vérification pour votre proxy inverse à utiliser.",
|
"static_check_endpoint": "Ceci est juste un point de terminaison de vérification à utiliser par votre proxy inverse.",
|
||||||
"authorization_required": "Autorisation requise",
|
"authorization_required": "Autorisation requise",
|
||||||
"cookies_disabled": "Votre navigateur est configuré pour désactiver les cookies. Anubis nécessite des cookies pour l'intérêt légitime de s'assurer que vous êtes un client valide. Veuillez activer les cookies pour ce domaine",
|
"cookies_disabled": "Les cookies sont désactivés dans votre navigateur. Anubis a recours aux cookies pour l'intérêt légitime de s'assurer que vous êtes un client valide. Veuillez activer les cookies pour ce domaine.",
|
||||||
"access_denied": "Accès refusé : code d'erreur",
|
"access_denied": "Accès refusé : code d'erreur",
|
||||||
"dronebl_entry": "DroneBL a signalé une entrée",
|
"dronebl_entry": "DroneBL a rapporté une entrée",
|
||||||
"see_dronebl_lookup": "voir",
|
"see_dronebl_lookup": "voir",
|
||||||
"internal_server_error": "Erreur interne du serveur : l'administrateur a mal configuré Anubis. Veuillez contacter l'administrateur et lui demander de consulter les logs autour de",
|
"internal_server_error": "Erreur interne du serveur : l'administrateur·rice a mal configuré Anubis. Veuillez contacter l'administrateur·rice et lui demander de consulter les logs autour de",
|
||||||
"invalid_redirect": "Redirection invalide",
|
"invalid_redirect": "Redirection invalide",
|
||||||
"redirect_not_parseable": "URL de redirection non analysable",
|
"redirect_not_parseable": "URL de redirection non analysable",
|
||||||
"redirect_domain_not_allowed": "Domaine de redirection non autorisé",
|
"redirect_domain_not_allowed": "Domaine de redirection non autorisé",
|
||||||
"failed_to_sign_jwt": "échec de la signature JWT",
|
"failed_to_sign_jwt": "échec de la signature du JWT",
|
||||||
"invalid_invocation": "Invocation invalide de MakeChallenge",
|
"invalid_invocation": "Invocation invalide de MakeChallenge",
|
||||||
"client_error_browser": "Erreur client : Veuillez vous assurer que votre navigateur est à jour et réessayez plus tard.",
|
"client_error_browser": "Erreur client : Veuillez vous assurer que votre navigateur est à jour et réessayez plus tard.",
|
||||||
"oh_noes": "Oh non !",
|
"oh_noes": "Oh non !",
|
||||||
"benchmarking_anubis": "Test de performance d'Anubis !",
|
"benchmarking_anubis": "Je vérifie les performances d'Anubis !",
|
||||||
"you_are_not_a_bot": "Vous n'êtes pas un robot !",
|
"you_are_not_a_bot": "Vous n'êtes pas un robot !",
|
||||||
"making_sure_not_bot": "Vérification que vous n'êtes pas un robot !",
|
"making_sure_not_bot": "Je m'assure que vous n'êtes pas un robot !",
|
||||||
"celphase": "PHASE de CEL",
|
"celphase": "CELPHASE",
|
||||||
"js_web_crypto_error": "Votre navigateur n'a pas d'élément web.crypto fonctionnel. Consultez-vous cette page dans un contexte sécurisé ?",
|
"js_web_crypto_error": "L'élément web.crypto de votre navigateur n'est pas fonctionnel. Consultez-vous bien cette page dans un contexte sécurisé ?",
|
||||||
"js_web_workers_error": "Votre navigateur ne prend pas en charge les web workers (Anubis les utilise pour éviter de bloquer votre navigateur). Avez-vous un plugin comme JShelter installé ?",
|
"js_web_workers_error": "Votre navigateur ne prend pas en charge les web workers (Anubis les utilise pour éviter de bloquer votre navigateur). Avez-vous installé un plugin comme JShelter ?",
|
||||||
"js_cookies_error": "Votre navigateur ne stocke pas les cookies. Anubis utilise des cookies pour déterminer quels clients ont réussi les défis en stockant un jeton signé dans un cookie. Veuillez activer le stockage des cookies pour ce domaine. Les noms des cookies qu'Anubis stocke peuvent varier sans préavis. Les noms et valeurs des cookies ne font pas partie de l'API publique.",
|
"js_cookies_error": "Votre navigateur ne stocke pas les cookies. Anubis a recours aux cookies pour déterminer quels clients ont réussi les défis en stockant un jeton signé dans un cookie. Veuillez activer le stockage des cookies pour ce domaine. Le nom des cookies stockés par Anubis peut varier à tout moment. Le nom et la valeur des cookies ne font pas partie de l'API publique.",
|
||||||
"js_context_not_secure": "Votre contexte n'est pas sécurisé !",
|
"js_context_not_secure": "Votre contexte n'est pas sécurisé !",
|
||||||
"js_context_not_secure_msg": "Essayez de vous connecter via HTTPS ou informez l'administrateur de configurer HTTPS. Pour plus d'informations, voir <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
"js_context_not_secure_msg": "Essayez de vous connecter via HTTPS ou demandez à l'administrateur·rice de configurer HTTPS. Pour plus d'informations, consultez <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
||||||
"js_calculating": "Calcul en cours...",
|
"js_calculating": "Calcul en cours...",
|
||||||
"js_missing_feature": "Fonctionnalité manquante",
|
"js_missing_feature": "Fonctionnalité manquante",
|
||||||
"js_challenge_error": "Erreur de défi !",
|
"js_challenge_error": "Erreur de défi !",
|
||||||
"js_challenge_error_msg": "Échec de la résolution de l'algorithme de vérification. Vous pouvez essayer de recharger la page.",
|
"js_challenge_error_msg": "Échec de la résolution de l'algorithme de vérification. Vous pouvez essayer de recharger la page.",
|
||||||
"js_calculating_difficulty": "Calcul en cours...<br/>Difficulté :",
|
"js_calculating_difficulty": "Calcul en cours...<br/>Difficulté :",
|
||||||
"js_speed": "Vitesse :",
|
"js_speed": "Vitesse :",
|
||||||
"js_verification_longer": "La vérification prend plus de temps que prévu. Veuillez ne pas actualiser la page.",
|
"js_verification_longer": "La vérification prend plus de temps que prévu. Veuillez ne pas actualiser la page.",
|
||||||
"js_success": "Succès !",
|
"js_success": "Vérification réussie !",
|
||||||
"js_done_took": "Terminé ! A pris",
|
"js_done_took": "Terminé ! Cela aura nécessité",
|
||||||
"js_iterations": "itérations",
|
"js_iterations": "itérations",
|
||||||
"js_finished_reading": "J'ai fini de lire, continuer →",
|
"js_finished_reading": "J'ai fini de lire, continuer →",
|
||||||
"js_calculation_error": "Erreur de calcul !",
|
"js_calculation_error": "Erreur de calcul !",
|
||||||
"js_calculation_error_msg": "Échec du calcul du défi :",
|
"js_calculation_error_msg": "Échec du calcul du défi :",
|
||||||
"missing_required_forwarded_headers": "En-têtes X-Forwarded-* requis manquants",
|
"missing_required_forwarded_headers": "En-têtes X-Forwarded-* manquants",
|
||||||
"simplified_explanation": "Il s'agit d'une mesure contre les robots et les requêtes malveillantes similaire à un CAPTCHA. Cependant, au lieu d'avoir à faire le travail vous-même, votre navigateur se voit confier une tâche de calcul qu'il doit résoudre pour s'assurer qu'il est un client valide. Ce concept s'appelle <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Preuve de travail</a>. La tâche est calculée en quelques secondes et vous avez accès au site Web. Merci de votre compréhension et de votre patience."
|
"simplified_explanation": "Ceci est une mesure contre les robots et les requêtes malveillantes, similaire à un CAPTCHA. Cependant, au lieu d'avoir à faire le travail vous-même, votre navigateur se voit confier une tâche de calcul qu'il doit résoudre pour confirmer qu'il est un client valide. Ce concept est nommé <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Preuve de travail</a>. La tâche s'effectue en quelques secondes, puis vous avez accès au site Web. Merci pour votre compréhension et votre patience.",
|
||||||
|
"js_challenge_data_missing": "Les données du défi sont manquantes. Veuillez recharger la page."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Reiknivilla!",
|
"js_calculation_error": "Reiknivilla!",
|
||||||
"js_calculation_error_msg": "Mistókst að reikna áskorun:",
|
"js_calculation_error_msg": "Mistókst að reikna áskorun:",
|
||||||
"missing_required_forwarded_headers": "Vantar nauðsynleg X-Forwarded-* hausar",
|
"missing_required_forwarded_headers": "Vantar nauðsynleg X-Forwarded-* hausar",
|
||||||
"simplified_explanation": "Þetta er ráðstöfun gegn vélmennum og illa meinandi beiðnum, sem virkar svipað og CAPTCHA-mennskupróf. Hins vegar; í stað þess að þurfa að vinna sjálfur, fær vafrinn þinn útreikningsverkefni sem hann þarf að leysa til að tryggja að hann sé gildur biðlari. Þetta hugtak er kallað <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Sönnun-á-vinnu</a>. Verkefnið er reiknað á nokkrum sekúndum og þú færð aðgang að vefsíðunni. Takk fyrir skilninginn og þolinmæðina."
|
"simplified_explanation": "Þetta er ráðstöfun gegn vélmennum og illa meinandi beiðnum, sem virkar svipað og CAPTCHA-mennskupróf. Hins vegar; í stað þess að þurfa að vinna sjálfur, fær vafrinn þinn útreikningsverkefni sem hann þarf að leysa til að tryggja að hann sé gildur biðlari. Þetta hugtak er kallað <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Sönnun-á-vinnu</a>. Verkefnið er reiknað á nokkrum sekúndum og þú færð aðgang að vefsíðunni. Takk fyrir skilninginn og þolinmæðina.",
|
||||||
|
"js_challenge_data_missing": "Áskorunargögn vantar. Vinsamlegast endurhlaðið síðuna."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Errore nel calcolo!",
|
"js_calculation_error": "Errore nel calcolo!",
|
||||||
"js_calculation_error_msg": "Impossibile superare il test:",
|
"js_calculation_error_msg": "Impossibile superare il test:",
|
||||||
"missing_required_forwarded_headers": "Mancano gli header X-Forwarded-* richiesti",
|
"missing_required_forwarded_headers": "Mancano gli header X-Forwarded-* richiesti",
|
||||||
"simplified_explanation": "Questa è una misura contro bot e richieste dannose simile a un CAPTCHA. Tuttavia, invece di dover lavorare tu stesso, al tuo browser viene assegnato un compito di calcolo che deve risolvere per garantire che sia un client valido. Questo concetto è chiamato <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Il compito viene calcolato in pochi secondi e ti viene concesso l'accesso al sito web. Grazie per la tua comprensione e pazienza."
|
"simplified_explanation": "Questa è una misura contro bot e richieste dannose simile a un CAPTCHA. Tuttavia, invece di dover lavorare tu stesso, al tuo browser viene assegnato un compito di calcolo che deve risolvere per garantire che sia un client valido. Questo concetto è chiamato <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Il compito viene calcolato in pochi secondi e ti viene concesso l'accesso al sito web. Grazie per la tua comprensione e pazienza.",
|
||||||
|
"js_challenge_data_missing": "I dati della sfida sono mancanti. Ricarica la pagina."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "計算エラー!",
|
"js_calculation_error": "計算エラー!",
|
||||||
"js_calculation_error_msg": "チャレンジの計算に失敗しました:",
|
"js_calculation_error_msg": "チャレンジの計算に失敗しました:",
|
||||||
"missing_required_forwarded_headers": "必要な X-Forwarded-* ヘッダーがありません",
|
"missing_required_forwarded_headers": "必要な X-Forwarded-* ヘッダーがありません",
|
||||||
"simplified_explanation": "これは、CAPTCHAと同様の、ボットや悪意のあるリクエストに対する対策です。ただし、自分で作業する代わりに、ブラウザに計算タスクが与えられ、それを解決して有効なクライアントであることを確認する必要があります。この概念は<a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>と呼ばれます。タスクは数秒で計算され、ウェブサイトへのアクセスが許可されます。ご理解とご協力をお願いいたします。"
|
"simplified_explanation": "これは、CAPTCHAと同様の、ボットや悪意のあるリクエストに対する対策です。ただし、自分で作業する代わりに、ブラウザに計算タスクが与えられ、それを解決して有効なクライアントであることを確認する必要があります。この概念は<a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>と呼ばれます。タスクは数秒で計算され、ウェブサイトへのアクセスが許可されます。ご理解とご協力をお願いいたします。",
|
||||||
|
"js_challenge_data_missing": "チャレンジデータがありません。ページを再読み込みしてください。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"invalid_redirect": "Netinkamas nukreipimas",
|
"invalid_redirect": "Netinkamas nukreipimas",
|
||||||
"redirect_not_parseable": "Nukreipimo adreso nepavyko išanalizuoti",
|
"redirect_not_parseable": "Nukreipimo adreso nepavyko išanalizuoti",
|
||||||
"redirect_domain_not_allowed": "Nukreipimo domenas neleistinas",
|
"redirect_domain_not_allowed": "Nukreipimo domenas neleistinas",
|
||||||
"missing_required_forwarded_headers": "Trūksta būtinų „X-Forwarded-*“ antraščių",
|
"missing_required_forwarded_headers": "Trūksta privalomų X-Forwarded-* antraščių",
|
||||||
"failed_to_sign_jwt": "nepavyko pasirašyti JWT",
|
"failed_to_sign_jwt": "nepavyko pasirašyti JWT",
|
||||||
"invalid_invocation": "Netinkamas kreipinys į „MakeChallenge“",
|
"invalid_invocation": "Netinkamas kreipinys į „MakeChallenge“",
|
||||||
"client_error_browser": "Problema klientinėje dalyje: įsitikinkite, jog jūsų naršyklė nepasenusi ir bandykite dar kartą.",
|
"client_error_browser": "Problema klientinėje dalyje: įsitikinkite, jog jūsų naršyklė nepasenusi ir bandykite dar kartą.",
|
||||||
@@ -63,5 +63,5 @@
|
|||||||
"js_finished_reading": "Viską perskaičiau, tęskime →",
|
"js_finished_reading": "Viską perskaičiau, tęskime →",
|
||||||
"js_calculation_error": "Skaičiavimo klaida!",
|
"js_calculation_error": "Skaičiavimo klaida!",
|
||||||
"js_calculation_error_msg": "Nepavyko įveikti iššūkio:",
|
"js_calculation_error_msg": "Nepavyko įveikti iššūkio:",
|
||||||
"missing_required_forwarded_headers": "Trūksta privalomų X-Forwarded-* antraščių"
|
"js_challenge_data_missing": "Trūksta iššūkio duomenų. Prašome perkrauti puslapį."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Beregningsfeil!",
|
"js_calculation_error": "Beregningsfeil!",
|
||||||
"js_calculation_error_msg": "Mislyktes i å beregne utfordring:",
|
"js_calculation_error_msg": "Mislyktes i å beregne utfordring:",
|
||||||
"missing_required_forwarded_headers": "Mangler nødvendige X-Forwarded-* header",
|
"missing_required_forwarded_headers": "Mangler nødvendige X-Forwarded-* header",
|
||||||
"simplified_explanation": "Dette er et tiltak mot roboter og ondsinnede forespørsler som ligner på en CAPTCHA. Men i stedet for å måtte gjøre arbeidet selv, får nettleseren din en beregningsoppgave som den må løse for å sikre at den er en gyldig klient. Dette konseptet kalles <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Oppgaven beregnes på noen få sekunder, og du får tilgang til nettstedet. Takk for din forståelse og tålmodighet."
|
"simplified_explanation": "Dette er et tiltak mot roboter og ondsinnede forespørsler som ligner på en CAPTCHA. Men i stedet for å måtte gjøre arbeidet selv, får nettleseren din en beregningsoppgave som den må løse for å sikre at den er en gyldig klient. Dette konseptet kalles <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Oppgaven beregnes på noen få sekunder, og du får tilgang til nettstedet. Takk for din forståelse og tålmodighet.",
|
||||||
|
"js_challenge_data_missing": "Utfordringsdata mangler. Vennligst last inn siden på nytt."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Rekenfout!",
|
"js_calculation_error": "Rekenfout!",
|
||||||
"js_calculation_error_msg": "Uitdaging niet berekend:",
|
"js_calculation_error_msg": "Uitdaging niet berekend:",
|
||||||
"missing_required_forwarded_headers": "Ontbrekende vereiste X-Forwarded-* headers",
|
"missing_required_forwarded_headers": "Ontbrekende vereiste X-Forwarded-* headers",
|
||||||
"simplified_explanation": "Dit is een maatregel tegen bots en kwaadwillende verzoeken, vergelijkbaar met een CAPTCHA. In plaats van dat je zelf werk moet verrichten, krijgt je browser een rekentaak die moet worden opgelost om ervoor te zorgen dat het een geldige client is. Dit concept wordt <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a> genoemd. De taak wordt in een paar seconden berekend en u krijgt toegang tot de website. Bedankt voor je begrip en geduld."
|
"simplified_explanation": "Dit is een maatregel tegen bots en kwaadwillende verzoeken, vergelijkbaar met een CAPTCHA. In plaats van dat je zelf werk moet verrichten, krijgt je browser een rekentaak die moet worden opgelost om ervoor te zorgen dat het een geldige client is. Dit concept wordt <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a> genoemd. De taak wordt in een paar seconden berekend en u krijgt toegang tot de website. Bedankt voor je begrip en geduld.",
|
||||||
|
"js_challenge_data_missing": "Uitdagingsgegevens ontbreken. Herlaad de pagina."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Rekningsfeil!",
|
"js_calculation_error": "Rekningsfeil!",
|
||||||
"js_calculation_error_msg": "Mislukkast i å rekna utfordring:",
|
"js_calculation_error_msg": "Mislukkast i å rekna utfordring:",
|
||||||
"missing_required_forwarded_headers": "Vantande naudsynte «X-Forwarded-*»-overskrifter",
|
"missing_required_forwarded_headers": "Vantande naudsynte «X-Forwarded-*»-overskrifter",
|
||||||
"simplified_explanation": "Dette er eit tiltak mot robotar og ondsinna førespurnader som liknar på ein CAPTCHA. Men i staden for å måtte gjera arbeidet sjølv, får netlesaren din ei utrekningsoppgåve som han må løysa for å stadfesta at han er ein gyldig klient. Dette konseptet vert kalla <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">arbeidsstadfesting</a>. Oppgåva vert rekna ut på nokre få sekund, og du får tilgang til nettstaden. Takk for forståinga di og tolmodet ditt."
|
"simplified_explanation": "Dette er eit tiltak mot robotar og ondsinna førespurnader som liknar på ein CAPTCHA. Men i staden for å måtte gjera arbeidet sjølv, får netlesaren din ei utrekningsoppgåve som han må løysa for å stadfesta at han er ein gyldig klient. Dette konseptet vert kalla <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">arbeidsstadfesting</a>. Oppgåva vert rekna ut på nokre få sekund, og du får tilgang til nettstaden. Takk for forståinga di og tolmodet ditt.",
|
||||||
|
"js_challenge_data_missing": "Utfordringsdata manglar. Last inn sida på nytt."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_iterations": "iteracji",
|
"js_iterations": "iteracji",
|
||||||
"js_finished_reading": "Skończyłem czytać, kontynuuj →",
|
"js_finished_reading": "Skończyłem czytać, kontynuuj →",
|
||||||
"js_calculation_error": "Błąd obliczeń!",
|
"js_calculation_error": "Błąd obliczeń!",
|
||||||
"js_calculation_error_msg": "Nie udało się obliczyć zadania:"
|
"js_calculation_error_msg": "Nie udało się obliczyć zadania:",
|
||||||
|
"js_challenge_data_missing": "Brak danych wyzwania. Proszę odświeżyć stronę."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Erro de cálculo!",
|
"js_calculation_error": "Erro de cálculo!",
|
||||||
"js_calculation_error_msg": "Falha ao calcular a validação:",
|
"js_calculation_error_msg": "Falha ao calcular a validação:",
|
||||||
"missing_required_forwarded_headers": "Faltam os cabeçalhos X-Forwarded-* obrigatórios",
|
"missing_required_forwarded_headers": "Faltam os cabeçalhos X-Forwarded-* obrigatórios",
|
||||||
"simplified_explanation": "Esta é uma medida contra bots e solicitações maliciosas, semelhante a um CAPTCHA. No entanto, em vez de você mesmo ter que fazer o trabalho, seu navegador recebe uma tarefa de cálculo que ele deve resolver para garantir que seja um cliente válido. Esse conceito é chamado de <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Prova de Trabalho</a>. A tarefa é calculada em poucos segundos e você tem acesso ao site. Obrigado pela sua compreensão e paciência."
|
"simplified_explanation": "Esta é uma medida contra bots e solicitações maliciosas, semelhante a um CAPTCHA. No entanto, em vez de você mesmo ter que fazer o trabalho, seu navegador recebe uma tarefa de cálculo que ele deve resolver para garantir que seja um cliente válido. Esse conceito é chamado de <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Prova de Trabalho</a>. A tarefa é calculada em poucos segundos e você tem acesso ao site. Obrigado pela sua compreensão e paciência.",
|
||||||
|
"js_challenge_data_missing": "Os dados do desafio estão ausentes. Por favor, recarregue a página."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Ошибка расчёта!",
|
"js_calculation_error": "Ошибка расчёта!",
|
||||||
"js_calculation_error_msg": "Не удалось рассчитать задачу:",
|
"js_calculation_error_msg": "Не удалось рассчитать задачу:",
|
||||||
"missing_required_forwarded_headers": "Отсутствуют требуемые заголовки X-Forwarded-*",
|
"missing_required_forwarded_headers": "Отсутствуют требуемые заголовки X-Forwarded-*",
|
||||||
"simplified_explanation": "Это мера против ботов и вредоносных запросов, аналогичная CAPTCHA. Однако вместо того, чтобы вам приходилось работать самостоятельно, вашему браузеру дается задача вычисления, которую он должен решить, чтобы убедиться, что он является действительным клиентом. Эта концепция называется <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Доказательство выполнения работы</a>. Задача рассчитывается за несколько секунд, и вам предоставляется доступ к веб-сайту. Спасибо за понимание и терпение."
|
"simplified_explanation": "Это мера против ботов и вредоносных запросов, аналогичная CAPTCHA. Однако вместо того, чтобы вам приходилось работать самостоятельно, вашему браузеру дается задача вычисления, которую он должен решить, чтобы убедиться, что он является действительным клиентом. Эта концепция называется <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Доказательство выполнения работы</a>. Задача рассчитывается за несколько секунд, и вам предоставляется доступ к веб-сайту. Спасибо за понимание и терпение.",
|
||||||
|
"js_challenge_data_missing": "Данные проверки отсутствуют. Пожалуйста, перезагрузите страницу."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Beräkningsfel!",
|
"js_calculation_error": "Beräkningsfel!",
|
||||||
"js_calculation_error_msg": "Misslyckades att kalkylera utmaning:",
|
"js_calculation_error_msg": "Misslyckades att kalkylera utmaning:",
|
||||||
"missing_required_forwarded_headers": "Saknar nödvändiga X-Forwarded-* headers",
|
"missing_required_forwarded_headers": "Saknar nödvändiga X-Forwarded-* headers",
|
||||||
"simplified_explanation": "Detta är en åtgärd mot botar och skadliga förfrågningar som liknar en CAPTCHA. Men i stället för att du själv måste göra jobbet får din webbläsare en beräkningsuppgift som den måste lösa för att säkerställa att den är en giltig klient. Detta koncept kallas <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Arbetsbevis</a>. Uppgiften beräknas på några sekunder och du beviljas tillgång till webbplatsen. Tack för din förståelse och ditt tålamod."
|
"simplified_explanation": "Detta är en åtgärd mot botar och skadliga förfrågningar som liknar en CAPTCHA. Men i stället för att du själv måste göra jobbet får din webbläsare en beräkningsuppgift som den måste lösa för att säkerställa att den är en giltig klient. Detta koncept kallas <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Arbetsbevis</a>. Uppgiften beräknas på några sekunder och du beviljas tillgång till webbplatsen. Tack för din förståelse och ditt tålamod.",
|
||||||
|
"js_challenge_data_missing": "Utmaningsdata saknas. Vänligen ladda om sidan."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,5 +60,6 @@
|
|||||||
"js_iterations": "รอบ",
|
"js_iterations": "รอบ",
|
||||||
"js_finished_reading": "อ่านจบแล้ว ดำเนินการต่อ →",
|
"js_finished_reading": "อ่านจบแล้ว ดำเนินการต่อ →",
|
||||||
"js_calculation_error": "เกิดข้อผิดพลาดในการคำนวณ!",
|
"js_calculation_error": "เกิดข้อผิดพลาดในการคำนวณ!",
|
||||||
"js_calculation_error_msg": "ไม่สามารถคำนวณการท้าทายได้:"
|
"js_calculation_error_msg": "ไม่สามารถคำนวณการท้าทายได้:",
|
||||||
|
"js_challenge_data_missing": "ข้อมูลการท้าทายหายไป กรุณาโหลดหน้าใหม่"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "Hesaplama hatası!",
|
"js_calculation_error": "Hesaplama hatası!",
|
||||||
"js_calculation_error_msg": "Zorluk hesaplaması başarısız oldu:",
|
"js_calculation_error_msg": "Zorluk hesaplaması başarısız oldu:",
|
||||||
"missing_required_forwarded_headers": "Gerekli X-Forwarded-* başlıkları eksik",
|
"missing_required_forwarded_headers": "Gerekli X-Forwarded-* başlıkları eksik",
|
||||||
"simplified_explanation": "Bu, botlara ve kötü niyetli isteklere karşı CAPTCHA'ya benzer bir önlemdir. Ancak, kendiniz çalışmak yerine, tarayıcınıza geçerli bir istemci olduğundan emin olmak için çözmesi gereken bir hesaplama görevi verilir. Bu kavrama <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">İş Kanıtı</a> denir. Görev birkaç saniye içinde hesaplanır ve web sitesine erişim hakkı kazanırsınız. Anlayışınız ve sabrınız için teşekkür ederiz."
|
"simplified_explanation": "Bu, botlara ve kötü niyetli isteklere karşı CAPTCHA'ya benzer bir önlemdir. Ancak, kendiniz çalışmak yerine, tarayıcınıza geçerli bir istemci olduğundan emin olmak için çözmesi gereken bir hesaplama görevi verilir. Bu kavrama <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">İş Kanıtı</a> denir. Görev birkaç saniye içinde hesaplanır ve web sitesine erişim hakkı kazanırsınız. Anlayışınız ve sabrınız için teşekkür ederiz.",
|
||||||
|
"js_challenge_data_missing": "Doğrulama verileri eksik. Lütfen sayfayı yeniden yükleyin."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_iterations": "ітерацій",
|
"js_iterations": "ітерацій",
|
||||||
"js_finished_reading": "Читання завершено, продовжити →",
|
"js_finished_reading": "Читання завершено, продовжити →",
|
||||||
"js_calculation_error": "Помилка обчислення!",
|
"js_calculation_error": "Помилка обчислення!",
|
||||||
"js_calculation_error_msg": "Не вдалося обчислити перевірку:"
|
"js_calculation_error_msg": "Не вдалося обчислити перевірку:",
|
||||||
|
"js_challenge_data_missing": "Дані перевірки відсутні. Будь ласка, перезавантажте сторінку."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_iterations": "lần lặp lại",
|
"js_iterations": "lần lặp lại",
|
||||||
"js_finished_reading": "Tôi đã đọc xong, tiếp tục →",
|
"js_finished_reading": "Tôi đã đọc xong, tiếp tục →",
|
||||||
"js_calculation_error": "Lỗi tính toán!",
|
"js_calculation_error": "Lỗi tính toán!",
|
||||||
"js_calculation_error_msg": "Không thể tính toán thử thách:"
|
"js_calculation_error_msg": "Không thể tính toán thử thách:",
|
||||||
|
"js_challenge_data_missing": "Thiếu dữ liệu thử thách. Vui lòng tải lại trang."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "计算错误!",
|
"js_calculation_error": "计算错误!",
|
||||||
"js_calculation_error_msg": "计算挑战失败:",
|
"js_calculation_error_msg": "计算挑战失败:",
|
||||||
"missing_required_forwarded_headers": "缺少必要的 X-Forwarded-* 头",
|
"missing_required_forwarded_headers": "缺少必要的 X-Forwarded-* 头",
|
||||||
"simplified_explanation": "这是一种类似于验证码的措施,用于防止机器人和恶意请求。但是,您无需自己动手,您的浏览器会收到一个计算任务,必须解决该任务以确保它是有效的客户端。这个概念称为<a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">工作量证明</a>。该任务在几秒钟内计算完毕,您将被授予访问网站的权限。感谢您的理解和耐心。"
|
"simplified_explanation": "这是一种类似于验证码的措施,用于防止机器人和恶意请求。但是,您无需自己动手,您的浏览器会收到一个计算任务,必须解决该任务以确保它是有效的客户端。这个概念称为<a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">工作量证明</a>。该任务在几秒钟内计算完毕,您将被授予访问网站的权限。感谢您的理解和耐心。",
|
||||||
|
"js_challenge_data_missing": "验证数据缺失。请重新加载页面。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_calculation_error": "計算錯誤!",
|
"js_calculation_error": "計算錯誤!",
|
||||||
"js_calculation_error_msg": "計算挑戰失敗:",
|
"js_calculation_error_msg": "計算挑戰失敗:",
|
||||||
"missing_required_forwarded_headers": "缺少必要的 X-Forwarded-* 標頭",
|
"missing_required_forwarded_headers": "缺少必要的 X-Forwarded-* 標頭",
|
||||||
"simplified_explanation": "這是一種類似於驗證碼的措施,用於防止機器人和惡意請求。但是,您無需自己動手,您的瀏覽器會收到一個計算任務,必須解決該任務以確保它是有效的客戶端。這個概念稱為<a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">工作量證明</a>。該任務在幾秒鐘內計算完畢,您將被授予訪問網站的權限。感謝您的理解和耐心。"
|
"simplified_explanation": "這是一種類似於驗證碼的措施,用於防止機器人和惡意請求。但是,您無需自己動手,您的瀏覽器會收到一個計算任務,必須解決該任務以確保它是有效的客戶端。這個概念稱為<a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">工作量證明</a>。該任務在幾秒鐘內計算完畢,您將被授予訪問網站的權限。感謝您的理解和耐心。",
|
||||||
|
"js_challenge_data_missing": "驗證資料遺失。請重新載入頁面。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{
|
result, _, err := prog.Eval(map[string]any{
|
||||||
"headers": tt.headers,
|
"headers": tt.headers,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -168,7 +168,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{
|
result, _, err := prog.Eval(map[string]any{
|
||||||
"path": tt.path,
|
"path": tt.path,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -280,7 +280,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{})
|
result, _, err := prog.Eval(map[string]any{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
@@ -359,7 +359,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{})
|
result, _, err := prog.Eval(map[string]any{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
@@ -421,7 +421,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{})
|
result, _, err := prog.Eval(map[string]any{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
@@ -514,7 +514,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{})
|
result, _, err := prog.Eval(map[string]any{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
@@ -572,7 +572,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{})
|
result, _, err := prog.Eval(map[string]any{})
|
||||||
if tt.evalError {
|
if tt.evalError {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("%s: expected an evaluation error, but got none", tt.description)
|
t.Errorf("%s: expected an evaluation error, but got none", tt.description)
|
||||||
@@ -598,7 +598,7 @@ func TestThresholdEnvironment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
variables map[string]interface{}
|
variables map[string]any
|
||||||
name string
|
name string
|
||||||
expression string
|
expression string
|
||||||
description string
|
description string
|
||||||
@@ -608,7 +608,7 @@ func TestThresholdEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "weight-variable-available",
|
name: "weight-variable-available",
|
||||||
expression: `weight > 100`,
|
expression: `weight > 100`,
|
||||||
variables: map[string]interface{}{"weight": 150},
|
variables: map[string]any{"weight": 150},
|
||||||
expected: types.Bool(true),
|
expected: types.Bool(true),
|
||||||
description: "should support weight variable in expressions",
|
description: "should support weight variable in expressions",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -616,7 +616,7 @@ func TestThresholdEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "weight-variable-false-case",
|
name: "weight-variable-false-case",
|
||||||
expression: `weight > 100`,
|
expression: `weight > 100`,
|
||||||
variables: map[string]interface{}{"weight": 50},
|
variables: map[string]any{"weight": 50},
|
||||||
expected: types.Bool(false),
|
expected: types.Bool(false),
|
||||||
description: "should correctly evaluate weight comparisons",
|
description: "should correctly evaluate weight comparisons",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -624,7 +624,7 @@ func TestThresholdEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missingHeader-not-available",
|
name: "missingHeader-not-available",
|
||||||
expression: `missingHeader(headers, "Test")`,
|
expression: `missingHeader(headers, "Test")`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expected: types.Bool(false), // not used
|
expected: types.Bool(false), // not used
|
||||||
description: "should not have missingHeader function available",
|
description: "should not have missingHeader function available",
|
||||||
shouldCompile: false,
|
shouldCompile: false,
|
||||||
@@ -667,7 +667,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
expression string
|
expression string
|
||||||
variables map[string]interface{}
|
variables map[string]any
|
||||||
expectBool *bool // nil if we just want to test compilation or non-bool result
|
expectBool *bool // nil if we just want to test compilation or non-bool result
|
||||||
description string
|
description string
|
||||||
shouldCompile bool
|
shouldCompile bool
|
||||||
@@ -675,7 +675,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "randInt-function-compilation",
|
name: "randInt-function-compilation",
|
||||||
expression: `randInt(10)`,
|
expression: `randInt(10)`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expectBool: nil, // Don't check result, just compilation
|
expectBool: nil, // Don't check result, just compilation
|
||||||
description: "should compile randInt function",
|
description: "should compile randInt function",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -683,7 +683,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "randInt-range-validation",
|
name: "randInt-range-validation",
|
||||||
expression: `randInt(10) >= 0 && randInt(10) < 10`,
|
expression: `randInt(10) >= 0 && randInt(10) < 10`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expectBool: boolPtr(true),
|
expectBool: boolPtr(true),
|
||||||
description: "should return values in correct range",
|
description: "should return values in correct range",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -691,7 +691,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "strings-extension-size",
|
name: "strings-extension-size",
|
||||||
expression: `"hello".size() == 5`,
|
expression: `"hello".size() == 5`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expectBool: boolPtr(true),
|
expectBool: boolPtr(true),
|
||||||
description: "should support string extension functions",
|
description: "should support string extension functions",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -699,7 +699,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "strings-extension-contains",
|
name: "strings-extension-contains",
|
||||||
expression: `"hello world".contains("world")`,
|
expression: `"hello world".contains("world")`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expectBool: boolPtr(true),
|
expectBool: boolPtr(true),
|
||||||
description: "should support string contains function",
|
description: "should support string contains function",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -707,7 +707,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "strings-extension-startsWith",
|
name: "strings-extension-startsWith",
|
||||||
expression: `"hello world".startsWith("hello")`,
|
expression: `"hello world".startsWith("hello")`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expectBool: boolPtr(true),
|
expectBool: boolPtr(true),
|
||||||
description: "should support string startsWith function",
|
description: "should support string startsWith function",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
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) {
|
||||||
fin, err := os.Open(filepath.Join("..", "config", "testdata", "good", st.Name()))
|
fin, err := os.Open(filepath.Join("..", "config", "testdata", "good", st.Name()))
|
||||||
@@ -71,7 +70,6 @@ func TestBadConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
fin, err := os.Open(filepath.Join("..", "config", "testdata", "bad", st.Name()))
|
fin, err := os.Open(filepath.Join("..", "config", "testdata", "bad", st.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -36,9 +37,7 @@ func (m *mockS3) PutObject(ctx context.Context, in *s3.PutObjectInput, _ ...func
|
|||||||
m.data[aws.ToString(in.Key)] = bytes.Clone(b)
|
m.data[aws.ToString(in.Key)] = bytes.Clone(b)
|
||||||
if in.Metadata != nil {
|
if in.Metadata != nil {
|
||||||
m.meta[aws.ToString(in.Key)] = map[string]string{}
|
m.meta[aws.ToString(in.Key)] = map[string]string{}
|
||||||
for k, v := range in.Metadata {
|
maps.Copy(m.meta[aws.ToString(in.Key)], in.Metadata)
|
||||||
m.meta[aws.ToString(in.Key)][k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
m.bucket = aws.ToString(in.Bucket)
|
m.bucket = aws.ToString(in.Bucket)
|
||||||
return &s3.PutObjectOutput{}, nil
|
return &s3.PutObjectOutput{}, nil
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func (s Sentinel) Valid() error {
|
|||||||
// redisClient is satisfied by *valkey.Client and *valkey.ClusterClient.
|
// redisClient is satisfied by *valkey.Client and *valkey.ClusterClient.
|
||||||
type redisClient interface {
|
type redisClient interface {
|
||||||
Get(ctx context.Context, key string) *valkey.StringCmd
|
Get(ctx context.Context, key string) *valkey.StringCmd
|
||||||
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *valkey.StatusCmd
|
Set(ctx context.Context, key string, value any, expiration time.Duration) *valkey.StatusCmd
|
||||||
Del(ctx context.Context, keys ...string) *valkey.IntCmd
|
Del(ctx context.Context, keys ...string) *valkey.IntCmd
|
||||||
Ping(ctx context.Context) *valkey.StatusCmd
|
Ping(ctx context.Context) *valkey.StatusCmd
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
bots:
|
||||||
|
- name: deny
|
||||||
|
user_agent_regex: DENY
|
||||||
|
action: DENY
|
||||||
|
|
||||||
|
- name: challenge
|
||||||
|
user_agent_regex: CHALLENGE
|
||||||
|
action: CHALLENGE
|
||||||
|
|
||||||
|
- name: allow
|
||||||
|
user_agent_regex: ALLOW
|
||||||
|
action: ALLOW
|
||||||
+2
-2
@@ -11,8 +11,8 @@ func authUnaryClientInterceptor(token string) grpc.UnaryClientInterceptor {
|
|||||||
return func(
|
return func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
method string,
|
method string,
|
||||||
req interface{},
|
req any,
|
||||||
reply interface{},
|
reply any,
|
||||||
cc *grpc.ClientConn,
|
cc *grpc.ClientConn,
|
||||||
invoker grpc.UnaryInvoker,
|
invoker grpc.UnaryInvoker,
|
||||||
opts ...grpc.CallOption,
|
opts ...grpc.CallOption,
|
||||||
|
|||||||
Generated
+310
-282
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -20,11 +20,11 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^20.4.1",
|
"@commitlint/cli": "^20.4.3",
|
||||||
"@commitlint/config-conventional": "^20.4.1",
|
"@commitlint/config-conventional": "^20.4.3",
|
||||||
"baseline-browser-mapping": "^2.9.19",
|
"baseline-browser-mapping": "^2.10.0",
|
||||||
"cssnano": "^7.1.2",
|
"cssnano": "^7.1.3",
|
||||||
"cssnano-preset-advanced": "^7.0.10",
|
"cssnano-preset-advanced": "^7.0.11",
|
||||||
"esbuild": "^0.27.3",
|
"esbuild": "^0.27.3",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"playwright": "^1.52.0",
|
"playwright": "^1.52.0",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-crypto/sha256-js": "^5.2.0",
|
"@aws-crypto/sha256-js": "^5.2.0",
|
||||||
"preact": "^10.28.3"
|
"preact": "^10.28.4"
|
||||||
},
|
},
|
||||||
"commitlint": {
|
"commitlint": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
+10
-3
@@ -41,15 +41,22 @@ cp ../lib/localization/locales/*.json static/locales/
|
|||||||
|
|
||||||
shopt -s nullglob globstar
|
shopt -s nullglob globstar
|
||||||
|
|
||||||
for file in js/**/*.ts js/**/*.mjs; do
|
for file in js/**/*.ts js/**/*.tsx js/**/*.mjs; do
|
||||||
out="static/${file}"
|
out="static/${file}"
|
||||||
if [[ "$file" == *.ts ]]; then
|
if [[ "$file" == *.tsx ]]; then
|
||||||
|
out="static/${file%.tsx}.mjs"
|
||||||
|
elif [[ "$file" == *.ts ]]; then
|
||||||
out="static/${file%.ts}.mjs"
|
out="static/${file%.ts}.mjs"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$(dirname "$out")"
|
mkdir -p "$(dirname "$out")"
|
||||||
|
|
||||||
esbuild "$file" --sourcemap --bundle --minify --outfile="$out" --banner:js="$LICENSE"
|
JSX_FLAGS=""
|
||||||
|
if [[ "$file" == *.tsx ]]; then
|
||||||
|
JSX_FLAGS="--jsx=automatic --jsx-import-source=preact"
|
||||||
|
fi
|
||||||
|
|
||||||
|
esbuild "$file" --sourcemap --bundle --minify --outfile="$out" $JSX_FLAGS --banner:js="$LICENSE"
|
||||||
gzip -f -k -n "$out"
|
gzip -f -k -n "$out"
|
||||||
zstd -f -k --ultra -22 "$out"
|
zstd -f -k --ultra -22 "$out"
|
||||||
brotli -fZk "$out"
|
brotli -fZk "$out"
|
||||||
|
|||||||
-281
@@ -1,281 +0,0 @@
|
|||||||
import algorithms from "./algorithms";
|
|
||||||
|
|
||||||
// from Xeact
|
|
||||||
const u = (url: string = "", params: Record<string, any> = {}) => {
|
|
||||||
let result = new URL(url, window.location.href);
|
|
||||||
Object.entries(params).forEach(([k, v]) => result.searchParams.set(k, v));
|
|
||||||
return result.toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const j = (id: string): any | null => {
|
|
||||||
const elem = document.getElementById(id);
|
|
||||||
if (elem === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.parse(elem.textContent);
|
|
||||||
};
|
|
||||||
|
|
||||||
const imageURL = (mood, cacheBuster, basePrefix) =>
|
|
||||||
u(`${basePrefix}/.within.website/x/cmd/anubis/static/img/${mood}.webp`, {
|
|
||||||
cacheBuster,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Detect available languages by loading the manifest
|
|
||||||
const getAvailableLanguages = async () => {
|
|
||||||
const basePrefix = j("anubis_base_prefix");
|
|
||||||
if (basePrefix === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`,
|
|
||||||
);
|
|
||||||
if (response.ok) {
|
|
||||||
const manifest = await response.json();
|
|
||||||
return manifest.supportedLanguages || ["en"];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
"Failed to load language manifest, falling back to default languages",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to default languages if manifest loading fails
|
|
||||||
return ["en"];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use the browser language from the HTML lang attribute which is set by the server settings or request headers
|
|
||||||
const getBrowserLanguage = async () => document.documentElement.lang;
|
|
||||||
|
|
||||||
// Load translations from JSON files
|
|
||||||
const loadTranslations = async (lang) => {
|
|
||||||
const basePrefix = j("anubis_base_prefix");
|
|
||||||
if (basePrefix === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`,
|
|
||||||
);
|
|
||||||
return await response.json();
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
`Failed to load translations for ${lang}, falling back to English`,
|
|
||||||
);
|
|
||||||
if (lang !== "en") {
|
|
||||||
return await loadTranslations("en");
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRedirectUrl = () => {
|
|
||||||
const publicUrl = j("anubis_public_url");
|
|
||||||
if (publicUrl === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (publicUrl && window.location.href.startsWith(publicUrl)) {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
return urlParams.get("redir");
|
|
||||||
}
|
|
||||||
return window.location.href;
|
|
||||||
};
|
|
||||||
|
|
||||||
let translations = {};
|
|
||||||
let currentLang;
|
|
||||||
|
|
||||||
// Initialize translations
|
|
||||||
const initTranslations = async () => {
|
|
||||||
currentLang = await getBrowserLanguage();
|
|
||||||
translations = await loadTranslations(currentLang);
|
|
||||||
};
|
|
||||||
|
|
||||||
const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
// Initialize translations first
|
|
||||||
await initTranslations();
|
|
||||||
|
|
||||||
const dependencies = [
|
|
||||||
{
|
|
||||||
name: "Web Workers",
|
|
||||||
msg: t("web_workers_error"),
|
|
||||||
value: window.Worker,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Cookies",
|
|
||||||
msg: t("cookies_error"),
|
|
||||||
value: navigator.cookieEnabled,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const status: HTMLParagraphElement = document.getElementById(
|
|
||||||
"status",
|
|
||||||
) as HTMLParagraphElement;
|
|
||||||
const image: HTMLImageElement = document.getElementById(
|
|
||||||
"image",
|
|
||||||
) as HTMLImageElement;
|
|
||||||
const title: HTMLHeadingElement = document.getElementById(
|
|
||||||
"title",
|
|
||||||
) as HTMLHeadingElement;
|
|
||||||
const progress: HTMLDivElement = document.getElementById(
|
|
||||||
"progress",
|
|
||||||
) as HTMLDivElement;
|
|
||||||
|
|
||||||
const anubisVersion = j("anubis_version");
|
|
||||||
const basePrefix = j("anubis_base_prefix");
|
|
||||||
const details = document.querySelector("details");
|
|
||||||
let userReadDetails = false;
|
|
||||||
|
|
||||||
if (details) {
|
|
||||||
details.addEventListener("toggle", () => {
|
|
||||||
if (details.open) {
|
|
||||||
userReadDetails = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ohNoes = ({ titleMsg, statusMsg, imageSrc }) => {
|
|
||||||
title.innerHTML = titleMsg;
|
|
||||||
status.innerHTML = statusMsg;
|
|
||||||
image.src = imageSrc;
|
|
||||||
progress.style.display = "none";
|
|
||||||
};
|
|
||||||
|
|
||||||
status.innerHTML = t("calculating");
|
|
||||||
|
|
||||||
for (const { value, name, msg } of dependencies) {
|
|
||||||
if (!value) {
|
|
||||||
ohNoes({
|
|
||||||
titleMsg: `${t("missing_feature")} ${name}`,
|
|
||||||
statusMsg: msg,
|
|
||||||
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { challenge, rules } = j("anubis_challenge");
|
|
||||||
|
|
||||||
const process = algorithms[rules.algorithm];
|
|
||||||
if (!process) {
|
|
||||||
ohNoes({
|
|
||||||
titleMsg: t("challenge_error"),
|
|
||||||
statusMsg: t("challenge_error_msg"),
|
|
||||||
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
status.innerHTML = `${t("calculating_difficulty")} ${rules.difficulty}, `;
|
|
||||||
progress.style.display = "inline-block";
|
|
||||||
|
|
||||||
// the whole text, including "Speed:", as a single node, because some browsers
|
|
||||||
// (Firefox mobile) present screen readers with each node as a separate piece
|
|
||||||
// of text.
|
|
||||||
const rateText = document.createTextNode(`${t("speed")} 0kH/s`);
|
|
||||||
status.appendChild(rateText);
|
|
||||||
|
|
||||||
let lastSpeedUpdate = 0;
|
|
||||||
let showingApology = false;
|
|
||||||
const likelihood = Math.pow(16, -rules.difficulty);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const t0 = Date.now();
|
|
||||||
const { hash, nonce } = await process(
|
|
||||||
{ basePrefix, version: anubisVersion },
|
|
||||||
challenge.randomData,
|
|
||||||
rules.difficulty,
|
|
||||||
null,
|
|
||||||
(iters) => {
|
|
||||||
const delta = Date.now() - t0;
|
|
||||||
// only update the speed every second so it's less visually distracting
|
|
||||||
if (delta - lastSpeedUpdate > 1000) {
|
|
||||||
lastSpeedUpdate = delta;
|
|
||||||
rateText.data = `${t("speed")} ${(iters / delta).toFixed(3)}kH/s`;
|
|
||||||
}
|
|
||||||
// the probability of still being on the page is (1 - likelihood) ^ iters.
|
|
||||||
// by definition, half of the time the progress bar only gets to half, so
|
|
||||||
// apply a polynomial ease-out function to move faster in the beginning
|
|
||||||
// and then slow down as things get increasingly unlikely. quadratic felt
|
|
||||||
// the best in testing, but this may need adjustment in the future.
|
|
||||||
|
|
||||||
const probability = Math.pow(1 - likelihood, iters);
|
|
||||||
const distance = (1 - Math.pow(probability, 2)) * 100;
|
|
||||||
progress["aria-valuenow"] = distance;
|
|
||||||
if (progress.firstElementChild !== null) {
|
|
||||||
(progress.firstElementChild as HTMLElement).style.width =
|
|
||||||
`${distance}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (probability < 0.1 && !showingApology) {
|
|
||||||
status.append(
|
|
||||||
document.createElement("br"),
|
|
||||||
document.createTextNode(t("verification_longer")),
|
|
||||||
);
|
|
||||||
showingApology = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const t1 = Date.now();
|
|
||||||
console.log({ hash, nonce });
|
|
||||||
|
|
||||||
if (userReadDetails) {
|
|
||||||
const container: HTMLDivElement = document.getElementById(
|
|
||||||
"progress",
|
|
||||||
) as HTMLDivElement;
|
|
||||||
|
|
||||||
// Style progress bar as a continue button
|
|
||||||
container.style.display = "flex";
|
|
||||||
container.style.alignItems = "center";
|
|
||||||
container.style.justifyContent = "center";
|
|
||||||
container.style.height = "2rem";
|
|
||||||
container.style.borderRadius = "1rem";
|
|
||||||
container.style.cursor = "pointer";
|
|
||||||
container.style.background = "#b16286";
|
|
||||||
container.style.color = "white";
|
|
||||||
container.style.fontWeight = "bold";
|
|
||||||
container.style.outline = "4px solid #b16286";
|
|
||||||
container.style.outlineOffset = "2px";
|
|
||||||
container.style.width = "min(20rem, 90%)";
|
|
||||||
container.style.margin = "1rem auto 2rem";
|
|
||||||
container.innerHTML = t("finished_reading");
|
|
||||||
|
|
||||||
function onDetailsExpand() {
|
|
||||||
const redir = getRedirectUrl();
|
|
||||||
window.location.replace(
|
|
||||||
u(`${basePrefix}/.within.website/x/cmd/anubis/api/pass-challenge`, {
|
|
||||||
id: challenge.id,
|
|
||||||
response: hash,
|
|
||||||
nonce,
|
|
||||||
redir,
|
|
||||||
elapsedTime: t1 - t0,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
container.onclick = onDetailsExpand;
|
|
||||||
setTimeout(onDetailsExpand, 30000);
|
|
||||||
} else {
|
|
||||||
const redir = getRedirectUrl();
|
|
||||||
window.location.replace(
|
|
||||||
u(`${basePrefix}/.within.website/x/cmd/anubis/api/pass-challenge`, {
|
|
||||||
id: challenge.id,
|
|
||||||
response: hash,
|
|
||||||
nonce,
|
|
||||||
redir,
|
|
||||||
elapsedTime: t1 - t0,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
ohNoes({
|
|
||||||
titleMsg: t("calculation_error"),
|
|
||||||
statusMsg: `${t("calculation_error_msg")} ${err.message}`,
|
|
||||||
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
+336
@@ -0,0 +1,336 @@
|
|||||||
|
import { render } from "preact";
|
||||||
|
import { useState, useEffect, useRef } from "preact/hooks";
|
||||||
|
import algorithms from "./algorithms";
|
||||||
|
|
||||||
|
// from Xeact
|
||||||
|
const u = (url: string = "", params: Record<string, any> = {}) => {
|
||||||
|
let result = new URL(url, window.location.href);
|
||||||
|
Object.entries(params).forEach(([k, v]) => result.searchParams.set(k, v));
|
||||||
|
return result.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const j = (id: string): any | null => {
|
||||||
|
const elem = document.getElementById(id);
|
||||||
|
if (elem === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = elem.textContent;
|
||||||
|
if (text == null || text.trim() === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageURL = (
|
||||||
|
mood: string,
|
||||||
|
cacheBuster: string,
|
||||||
|
basePrefix: string,
|
||||||
|
): string =>
|
||||||
|
u(`${basePrefix}/.within.website/x/cmd/anubis/static/img/${mood}.webp`, {
|
||||||
|
cacheBuster,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use the browser language from the HTML lang attribute which is set by the server settings or request headers
|
||||||
|
const getBrowserLanguage = async () => document.documentElement.lang;
|
||||||
|
|
||||||
|
// Load translations from JSON files
|
||||||
|
const loadTranslations = async (lang: string) => {
|
||||||
|
const basePrefix = j("anubis_base_prefix");
|
||||||
|
if (basePrefix === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`,
|
||||||
|
);
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
`Failed to load translations for ${lang}, falling back to English`,
|
||||||
|
);
|
||||||
|
if (lang !== "en") {
|
||||||
|
return await loadTranslations("en");
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRedirectUrl = () => {
|
||||||
|
const publicUrl = j("anubis_public_url");
|
||||||
|
if (publicUrl === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (publicUrl && window.location.href.startsWith(publicUrl)) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
return urlParams.get("redir");
|
||||||
|
}
|
||||||
|
return window.location.href;
|
||||||
|
};
|
||||||
|
|
||||||
|
let translations: Record<string, string> = {};
|
||||||
|
|
||||||
|
// Initialize translations
|
||||||
|
const initTranslations = async () => {
|
||||||
|
const currentLang = await getBrowserLanguage();
|
||||||
|
translations = await loadTranslations(currentLang);
|
||||||
|
};
|
||||||
|
|
||||||
|
const t = (key: string): string =>
|
||||||
|
translations[`js_${key}`] || translations[key] || key;
|
||||||
|
|
||||||
|
interface AppProps {
|
||||||
|
anubisVersion: string;
|
||||||
|
basePrefix: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function App({ anubisVersion, basePrefix }: AppProps) {
|
||||||
|
const [phase, setPhase] = useState<
|
||||||
|
"loading" | "computing" | "reading" | "error"
|
||||||
|
>("loading");
|
||||||
|
|
||||||
|
// Error info
|
||||||
|
const [errorTitle, setErrorTitle] = useState("");
|
||||||
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
|
const [errorImage, setErrorImage] = useState("");
|
||||||
|
|
||||||
|
// Computing info
|
||||||
|
const [difficulty, setDifficulty] = useState(0);
|
||||||
|
const [speed, setSpeed] = useState("0kH/s");
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
const [showApology, setShowApology] = useState(false);
|
||||||
|
|
||||||
|
// Reading redirect callback
|
||||||
|
const redirectFn = useRef<(() => void) | null>(null);
|
||||||
|
const detailsRead = useRef(false);
|
||||||
|
|
||||||
|
// Sync <h1 id="title"> when entering error state (it's outside the Preact tree)
|
||||||
|
useEffect(() => {
|
||||||
|
if (phase === "error") {
|
||||||
|
const titleEl = document.getElementById("title");
|
||||||
|
if (titleEl) {
|
||||||
|
titleEl.textContent = errorTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [phase, errorTitle]);
|
||||||
|
|
||||||
|
// Main initialization
|
||||||
|
useEffect(() => {
|
||||||
|
const details = document.querySelector("details");
|
||||||
|
if (details) {
|
||||||
|
details.addEventListener("toggle", () => {
|
||||||
|
if (details.open) {
|
||||||
|
detailsRead.current = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const showError = (title: string, message: string, imageSrc: string) => {
|
||||||
|
setErrorTitle(title);
|
||||||
|
setErrorMessage(message);
|
||||||
|
setErrorImage(imageSrc);
|
||||||
|
setPhase("error");
|
||||||
|
};
|
||||||
|
|
||||||
|
const dependencies = [
|
||||||
|
{
|
||||||
|
name: "Web Workers",
|
||||||
|
msg: t("web_workers_error"),
|
||||||
|
value: window.Worker,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cookies",
|
||||||
|
msg: t("cookies_error"),
|
||||||
|
value: navigator.cookieEnabled,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { value, name, msg } of dependencies) {
|
||||||
|
if (!value) {
|
||||||
|
showError(
|
||||||
|
`${t("missing_feature")} ${name}`,
|
||||||
|
msg,
|
||||||
|
imageURL("reject", anubisVersion, basePrefix),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const challengeData = j("anubis_challenge");
|
||||||
|
if (!challengeData) {
|
||||||
|
showError(
|
||||||
|
t("challenge_error"),
|
||||||
|
t("challenge_data_missing"),
|
||||||
|
imageURL("reject", anubisVersion, basePrefix),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { challenge, rules } = challengeData;
|
||||||
|
|
||||||
|
const process = algorithms[rules.algorithm];
|
||||||
|
if (!process) {
|
||||||
|
showError(
|
||||||
|
t("challenge_error"),
|
||||||
|
t("challenge_error_msg"),
|
||||||
|
imageURL("reject", anubisVersion, basePrefix),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPhase("computing");
|
||||||
|
setDifficulty(rules.difficulty);
|
||||||
|
|
||||||
|
const likelihood = Math.pow(16, -rules.difficulty);
|
||||||
|
let lastSpeedUpdate = 0;
|
||||||
|
let apologyShown = false;
|
||||||
|
const t0 = Date.now();
|
||||||
|
|
||||||
|
process(
|
||||||
|
{ basePrefix, version: anubisVersion },
|
||||||
|
challenge.randomData,
|
||||||
|
rules.difficulty,
|
||||||
|
null,
|
||||||
|
(iters: number) => {
|
||||||
|
const delta = Date.now() - t0;
|
||||||
|
// only update the speed every second so it's less visually distracting
|
||||||
|
if (delta - lastSpeedUpdate > 1000) {
|
||||||
|
lastSpeedUpdate = delta;
|
||||||
|
setSpeed(`${(iters / delta).toFixed(3)}kH/s`);
|
||||||
|
}
|
||||||
|
// the probability of still being on the page is (1 - likelihood) ^ iters.
|
||||||
|
// by definition, half of the time the progress bar only gets to half, so
|
||||||
|
// apply a polynomial ease-out function to move faster in the beginning
|
||||||
|
// and then slow down as things get increasingly unlikely. quadratic felt
|
||||||
|
// the best in testing, but this may need adjustment in the future.
|
||||||
|
|
||||||
|
const probability = Math.pow(1 - likelihood, iters);
|
||||||
|
const distance = (1 - Math.pow(probability, 2)) * 100;
|
||||||
|
setProgress(distance);
|
||||||
|
|
||||||
|
if (probability < 0.1 && !apologyShown) {
|
||||||
|
apologyShown = true;
|
||||||
|
setShowApology(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then((result: any) => {
|
||||||
|
const t1 = Date.now();
|
||||||
|
const { hash, nonce } = result;
|
||||||
|
console.log({ hash, nonce });
|
||||||
|
|
||||||
|
const doRedirect = () => {
|
||||||
|
const redir = getRedirectUrl();
|
||||||
|
window.location.replace(
|
||||||
|
u(`${basePrefix}/.within.website/x/cmd/anubis/api/pass-challenge`, {
|
||||||
|
id: challenge.id,
|
||||||
|
response: hash,
|
||||||
|
nonce,
|
||||||
|
redir,
|
||||||
|
elapsedTime: t1 - t0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (detailsRead.current) {
|
||||||
|
redirectFn.current = doRedirect;
|
||||||
|
setPhase("reading");
|
||||||
|
setTimeout(doRedirect, 30000);
|
||||||
|
} else {
|
||||||
|
doRedirect();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
showError(
|
||||||
|
t("calculation_error"),
|
||||||
|
`${t("calculation_error_msg")} ${err.message}`,
|
||||||
|
imageURL("reject", anubisVersion, basePrefix),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const pensiveURL = imageURL("pensive", anubisVersion, basePrefix);
|
||||||
|
|
||||||
|
if (phase === "error") {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img style="width:100%;max-width:256px;" src={errorImage} />
|
||||||
|
<p id="status">{errorMessage}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phase === "loading") {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img style="width:100%;max-width:256px;" src={pensiveURL} />
|
||||||
|
<p id="status">{t("calculating")}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// computing or reading
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img style="width:100%;max-width:256px;" src={pensiveURL} />
|
||||||
|
<p id="status">
|
||||||
|
{`${t("calculating_difficulty")} ${difficulty}, `}
|
||||||
|
{`${t("speed")} ${speed}`}
|
||||||
|
{showApology && (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
{t("verification_longer")}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{phase === "reading" ? (
|
||||||
|
<button
|
||||||
|
id="progress"
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "2rem",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
background: "#b16286",
|
||||||
|
color: "white",
|
||||||
|
fontWeight: "bold",
|
||||||
|
outline: "4px solid #b16286",
|
||||||
|
outlineOffset: "2px",
|
||||||
|
width: "min(20rem, 90%)",
|
||||||
|
margin: "1rem auto 2rem",
|
||||||
|
border: "none",
|
||||||
|
fontSize: "inherit",
|
||||||
|
fontFamily: "inherit",
|
||||||
|
}}
|
||||||
|
onClick={() => redirectFn.current?.()}
|
||||||
|
>
|
||||||
|
{t("finished_reading")}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
id="progress"
|
||||||
|
role="progressbar"
|
||||||
|
aria-labelledby="status"
|
||||||
|
aria-valuenow={progress}
|
||||||
|
style={{ display: "inline-block" }}
|
||||||
|
>
|
||||||
|
<div class="bar-inner" style={{ width: `${progress}%` }}></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap: init translations, then mount Preact
|
||||||
|
(async () => {
|
||||||
|
await initTranslations();
|
||||||
|
const anubisVersion = j("anubis_version");
|
||||||
|
const basePrefix = j("anubis_base_prefix");
|
||||||
|
const root = document.getElementById("app");
|
||||||
|
if (root) {
|
||||||
|
render(<App anubisVersion={anubisVersion} basePrefix={basePrefix} />, root);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "preact",
|
||||||
|
"noEmit": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
"include": ["js/**/*.ts", "js/**/*.tsx"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user