Repository: TecharoHQ/anubis

Issue:
bug: CI is failing on main since #1038
- [ ] Investigate
- [ ] Fix root cause

Discussion:

Solution patch:
diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md
index 8bd209b..453d41a 100644
--- a/docs/docs/CHANGELOG.md
+++ b/docs/docs/CHANGELOG.md
@@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Add a "proof of React" challenge to prove that the client is able to run a simple JSX app.
 - Added possibility to disable HTTP keep-alive to support backends not properly
-  handling it
+  handling it.
+- Add a server-side check for the meta-refresh challenge that makes sure clients have waited for at least 95% of the time that they should.
 - Added a missing link to the Caddy installation environment in the installation documentation.
 - Downstream consumers can change the default [log/slog#Logger](https://pkg.go.dev/log/slog#Logger) instance that Anubis uses by setting `opts.Logger` to your slog instance of choice ([#864](https://github.com/TecharoHQ/anubis/issues/864)).
 - The [Thoth client](https://anubis.techaro.lol/docs/admin/thoth) is now public in the repo instead of being an internal package.
diff --git a/lib/challenge/metarefresh/metarefresh.go b/lib/challenge/metarefresh/metarefresh.go
index 7655a9d..5564ae2 100644
--- a/lib/challenge/metarefresh/metarefresh.go
+++ b/lib/challenge/metarefresh/metarefresh.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"log/slog"
 	"net/http"
+	"time"
 
 	"github.com/TecharoHQ/anubis"
 	"github.com/TecharoHQ/anubis/lib/challenge"
@@ -42,6 +43,12 @@ func (i *Impl) Issue(r *http.Request, lg *slog.Logger, in *challenge.IssueInput)
 }
 
 func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
+	wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 950 * time.Millisecond)
+
+	if time.Now().Before(wantTime) {
+		return challenge.NewError("validate", "insufficent time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339)))
+	}
+
 	gotChallenge := r.FormValue("challenge")
 
 	if subtle.ConstantTimeCompare([]byte(in.Challenge.RandomData), []byte(gotChallenge)) != 1 {
diff --git a/lib/localization/locales/lt.json b/lib/localization/locales/lt.json
index 39ba97e..a68e875 100644
--- a/lib/localization/locales/lt.json
+++ b/lib/localization/locales/lt.json
@@ -36,6 +36,7 @@
   "invalid_redirect": "Netinkamas nukreipimas",
   "redirect_not_parseable": "Nukreipimo adreso nepavyko išanalizuoti",
   "redirect_domain_not_allowed": "Nukreipimo domenas neleistinas",
+  "missing_required_forwarded_headers": "Trūksta būtinų X-Forwarded-* antraščių",
   "failed_to_sign_jwt": "nepavyko pasirašyti JWT",
   "invalid_invocation": "Netinkamas kreipinys į „MakeChallenge“",
   "client_error_browser": "Kliento klaida: įsitikinkite, jog jūsų naršyklė pakankamai atšviežinta ir bandykite dar kartą.",
@@ -62,4 +63,4 @@
   "js_finished_reading": "Viską perskaičiau, tęskime →",
   "js_calculation_error": "Skaičiavimo klaida!",
   "js_calculation_error_msg": "Nepavyko įveikti iššūkio:"
-}
+}
\ No newline at end of file
diff --git a/lib/localization/locales/vi.json b/lib/localization/locales/vi.json
index 3ffac7a..edaf20f 100644
--- a/lib/localization/locales/vi.json
+++ b/lib/localization/locales/vi.json
@@ -8,6 +8,7 @@
   "ai_companies_explanation": "Bạn đang thấy trang này do quản trị viên của trang web này đã thiết lập Anubis để bảo vệ máy chủ của họ khỏi quấy rầy từ những công ty AI hung hãn cóp nhặt nội dung khắp Internet. Điều này có thể và đã dẫn tới tình trạng gián đoạn hoạt động trên nhiều trang web, khiến tài nguyên tại đó nằm ngoài tầm với của mọi người.",
   "anubis_compromise": "Anubis là giải pháp thỏa hiệp. Anubis sử dụng cơ chế Proof-of-Work dựa trên Hashcash, được thiết kế ban đầu để giảm bớt email spam. Ý tưởng đằng sau đó là với người dùng cá nhân phần nạp thêm sẽ không đáng kể, nhưng ở tầm mức quy mô lớn sẽ cộng dồn và dẫn tới chi phí tiêu hao hơn rất nhiều.",
   "hack_purpose": "Chốt lại, đây cũng chỉ là giải pháp \"tạm ổn\" với mục đích thực sự là để giành thêm thời gian nhận diện và fingerprint những trình duyệt headless (VD: cách dựng font ra sao), sao cho hạn chế tối đa các yêu cầu tính toán trang thử thách Proof-of-Work tới nhóm người dùng có khả năng cao là con người hơn.",
