aboutsummaryrefslogtreecommitdiff
path: root/cmd/anubis
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2025-01-18 23:29:06 -0500
committerXe Iaso <me@xeiaso.net>2025-01-18 23:29:10 -0500
commit10a208640deac5474ceb15c553cda1c17776fcfd (patch)
tree0f70edc91495cf723983274e12ff68f5cbb06ba9 /cmd/anubis
parent84b152afc083ff5421c76e1eb0b7eac9e0f20569 (diff)
downloadx-10a208640deac5474ceb15c553cda1c17776fcfd.tar.xz
x-10a208640deac5474ceb15c553cda1c17776fcfd.zip
cmd/anubis: embed diagrams in README
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd/anubis')
-rw-r--r--cmd/anubis/README.md75
-rw-r--r--cmd/anubis/main.go13
2 files changed, 83 insertions, 5 deletions
diff --git a/cmd/anubis/README.md b/cmd/anubis/README.md
index 2de4847..bbfe795 100644
--- a/cmd/anubis/README.md
+++ b/cmd/anubis/README.md
@@ -30,6 +30,39 @@ For live chat, please join the [Patreon](https://patreon.com/cadey) and ask in t
Anubis uses a proof-of-work challenge to ensure that clients are using a modern browser and are able to calculate SHA-256 checksums. Anubis has a customizable difficulty for this proof-of-work challenge, but defaults to 5 leading zeroes.
+```mermaid
+---
+title: Challenge generation and validation
+---
+
+flowchart TD
+ Backend("Backend")
+ Fail("Fail")
+
+ style PresentChallenge color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF
+ style ValidateChallenge color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF
+ style Backend color:#FFFFFF, stroke:#00C853, fill:#00C853
+ style Fail color:#FFFFFF, stroke:#FF2962, fill:#FF2962
+
+ subgraph Server
+ PresentChallenge("Present Challenge")
+ ValidateChallenge("Validate Challenge")
+ end
+
+ subgraph Client
+ Main("main.mjs")
+ Worker("Worker")
+ end
+
+ Main -- Request challenge --> PresentChallenge
+ PresentChallenge -- Return challenge & difficulty --> Main
+ Main -- Spawn worker --> Worker
+ Worker -- Successful challenge --> Main
+ Main -- Validate challenge --> ValidateChallenge
+ ValidateChallenge -- Return cookie --> Backend
+ ValidateChallenge -- If anything is wrong --> Fail
+```
+
### Challenge presentation
Anubis decides to present a challenge using this logic:
@@ -40,6 +73,48 @@ Anubis decides to present a challenge using this logic:
This should ensure that git clients, RSS readers, and other low-harm clients can get through without issue, but high-risk clients such as browsers and AI scraper bots will get blocked.
+```mermaid
+---
+title: Challenge presentation logic
+---
+
+flowchart LR
+ Request("Request")
+ Backend("Backend")
+ %%Fail("Fail")
+ PresentChallenge("Present
+challenge")
+ HasMozilla{"Is browser
+or scraper?"}
+ HasCookie{"Has cookie?"}
+ HasExpired{"Cookie expired?"}
+ HasSignature{"Has valid
+signature?"}
+ RandomJitter{"Secondary
+screening?"}
+ POWPass{"Proof of
+work valid?"}
+
+ style PresentChallenge color:#FFFFFF, fill:#AA00FF, stroke:#AA00FF
+ style Backend color:#FFFFFF, stroke:#00C853, fill:#00C853
+ %%style Fail color:#FFFFFF, stroke:#FF2962, fill:#FF2962
+
+ Request --> HasMozilla
+ HasMozilla -- Yes --> HasCookie
+ HasMozilla -- No --> Backend
+ HasCookie -- Yes --> HasExpired
+ HasCookie -- No --> PresentChallenge
+ HasExpired -- Yes --> PresentChallenge
+ HasExpired -- No --> HasSignature
+ HasSignature -- Yes --> RandomJitter
+ HasSignature -- No --> PresentChallenge
+ RandomJitter -- Yes --> POWPass
+ RandomJitter -- No --> Backend
+ POWPass -- Yes --> Backend
+ PowPass -- No --> PresentChallenge
+ PresentChallenge -- Back again for another cycle --> Request
+```
+
### Proof of passing challenges
When a client passes a challenge, Anubis sets an HTTP cookie named `"within.website-x-cmd-anubis-auth"` containing a signed [JWT](https://jwt.io/) (JSON Web Token). This JWT contains the following claims:
diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go
index 2cef409..f3fa06b 100644
--- a/cmd/anubis/main.go
+++ b/cmd/anubis/main.go
@@ -202,6 +202,13 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request) {
return
}
+ if ckie.Expires.Before(time.Now()) {
+ slog.Debug("cookie expired", "path", r.URL.Path)
+ clearCookie(w)
+ s.renderIndex(w, r)
+ return
+ }
+
token, err := jwt.ParseWithClaims(ckie.Value, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
return s.pub, nil
})
@@ -244,11 +251,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request) {
if subtle.ConstantTimeCompare([]byte(claims["response"].(string)), []byte(calculated)) != 1 {
slog.Debug("invalid response", "path", r.URL.Path)
failedValidations.Inc()
- http.SetCookie(w, &http.Cookie{
- Name: cookieName,
- Value: "",
- Expires: time.Now().Add(-1 * time.Hour),
- })
+ clearCookie(w)
s.renderIndex(w, r)
return
}