+  "simplified_explanation": "Đây là một biện pháp chống lại bot và các yêu cầu độc hại tương tự như CAPTCHA. Tuy nhiên, thay vì bạn phải tự mình thực hiện, trình duyệt của bạn sẽ được giao một nhiệm vụ tính toán mà nó phải giải quyết để đảm bảo rằng nó là một máy khách hợp lệ. Khái niệm này được gọi là <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Bằng chứng Công việc</a>. Nhiệm vụ được tính toán trong vài giây và bạn được cấp quyền truy cập vào trang web. Cảm ơn sự thông cảm và kiên nhẫn của bạn.",
   "jshelter_note": "Vui lòng lưu ý Anubis cần sử dụng những tính năng JavaScript hiện đại mà một số phần mở rộng như JShelter sẽ tắt. Vui lòng vô hiệu hóa JShelter hoặc những phần mở rộng tương tự cho tên miền này.",
   "version_info": "Trang web này đang chạy Anubis phiên bản",
   "try_again": "Thử lại",
@@ -35,6 +36,7 @@
   "invalid_redirect": "Điều hướng không hợp lệ",
   "redirect_not_parseable": "Liên kết điều hướng không thể xử lý",
   "redirect_domain_not_allowed": "Tên miền điều hướng không được phép",
+  "missing_required_forwarded_headers": "Thiếu các tiêu đề X-Forwarded-* bắt buộc",
   "failed_to_sign_jwt": "không thể ký JWT",
   "invalid_invocation": "Gọi hàm MakeChallenge không hợp lệ",
   "client_error_browser": "Lỗi client: Vui lòng kiểm tra trình duyệt của bạn đã cập nhật và thử lại sau.",



Test patch:
diff --git a/test/git-clone/test.sh b/test/git-clone/test.sh
index 94ad663..130e6a5 100755
--- a/test/git-clone/test.sh
+++ b/test/git-clone/test.sh
@@ -7,12 +7,9 @@ export KO_DOCKER_REPO=ko.local
 
 set -u
 
-(
-  cd ../.. && \
-  npm ci && \
-  npm run assets \
-  ko build --platform=all --base-import-paths --tags="latest" --image-user=1000 --image-annotation="" --image-label="" ./cmd/anubis -L
-)
+source ../lib/lib.sh
+
+build_anubis_ko
 
 rm -rf ./var/repos ./var/clones
 mkdir -p ./var/repos ./var/clones
@@ -25,4 +22,4 @@ sleep 2
 
 (cd ./var/clones && git clone http://localhost:8005/status.git)
 
-docker compose down
\ No newline at end of file
+exit 0
\ No newline at end of file
diff --git a/test/git-push/test.sh b/test/git-push/test.sh
index 35ed9fe..a6d5f97 100755
--- a/test/git-push/test.sh
+++ b/test/git-push/test.sh
@@ -7,12 +7,9 @@ export KO_DOCKER_REPO=ko.local
 
 set -u
 
-(
-  cd ../.. && \
-  npm ci && \
-  npm run assets \
-  ko build --platform=all --base-import-paths --tags="latest" --image-user=1000 --image-annotation="" --image-label="" ./cmd/anubis -L
-)
+source ../lib/lib.sh
+
+build_anubis_ko
 
 rm -rf ./var/repos ./var/foo
 mkdir -p ./var/repos
@@ -36,4 +33,4 @@ sleep 2
   git push -u http://localhost:3000/git/foo.git master
 )
 
-docker compose down
\ No newline at end of file
+exit 0
\ No newline at end of file
diff --git a/test/healthcheck/test.sh b/test/healthcheck/test.sh
index 33f0a19..f357ff9 100755
--- a/test/healthcheck/test.sh
+++ b/test/healthcheck/test.sh
@@ -7,13 +7,9 @@ export KO_DOCKER_REPO=ko.local
 
 set -u
 
-(
-  cd ../.. && \
-  npm ci && \
-  npm run assets \
-  ko build --platform=all --base-import-paths --tags="latest" --image-user=1000 --image-annotation="" --image-label="" ./cmd/anubis -L
-)
+source ../lib/lib.sh
 
+build_anubis_ko
 docker compose up -d
 
 attempt=1
@@ -31,4 +27,4 @@ while ! docker compose ps | grep healthy; do
   attempt=$(( attempt + 1 ))
 done
 
-docker compose down
\ No newline at end of file
+exit 0
\ No newline at end of file
diff --git a/test/lib/lib.sh b/test/lib/lib.sh
index 2fc0b11..132848a 100644
--- a/test/lib/lib.sh
+++ b/test/lib/lib.sh
@@ -2,6 +2,8 @@ REPO_ROOT=$(git rev-parse --show-toplevel)
 (cd $REPO_ROOT && go install ./utils/cmd/...)
 
 function cleanup() {
+  set +e
+
   pkill -P $$
 
   if [ -f "docker-compose.yaml" ]; then
diff --git a/test/palemoon/amd64/test.sh b/test/palemoon/amd64/test.sh
index 3a0ccbe..7f85186 100755
--- a/test/palemoon/amd64/test.sh
+++ b/test/palemoon/amd64/test.sh
@@ -34,3 +34,5 @@ go run ../../cmd/cipra/ --compose-name $(basename $(pwd))
 
 docker compose down -t 1 || :
 docker compose rm -f || :
+
+exit 0